Backend
인터페이스, 백엔드 분류
The contract a target writer implements.
import type { Backend } from 'typeshade'
시그니처, 설명, 예제는 커밋 c66579bf의 컴파일러 소스에서 그대로 가져온 영어 원문입니다.
구문
interface Backend { readonly id: string; readonly capProfile: CapProfile; readonly absentBuiltins?: ReadonlyMap<string, string>; typeName(t: ShaderType): string; literal(value: number | boolean, t: ShaderType): string; intrinsic(name: string, args: string[]): string; localLet(name: string, type: ShaderType, init: string): string; localVar(name: string, type: ShaderType, init?: string): string; constDecl(name: string, type: ShaderType, value: string): string; caseLabel(value: number, scrutType: ShaderType): string; caseLabels?(labels: readonly string[]): string; switchHead(scrut: string): string; readonly floatMod?: (a: string, b: string) => string; readonly intBinop?: ( bop: BinOp, b: Expr, aText: string, bText: string, type: ShaderType, ) => string | undefined; readonly floatToInt?: (to: ShaderType, from: ShaderType, argText: string) => string | undefined; readonly vectorCompare?: (cop: CmpOp, a: string, b: string) => string; readonly vectorSelect?: ( ifFalse: string, ifTrue: string, cond: string, type: ShaderType, ) => string; readonly caseBreak?: string; rawStmt(s: RawStmt): string; placeholderStmt(tag: string): string; readonly phonyAssign?: string; emitConst(c: ConstDecl): string; emitOverride?(o: OverrideDecl): string; emitStruct(s: StructDecl): string; emitBinding(b: BindingDecl): string; emitModuleVar?(v: ModuleVarDecl): string; emitFunc(f: FuncDecl, parens?: ParenMode): string; paramDecl?(p: FuncDecl['params'][number]): string; reference?(lvalue: string): string; dereference?(name: string): string; optimize(lowered: ModuleDecl): ModuleDecl; preOptimize?(lowered: ModuleDecl): ModuleDecl; postLower?(lowered: ModuleDecl): ModuleDecl; modulePreamble?(m: ModuleDecl): string;}설명
The package ships two backends, WGSL and GLSL
ES 3.00, and the shared emit driver never branches on which one it is writing for: every
target-specific decision (how types, literals and intrinsic calls are spelled, the handful
of statement fragments that differ between targets, and the module-level declarations) is
a method call into whichever Backend the caller passed.
capProfile is the one member that spells no code. The capability gate reads it before any
of the methods run, so a module that needs a capability the target lacks throws
UnsupportedFeatureError, naming the missing capabilities, before the backend can
emit source the driver would reject. The GLSL ES 3.00 profile has no row for
storageBuffer, compute or msaaTextureLoad, because WebGL2 has neither compute shaders
nor multisampled texture loads, while the WGSL profile gives the same three an empty row:
core, nothing to declare or activate. A capability can also be core on one target and an
extension on the other: floatRenderTarget is {} on WGSL and needs
EXT_color_buffer_float activated on GLSL. A backend whose target cannot represent a
declaration (GLSL has no syntax for a struct or binding at a bare emit site) throws the
same error from that method.
인스턴스 속성
id읽기 전용stringcapProfile읽기 전용CapProfileThe capability table for this target: neutral capability id to
{ directive?, hostFeature? }, and the only capability authority a backend carries. Coverage isCapabilities.fromProfile(capProfile),modulePreamblereads thedirectivefields off the same rows, andhostFeaturesForreads thehostFeaturefields, so a capability cannot be supported without its directive, or directed without being supported. A target gains support for a feature by gaining one row.absentBuiltins선택 사항읽기 전용ReadonlyMap<string, string>Optional. The
@builtin(<id>)ids this target lacks, each mapped to the message tail printed after<backend id>: @builtin(<id>). A pre-pass that runs beside the capability gate throwsUnsupportedFeatureErrorfor any of them, so the problem is named at the author’s module with the whole module as context. A backend whose own input/output translation already rejects every builtin it cannot map (GLSL ES 3.00 does) omits this.floatMod선택 사항읽기 전용(a: string, b: string) => stringOptional. Spelling for
%on float operands, for a target whose native%accepts integers only. GLSL ES 3.00 rejects float%, so the GLSL backend provides this. The operand texts arrive fully parenthesized (single atoms aside), and the returned expression must be wrapped in its own parentheses and must reproduce WGSL float%semantics: truncated modulo,a - b * trunc(a / b). GLSLmod()is floor modulo and gives a different answer for negative operands, so it is the wrong choice here. When absent, the native%is emitted.intBinop선택 사항읽기 전용( bop: BinOp, b: Expr, aText: string, bText: string, type: ShaderType, ) => string | undefinedOptional. Spelling for an integer
/,%or shift on a target whose bare operator gives some input WGSL settles no result (GLSL ES 3.00, Rule 11.12): a zero divisor, the leasti32over -1, a negative operand of%, a shift amount of 32 or more.bis the right operand as written, so a literal divisor or amount can keep the bare operator;aTextandbTextarrive as primaries, and the result is one too.undefinedkeeps the bare operator, and so does a target that omits this.floatToInt선택 사항읽기 전용(to: ShaderType, from: ShaderType, argText: string) => string | undefinedOptional. Spelling for a float’s conversion to the integer type
to, a scalar or a vector, on a target whose bare conversion gives an out-of-range or NaN source no result (GLSL ES 3.00, Rule 11.12); WGSL saturates, and leaves a NaN source indeterminate, which the oracle takes as 0.undefinedkeeps the bare conversion.vectorCompare선택 사항읽기 전용(cop: CmpOp, a: string, b: string) => stringOptional. Spelling for a comparison of two vectors, which yields a vector of bools (roadmap 0.2 item 7). WGSL has the operator form and omits this; GLSL ES 3.00 has only the functions
lessThan,equaland their siblings, so the GLSL backend provides it.vectorSelect선택 사항읽기 전용( ifFalse: string, ifTrue: string, cond: string, type: ShaderType, ) => stringOptional. Spelling for
select(f, t, c)whencis a vector of bools and the pick is per component. WGSL’sselecttakes it and omits this; GLSL ES 3.00’s ternary does not, so the GLSL backend spellsmix(f, t, c)for a float vector and a componentwise ternary otherwise.typeis the type of the result.caseBreak선택 사항읽기 전용stringOptional. A terminator written at the end of every
switchcase. WGSL cases do not fall through, so the WGSL backend omits this. GLSL follows C and does fall through, so the GLSL backend returnsbreak;; without it everymatch()arm would run into the next and the function would return the last arm’s value.phonyAssign선택 사항읽기 전용stringOptional. The prefix a value-dropping
callstatement takes when the callee is a value-returning builtin. WGSL treats every such builtin as@must_use, so a baremax(a, b);is rejected and the phony assignment_ = max(a, b);is required, while a user function’s dropped result is accepted bare. GLSL ES 3.00 takes the bare call in every case, so the GLSL backend omits this.
인스턴스 메서드
typeName(t: ShaderType): stringSpell a type for this target (e.g. WGSL
vec3<f32>vs GLSLvec3).literal(value: number | boolean, t: ShaderType): stringSpell a scalar literal for this target (e.g. WGSL
1uvs GLSL1).intrinsic(name: string, args: string[]): stringSpell an intrinsic or builtin call from already-emitted argument strings.
nameis the WGSL id of the function (the call node’sfn, plus the reserved'select'). The WGSL writer emitsname(args)as is; the GLSL writer remaps the names that differ (textureSample to texture, unpack4x8unorm to unpackUnorm4x8, bitcastto floatBitsToUint, select(f, t, c) to a ternary) and passes the rest through. Calls to user-defined functions also arrive here and pass through unchanged. localLet(name: string, type: ShaderType, init: string): stringlet n = init(WGSL, type inferred) vsT n = init(GLSL).localVar(name: string, type: ShaderType, init?: string): stringvar n: T[= init](WGSL) vsT n[= init](GLSL).constDecl(name: string, type: ShaderType, value: string): stringA module-level const declaration line, incl. trailing
;:const n: T = v;(WGSL) vsconst T n = v;(GLSL).caseLabel(value: number, scrutType: ShaderType): stringA
switchcase label:${v}ufor a u32 scrutinee on WGSL;${v}on GLSL.caseLabels(labels: readonly string[]): stringThe whole
case …:prefix for a clause, given each selector already spelled byBackend.caseLabel. Only a target whose multi-selector form is not WGSL’scase a, b:declares one: GLSL ES 3.00 stackscase a: case b:instead. Absent, the emitter writescase ${'${labels.join(\', \')}'}:, which is also the one-selector spelling every backend wrote before a clause could hold more than one.switchHead(scrut: string): stringThe
switchhead:switch ${scrut} {(WGSL) vsswitch (${scrut}) {(GLSL).rawStmt(s: RawStmt): stringA
rawstatement, the escape hatch that splices source text verbatim. It takes the whole node because the node carries one payload per target (wgslandglsl); each backend picks its own side here, and the shared emit walk stays target-blind. A backend whose side is absent throwsUnsupportedFeatureError(SD0030).placeholderStmt(tag: string): stringA
placeholderstatement that no composition step replaced before emit. The WGSL backend emits a comment carrying the tag; the GLSL backend throwsUnsupportedFeatureError.emitConst(c: ConstDecl): stringA module-level const declaration line, incl. trailing
;.emitOverride(o: OverrideDecl): stringOptional. A module-level specialization constant declaration. The WGSL writer emits
override name: T = default;, which the host can override at pipeline creation throughconstants: {}. The GLSL writer emits an#ifndef/#define/#endifblock that supplies the default; the host specializes by emitting again withemitGlslModule’soverrideValues, which the emitter places after the#versionline, since a#defineahead of#versionis invalid GLSL. A backend with no such construct omits this, and module assembly skips overrides for that target.emitStruct(s: StructDecl): stringA struct declaration block.
emitBinding(b: BindingDecl): stringA resource binding declaration line.
emitModuleVar(v: ModuleVarDecl): stringOptional. A module-scope variable that is not a resource (
ModuleVarDecl). The WGSL writer spellsvar<workgroup> x: T;andvar<private> y: T = init;; the GLSL writer spells a private variable as a plain global, which GLSL ES 3.00 gives every invocation its own copy of, and fails closed on a workgroup one, since WebGL2 has no workgroup memory. A GLSL global with no initializer is undefined, so that writer spells the zero of one the IR gives none; the zero of a struct lists its fields, whichemitGlslModulehas and this method alone does not, so here it fails closed. A backend that omits this cannot emit a module that declares one.emitFunc(f: FuncDecl, parens?: ParenMode): stringA function declaration block: the signature and the emitted body.
parensselects how many parentheses the shared expression walk writes,'full'or'minimal'; omitted means'full'. A backend forwards it to the body emitter.paramDecl(p: FuncDecl['params'][number]): stringOptional. How this target spells a parameter the callee writes through (
FuncDecl.params[i].mode === 'inout'): GLSL ES 3.00inout vec3 v, WGSLv: ptr<function, vec3<f32>>. A backend that omits it takes such a parameter by value, which is what every backend did before the mode existed.reference(lvalue: string): stringOptional, and required alongside
paramDeclwhen the spelling is a pointer rather than a qualifier.referenceis the argument a call passes for aninoutparameter (WGSL&x), anddereferenceis how the callee’s body reads the parameter itself (WGSL(*x)). GLSL needs neither: itsinoutargument and its uses are written plainly.dereference(name: string): stringoptimize(lowered: ModuleDecl): ModuleDeclThe backend’s emit-time optimization of the lowered module. Both shipped backends run the same optimization pipeline; the hook is per backend so that a target can choose differently without a change to the shared driver. It runs after the lowering passes and before the module is assembled into source.
preOptimize(lowered: ModuleDecl): ModuleDeclOptional. The target’s own lowerings, run after the shared lowering passes and before either optimizer tier — the explicit-level one as well as
optimize.The distinction is what a pass is, not when it happens to be convenient. An optimizer makes a module faster and may be skipped; a lowering makes it the module the target accepts and may not. WGSL’s uniform 16-byte array padding (§51) and its
@interpolate(flat)derivation (§53) are lowerings, and while they lived insideoptimizethe publicemitModuleAt(m, level)andlowerWgsl(m, level)skipped both and wrote the exact two programs the default path was fixed to stop writing — a bare@location(0) id: u32Tint refuses, and an unpadded uniform array.Before the optimizer, not after it in
postLower: the padding changes a struct’s member types and the reads that reach through them, and an optimizer that has not seen it hoists the unlowered form. LICM liftedU.weightsout of a loop aslet _licm0 = U.weights;and the padding then had nomembernode left to rewrite.postLower(lowered: ModuleDecl): ModuleDeclOptional. The last rewrite before the module is spelled, run after
optimizeand after every optimizer tier, for a target whose spelling needs a shape the IR does not carry. WGSL uses it to give a function with a pointer parameter one copy per address space its calls use, which is a fact about WGSL’s pointer types and about nothing else.modulePreamble(m: ModuleDecl): stringOptional. The module header carrying the source-level directives the module’s declared capabilities need on this target: one line per
m.enablesentry whosecapProfilerow has adirective(WGSLenable <d>;, GLSL ES 3.00#extension <d> : require), deduplicated and sorted so the byte order is deterministic.The contract is the same for every backend: the directive lines joined by
'\n', with no trailing separator, and''when there is nothing to direct (every declared capability is host-side, or none is declared). The caller separates the header from what follows and decides where it lands, because the two targets have different legal slots: the WGSL driver prepends it to the whole module with a blank line after it, and the GLSL assembler splices it in after#version 300 es, since GLSL ES 3.00 requires#versionto lead the file and#extensionto precede any non-preprocessor token. Both call it with the module as authored:enablesis an authoring-level declaration, and reading it off the lowered module would make the header depend on every pass preserving it.
함께 보기
소스
src/core/backend.ts, 170행, 커밋 c66579bf 기준