wesl 0.7.26 → 0.7.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +283 -147
- package/dist/index.js +1765 -1143
- package/package.json +2 -2
- package/src/AbstractElems.ts +264 -82
- package/src/ClickableError.ts +8 -1
- package/src/Linker.ts +63 -67
- package/src/LinkerUtil.ts +141 -7
- package/src/LowerAndEmit.ts +660 -304
- package/src/Mangler.ts +1 -1
- package/src/ModuleResolver.ts +15 -4
- package/src/ParseWESL.ts +21 -33
- package/src/SrcMap.ts +7 -19
- package/src/StandardTypes.ts +1 -1
- package/src/WeslDevice.ts +1 -0
- package/src/debug/ASTtoString.ts +92 -76
- package/src/index.ts +1 -1
- package/src/parse/AttachComments.ts +289 -0
- package/src/parse/ExpressionUtil.ts +3 -3
- package/src/parse/Keywords.ts +1 -1
- package/src/parse/ParseAttribute.ts +49 -71
- package/src/parse/ParseCall.ts +3 -4
- package/src/parse/ParseControlFlow.ts +100 -56
- package/src/parse/ParseDirective.ts +9 -8
- package/src/parse/ParseDoBlock.ts +63 -0
- package/src/parse/ParseExpression.ts +11 -10
- package/src/parse/ParseFn.ts +11 -33
- package/src/parse/ParseGlobalVar.ts +36 -27
- package/src/parse/ParseIdent.ts +4 -5
- package/src/parse/ParseLocalVar.ts +16 -12
- package/src/parse/ParseLoop.ts +76 -39
- package/src/parse/ParseModule.ts +65 -19
- package/src/parse/ParseSimpleStatement.ts +112 -66
- package/src/parse/ParseStatement.ts +40 -67
- package/src/parse/ParseStruct.ts +8 -22
- package/src/parse/ParseType.ts +10 -13
- package/src/parse/ParseUtil.ts +26 -12
- package/src/parse/ParseValueDeclaration.ts +18 -31
- package/src/parse/ParseWesl.ts +11 -14
- package/src/parse/ParsingContext.ts +11 -9
- package/src/parse/WeslStream.ts +138 -121
- package/src/parse/stream/RegexMatchers.ts +45 -0
- package/src/parse/stream/WeslLexer.ts +249 -0
- package/src/test/BevyLink.test.ts +18 -11
- package/src/test/ConditionalElif.test.ts +4 -2
- package/src/test/DeclCommentEmit.test.ts +122 -0
- package/src/test/DoBlock.test.ts +164 -0
- package/src/test/FilterValidElements.test.ts +4 -6
- package/src/test/Linker.test.ts +23 -7
- package/src/test/Mangling.test.ts +8 -4
- package/src/test/ParseComments.test.ts +234 -6
- package/src/test/ParseConditionsV2.test.ts +47 -175
- package/src/test/ParseElifV2.test.ts +12 -33
- package/src/test/ParseErrorV2.test.ts +0 -0
- package/src/test/ParseWeslV2.test.ts +247 -626
- package/src/test/StatementEmit.test.ts +143 -0
- package/src/test/StripWesl.ts +24 -3
- package/src/test/TestLink.ts +19 -3
- package/src/test/TestUtil.ts +18 -8
- package/src/test/Tokenizer.test.ts +96 -0
- package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +10 -42
- package/src/RawEmit.ts +0 -103
- package/src/Reflection.ts +0 -336
- package/src/TransformBindingStructs.ts +0 -320
- package/src/parse/ContentsHelpers.ts +0 -70
- package/src/parse/stream/CachingStream.ts +0 -48
- package/src/parse/stream/MatchersStream.ts +0 -85
package/src/Reflection.ts
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BindingStructElem,
|
|
3
|
-
NameElem,
|
|
4
|
-
StructMemberElem,
|
|
5
|
-
TextElem,
|
|
6
|
-
TranslateTimeExpressionElem,
|
|
7
|
-
TypeRefElem,
|
|
8
|
-
UnknownExpressionElem,
|
|
9
|
-
} from "./AbstractElems.ts";
|
|
10
|
-
import { assertThat } from "./Assertions.ts";
|
|
11
|
-
import type { TransformedAST, WeslJsPlugin } from "./Linker.ts";
|
|
12
|
-
import { identElemLog } from "./LinkerUtil.ts";
|
|
13
|
-
import { matchOneOf } from "./parse/stream/RegexHelpers.ts";
|
|
14
|
-
import type { RefIdent } from "./Scope.ts";
|
|
15
|
-
import {
|
|
16
|
-
multisampledTextureTypes,
|
|
17
|
-
sampledTextureTypes,
|
|
18
|
-
textureStorageTypes,
|
|
19
|
-
} from "./StandardTypes.ts";
|
|
20
|
-
import { findMap } from "./Util.ts";
|
|
21
|
-
|
|
22
|
-
export type BindingStructReportFn = (structs: BindingStructElem[]) => void;
|
|
23
|
-
export const textureStorage = matchOneOf(textureStorageTypes);
|
|
24
|
-
|
|
25
|
-
export function reportBindingStructsPlugin(
|
|
26
|
-
fn: BindingStructReportFn,
|
|
27
|
-
): WeslJsPlugin {
|
|
28
|
-
return {
|
|
29
|
-
transform: reportBindingStructs(fn),
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Linker plugin that generates TypeScript strings for GPUBindingGroupLayouts
|
|
34
|
-
* based on the binding structs in the WESL source
|
|
35
|
-
*
|
|
36
|
-
* requires the enableBindingStructs() transform to be enabled
|
|
37
|
-
*
|
|
38
|
-
* @param fn a function that will be called with the binding structs
|
|
39
|
-
* (Normally the caller will pass a function that uses bindingGroupLayoutTs()
|
|
40
|
-
* to generate the TypeScript)
|
|
41
|
-
*
|
|
42
|
-
* The generated TypeScript looks looks roughly like this
|
|
43
|
-
|
|
44
|
-
export function MyBindingLayout(device: GPUDevice): GPUBindGroupLayout {
|
|
45
|
-
return device.createBindGroupLayout({
|
|
46
|
-
entries: [
|
|
47
|
-
{
|
|
48
|
-
binding: 0,
|
|
49
|
-
visibility: GPUShaderStage.COMPUTE,
|
|
50
|
-
buffer: {
|
|
51
|
-
type: "storage",
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
*/
|
|
58
|
-
export function reportBindingStructs(
|
|
59
|
-
fn: BindingStructReportFn,
|
|
60
|
-
): (ast: TransformedAST) => TransformedAST {
|
|
61
|
-
return (ast: TransformedAST) => {
|
|
62
|
-
const structs = ast.notableElems.bindingStructs as BindingStructElem[];
|
|
63
|
-
fn(structs);
|
|
64
|
-
return ast;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function firstLetterLower(s: string): string {
|
|
69
|
-
return s[0].toLowerCase() + s.slice(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* @return a string containing a generated TypeScript function that creates
|
|
74
|
-
* a GPUBindingGroupLayout instance to align with the binding structures
|
|
75
|
-
* in wesl source.
|
|
76
|
-
*/
|
|
77
|
-
export function bindingGroupLayoutTs(
|
|
78
|
-
struct: BindingStructElem,
|
|
79
|
-
typeScript = true,
|
|
80
|
-
): string {
|
|
81
|
-
if (!struct) {
|
|
82
|
-
console.log("no struct!???");
|
|
83
|
-
return "";
|
|
84
|
-
}
|
|
85
|
-
const structName = firstLetterLower(struct.name.ident.mangledName!);
|
|
86
|
-
const visibility = shaderVisiblity(struct);
|
|
87
|
-
const entries = struct.members
|
|
88
|
-
.map(m => memberToLayoutEntry(m, visibility))
|
|
89
|
-
.join(",");
|
|
90
|
-
|
|
91
|
-
const fnName = `${structName}Layout`;
|
|
92
|
-
const entriesName = `${structName}Entries`;
|
|
93
|
-
|
|
94
|
-
const fnParams = typeScript
|
|
95
|
-
? "(device: GPUDevice): GPUBindGroupLayout"
|
|
96
|
-
: "(device)";
|
|
97
|
-
|
|
98
|
-
const src = `
|
|
99
|
-
const ${entriesName} = [ ${entries} ];
|
|
100
|
-
function ${fnName}${fnParams} {
|
|
101
|
-
return device.createBindGroupLayout({
|
|
102
|
-
entries: ${entriesName}
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export const layoutFunctions = { ${fnName} };
|
|
107
|
-
export const layouts = { ${entriesName} };
|
|
108
|
-
`;
|
|
109
|
-
return src;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** return the shader stage visibility for a binding struct, based on
|
|
113
|
-
* the shader entry function that has the binding struct as a parameter.
|
|
114
|
-
*
|
|
115
|
-
* The shader entry function is attached to the binding struct
|
|
116
|
-
* by the enableBindingStructs() transform.
|
|
117
|
-
*/
|
|
118
|
-
function shaderVisiblity(struct: BindingStructElem): string {
|
|
119
|
-
const { entryFn } = struct;
|
|
120
|
-
if (!entryFn) {
|
|
121
|
-
identElemLog(struct.name, "missing entry function for binding struct");
|
|
122
|
-
} else {
|
|
123
|
-
const { attributes = [] } = entryFn;
|
|
124
|
-
if (
|
|
125
|
-
attributes.find(
|
|
126
|
-
({ attribute: a }) => a.kind === "@attribute" && a.name === "compute",
|
|
127
|
-
)
|
|
128
|
-
) {
|
|
129
|
-
return "GPUShaderStage.COMPUTE";
|
|
130
|
-
}
|
|
131
|
-
if (
|
|
132
|
-
attributes.find(
|
|
133
|
-
({ attribute: a }) => a.kind === "@attribute" && a.name === "vertex",
|
|
134
|
-
)
|
|
135
|
-
) {
|
|
136
|
-
return "GPUShaderStage.VERTEX";
|
|
137
|
-
}
|
|
138
|
-
if (
|
|
139
|
-
attributes.find(
|
|
140
|
-
({ attribute: a }) => a.kind === "@attribute" && a.name === "fragment",
|
|
141
|
-
)
|
|
142
|
-
) {
|
|
143
|
-
return "GPUShaderStage.FRAGMENT";
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
identElemLog(struct.name, "unknown entry point type for binding struct");
|
|
147
|
-
return "GPUShaderStage.COMPUTE";
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* @return a GPUBindGroupLayoutEntry corresponding to one member
|
|
152
|
-
* of a WESL binding struct.
|
|
153
|
-
*/
|
|
154
|
-
function memberToLayoutEntry(
|
|
155
|
-
member: StructMemberElem,
|
|
156
|
-
visibility: string,
|
|
157
|
-
): string {
|
|
158
|
-
const bindingParam = findMap(member.attributes ?? [], ({ attribute: a }) =>
|
|
159
|
-
a.kind === "@attribute" && a.name === "binding" ? a : undefined,
|
|
160
|
-
)?.params?.[0];
|
|
161
|
-
const binding = bindingParam ? paramText(bindingParam) : "?";
|
|
162
|
-
|
|
163
|
-
const src = `
|
|
164
|
-
{
|
|
165
|
-
binding: ${binding},
|
|
166
|
-
visibility: ${visibility},
|
|
167
|
-
${layoutEntry(member)}
|
|
168
|
-
}`;
|
|
169
|
-
return src;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** @return the guts of the GPUBindGroupLayoutEntry for this binding struct member.
|
|
173
|
-
* ptr references to storage arrays become 'buffer' GPUBufferBindingLayout intances,
|
|
174
|
-
* references to WGSL samplers become 'sampler' GPUSamplerBindingLayout instances, etc.
|
|
175
|
-
*/
|
|
176
|
-
function layoutEntry(member: StructMemberElem): string {
|
|
177
|
-
const { typeRef } = member;
|
|
178
|
-
let entry: string | undefined;
|
|
179
|
-
const { name: typeName } = typeRef;
|
|
180
|
-
entry = ptrLayoutEntry(typeRef) ?? storageTextureLayoutEntry(typeRef);
|
|
181
|
-
if (!entry && typeof typeName !== "string" && typeName.std) {
|
|
182
|
-
entry =
|
|
183
|
-
samplerLayoutEntry(typeRef) ??
|
|
184
|
-
textureLayoutEntry(typeRef) ??
|
|
185
|
-
externalTextureLayoutEntry(typeRef);
|
|
186
|
-
}
|
|
187
|
-
if (!entry) {
|
|
188
|
-
console.error("unhandled type", typeName);
|
|
189
|
-
entry = "{ }";
|
|
190
|
-
}
|
|
191
|
-
return entry;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function ptrLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
195
|
-
if (typeRef.name.originalName === "ptr") {
|
|
196
|
-
const param1 = typeRef.templateParams?.[0];
|
|
197
|
-
const param3 = typeRef.templateParams?.[2];
|
|
198
|
-
if (param1?.kind === "type" && param1.name.originalName === "uniform") {
|
|
199
|
-
return `buffer: { type: "uniform" }`;
|
|
200
|
-
} else if (
|
|
201
|
-
param1?.kind === "type" &&
|
|
202
|
-
param1.name.originalName === "storage"
|
|
203
|
-
) {
|
|
204
|
-
if (param3?.kind === "type" && param3.name.originalName === "read") {
|
|
205
|
-
return `buffer: { type: "read-only-storage" }`;
|
|
206
|
-
} else {
|
|
207
|
-
return `buffer: { type: "storage" }`;
|
|
208
|
-
}
|
|
209
|
-
// LATER what do we do with the element type (2nd parameter)
|
|
210
|
-
// LATER should there be an ability to set hasDynamicOffset?
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function samplerLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
216
|
-
const { originalName } = typeRef.name as RefIdent;
|
|
217
|
-
if (originalName === "sampler") {
|
|
218
|
-
// LATER how do we set: type GPUSamplerBindingType = | "filtering" | "non-filtering";
|
|
219
|
-
// (just assuming filtering as a placeholder for now)
|
|
220
|
-
return `sampler: { type: "filtering" }`;
|
|
221
|
-
}
|
|
222
|
-
if (originalName === "sampler_comparison") {
|
|
223
|
-
return `sampler: { type: "comparison" }`;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const textureTypes = matchOneOf(sampledTextureTypes);
|
|
228
|
-
const multiNames = matchOneOf(multisampledTextureTypes);
|
|
229
|
-
|
|
230
|
-
function textureLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
231
|
-
const { originalName } = typeRef.name as RefIdent;
|
|
232
|
-
const multisampled = multiNames.test(originalName)
|
|
233
|
-
? ", multisampled: true, "
|
|
234
|
-
: "";
|
|
235
|
-
if (multisampled || textureTypes.test(originalName)) {
|
|
236
|
-
// LATER viewDimension
|
|
237
|
-
const sampleType = getSampleType(typeRef);
|
|
238
|
-
return `texture: { sampleType: "${sampleType}"${multisampled} }`;
|
|
239
|
-
}
|
|
240
|
-
return undefined;
|
|
241
|
-
|
|
242
|
-
function getSampleType(typeRef: TypeRefElem): GPUTextureSampleType {
|
|
243
|
-
const firstParam = typeRef.templateParams?.[0] as TypeRefElem;
|
|
244
|
-
const texelType = (firstParam.name as RefIdent)
|
|
245
|
-
.originalName as WgslTexelType;
|
|
246
|
-
const sampleType = texelTypeToSampleType(texelType);
|
|
247
|
-
return sampleType;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function storageTextureLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
252
|
-
if (textureStorage.test(typeRef.name.originalName)) {
|
|
253
|
-
const firstParam = typeRef.templateParams?.[0];
|
|
254
|
-
const secondParam = typeRef.templateParams?.[1];
|
|
255
|
-
assertThat(firstParam?.kind === "type"); // LATER: Temp hack
|
|
256
|
-
assertThat(secondParam?.kind === "type"); // LATER: Temp hack
|
|
257
|
-
const sampleType = formatToTextureSampleType(
|
|
258
|
-
firstParam.name.originalName as GPUTextureFormat,
|
|
259
|
-
);
|
|
260
|
-
const access = accessMode(secondParam.name.originalName);
|
|
261
|
-
return `storageTexture: { format: "${firstParam.name.originalName}", sampleType: "${sampleType}", access: "${access}" }`;
|
|
262
|
-
}
|
|
263
|
-
return undefined;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function externalTextureLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
267
|
-
const { originalName } = typeRef.name as RefIdent;
|
|
268
|
-
if (originalName === "texture_external") {
|
|
269
|
-
// LATER. how would we set the required source: HTMLVideoElement or VideoFrame?
|
|
270
|
-
}
|
|
271
|
-
return undefined;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function paramText(
|
|
275
|
-
expression: UnknownExpressionElem | NameElem | TranslateTimeExpressionElem,
|
|
276
|
-
): string {
|
|
277
|
-
assertThat(
|
|
278
|
-
expression.kind === "expression",
|
|
279
|
-
"Only expression elements are supported in this position",
|
|
280
|
-
);
|
|
281
|
-
const text = expression.contents[0] as TextElem;
|
|
282
|
-
return text.srcModule.src.slice(expression.start, expression.end);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function formatToTextureSampleType(
|
|
286
|
-
format: GPUTextureFormat,
|
|
287
|
-
float32Filterable = false,
|
|
288
|
-
): GPUTextureSampleType {
|
|
289
|
-
if (format.includes("32float")) {
|
|
290
|
-
return float32Filterable ? "float" : "unfilterable-float";
|
|
291
|
-
}
|
|
292
|
-
if (format.includes("float") || format.includes("unorm")) {
|
|
293
|
-
return "float";
|
|
294
|
-
}
|
|
295
|
-
if (format.includes("uint")) {
|
|
296
|
-
return "uint";
|
|
297
|
-
}
|
|
298
|
-
if (format.includes("sint")) {
|
|
299
|
-
return "sint";
|
|
300
|
-
}
|
|
301
|
-
throw new Error(`native sample type unknwon for texture format ${format}`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export type WgslTexelType = "f32" | "u32" | "i32";
|
|
305
|
-
|
|
306
|
-
/** return the wgsl element type for a given texture format */
|
|
307
|
-
export function formatToTexelType(format: GPUTextureFormat): WgslTexelType {
|
|
308
|
-
if (format.includes("float")) return "f32";
|
|
309
|
-
if (format.includes("unorm")) return "f32";
|
|
310
|
-
if (format.includes("uint")) return "u32";
|
|
311
|
-
if (format.includes("sint")) return "i32";
|
|
312
|
-
throw new Error(`unknown format ${format}`);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/** @return the webgpu GPUTextureSampleType from the wgsl texel type */
|
|
316
|
-
export function texelTypeToSampleType(
|
|
317
|
-
type: WgslTexelType,
|
|
318
|
-
): GPUTextureSampleType {
|
|
319
|
-
if (type === "f32") return "float";
|
|
320
|
-
if (type === "u32") return "uint";
|
|
321
|
-
if (type === "i32") return "sint";
|
|
322
|
-
throw new Error(`unknown texel type ${type}`);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function accessMode(access: string): GPUStorageTextureAccess {
|
|
326
|
-
if (access === "read") {
|
|
327
|
-
return "read-only";
|
|
328
|
-
}
|
|
329
|
-
if (access === "write") {
|
|
330
|
-
return "write-only";
|
|
331
|
-
}
|
|
332
|
-
if (access === "read_write") {
|
|
333
|
-
return "read-write";
|
|
334
|
-
}
|
|
335
|
-
throw new Error(`unknown access mode: ${access}`);
|
|
336
|
-
}
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AbstractElem,
|
|
3
|
-
AttributeElem,
|
|
4
|
-
BindingStructElem,
|
|
5
|
-
DeclarationElem,
|
|
6
|
-
FnElem,
|
|
7
|
-
ModuleElem,
|
|
8
|
-
SimpleMemberRef,
|
|
9
|
-
StructElem,
|
|
10
|
-
SyntheticElem,
|
|
11
|
-
TypeTemplateParameter,
|
|
12
|
-
} from "./AbstractElems.ts";
|
|
13
|
-
import type { TransformedAST, WeslJsPlugin } from "./Linker.ts";
|
|
14
|
-
import { visitAst } from "./LinkerUtil.ts";
|
|
15
|
-
import { debug } from "./Logging.ts";
|
|
16
|
-
import { findDecl } from "./LowerAndEmit.ts";
|
|
17
|
-
import { minimallyMangledName } from "./Mangler.ts";
|
|
18
|
-
import {
|
|
19
|
-
attributeToString,
|
|
20
|
-
contentsToString,
|
|
21
|
-
typeListToString,
|
|
22
|
-
typeParamToString,
|
|
23
|
-
} from "./RawEmit.ts";
|
|
24
|
-
import { textureStorage } from "./Reflection.ts";
|
|
25
|
-
import type { DeclIdent, Ident, RefIdent } from "./Scope.ts";
|
|
26
|
-
import { filterMap } from "./Util.ts";
|
|
27
|
-
|
|
28
|
-
export function bindingStructsPlugin(): WeslJsPlugin {
|
|
29
|
-
return {
|
|
30
|
-
transform: lowerBindingStructs,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Transform binding structures into binding variables by mutating the AST.
|
|
36
|
-
*
|
|
37
|
-
* First we find all the binding structs:
|
|
38
|
-
* . find all the structs in the module by filtering the moduleElem.contents
|
|
39
|
-
* . for each struct:
|
|
40
|
-
* . mark any structs with that contain @group or @binding annotations as 'binding structs' and save them in a list
|
|
41
|
-
* . (later) create reverse links from structs to struct members
|
|
42
|
-
* . (later) visit all the binding structs and traverse to referencing structs, marking the referencing structs as binding structs too
|
|
43
|
-
* Generate synethic AST nodes for binding variables
|
|
44
|
-
*
|
|
45
|
-
* Find all references to binding struct members
|
|
46
|
-
* . find the componound idents by traversing moduleElem.contents
|
|
47
|
-
* . filter to find the compound idents that refer to 'binding structs'
|
|
48
|
-
* . go from each ident to its declaration,
|
|
49
|
-
* . declaration to typeRef reference
|
|
50
|
-
* . typeRef to type declaration
|
|
51
|
-
* . check type declaration to see if it's a binding struct
|
|
52
|
-
* . record the intermediate declaration (e.g. a fn param b:Bindings from 'fn(b:Bindings)' )
|
|
53
|
-
* rewrite references to binding struct members as synthetic elements
|
|
54
|
-
*
|
|
55
|
-
* Remove the binding structs from the AST
|
|
56
|
-
* Remove the intermediate fn param declarations from the AST
|
|
57
|
-
* Add the new binding variables to the AST
|
|
58
|
-
*
|
|
59
|
-
* @return the binding structs and the mutated AST
|
|
60
|
-
*/
|
|
61
|
-
export function lowerBindingStructs(ast: TransformedAST): TransformedAST {
|
|
62
|
-
const clonedAst = structuredClone(ast);
|
|
63
|
-
const { moduleElem, globalNames, notableElems } = clonedAst;
|
|
64
|
-
const bindingStructs = markBindingStructs(moduleElem); // CONSIDER should we only mark bining structs referenced from the entry point?
|
|
65
|
-
markEntryTypes(moduleElem, bindingStructs);
|
|
66
|
-
const newVars = bindingStructs.flatMap(s =>
|
|
67
|
-
transformBindingStruct(s, globalNames),
|
|
68
|
-
);
|
|
69
|
-
const bindingRefs = findRefsToBindingStructs(moduleElem);
|
|
70
|
-
|
|
71
|
-
// convert references 'b.particles' to references to the synthetic var 'particles'
|
|
72
|
-
bindingRefs.forEach(({ memberRef, struct }) => {
|
|
73
|
-
transformBindingReference(memberRef, struct);
|
|
74
|
-
});
|
|
75
|
-
// remove intermediate fn param declaration b:Bindings from 'fn(b:Bindings)'
|
|
76
|
-
bindingRefs.forEach(({ intermediates }) => {
|
|
77
|
-
intermediates.forEach(e => {
|
|
78
|
-
e.contents = [];
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
const contents = removeBindingStructs(moduleElem);
|
|
82
|
-
moduleElem.contents = [...newVars, ...contents];
|
|
83
|
-
notableElems.bindingStructs = bindingStructs;
|
|
84
|
-
return { ...clonedAst, moduleElem };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function markEntryTypes(
|
|
88
|
-
moduleElem: ModuleElem,
|
|
89
|
-
bindingStructs: BindingStructElem[],
|
|
90
|
-
): void {
|
|
91
|
-
const fns = moduleElem.contents.filter(e => e.kind === "fn");
|
|
92
|
-
const fnFound = fnReferencesBindingStruct(fns, bindingStructs);
|
|
93
|
-
if (fnFound) {
|
|
94
|
-
const { fn, struct } = fnFound;
|
|
95
|
-
struct.entryFn = fn;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function fnReferencesBindingStruct(
|
|
100
|
-
fns: FnElem[],
|
|
101
|
-
bindingStructs: BindingStructElem[],
|
|
102
|
-
): { fn: FnElem; struct: BindingStructElem } | undefined {
|
|
103
|
-
for (const fn of fns) {
|
|
104
|
-
const { params } = fn;
|
|
105
|
-
for (const p of params) {
|
|
106
|
-
const ref = p.name?.typeRef?.name as RefIdent | undefined;
|
|
107
|
-
const referencedElem = (ref?.refersTo as DeclIdent)
|
|
108
|
-
?.declElem as StructElem;
|
|
109
|
-
const struct = bindingStructs.find(s => s === referencedElem);
|
|
110
|
-
if (struct) {
|
|
111
|
-
return { fn, struct };
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function removeBindingStructs(moduleElem: ModuleElem): AbstractElem[] {
|
|
118
|
-
return moduleElem.contents.filter(
|
|
119
|
-
elem => elem.kind !== "struct" || !elem.bindingStruct,
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** mutate the AST, marking StructElems as bindingStructs
|
|
124
|
-
* (if they contain ptrs with @group @binding annotations)
|
|
125
|
-
* @return the binding structs
|
|
126
|
-
*/
|
|
127
|
-
export function markBindingStructs(
|
|
128
|
-
moduleElem: ModuleElem,
|
|
129
|
-
): BindingStructElem[] {
|
|
130
|
-
const structs = moduleElem.contents.filter(elem => elem.kind === "struct");
|
|
131
|
-
const bindingStructs = structs.filter(containsBinding);
|
|
132
|
-
bindingStructs.forEach(struct => {
|
|
133
|
-
struct.bindingStruct = true;
|
|
134
|
-
});
|
|
135
|
-
// LATER also mark structs that reference a binding struct..
|
|
136
|
-
return bindingStructs as BindingStructElem[];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** @return true if this struct contains a member with marked with @binding or @group */
|
|
140
|
-
function containsBinding(struct: StructElem): boolean {
|
|
141
|
-
return struct.members.some(({ attributes }) => bindingAttribute(attributes));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function bindingAttribute(attributes?: AttributeElem[]): boolean {
|
|
145
|
-
if (!attributes) return false;
|
|
146
|
-
return attributes.some(
|
|
147
|
-
({ attribute }) =>
|
|
148
|
-
attribute.kind === "@attribute" &&
|
|
149
|
-
(attribute.name === "binding" || attribute.name === "group"),
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** convert each member of the binding struct into a synthetic global variable */
|
|
154
|
-
export function transformBindingStruct(
|
|
155
|
-
s: StructElem,
|
|
156
|
-
globalNames: Set<string>,
|
|
157
|
-
): SyntheticElem[] {
|
|
158
|
-
return s.members.map(member => {
|
|
159
|
-
const { typeRef, name: memberName } = member;
|
|
160
|
-
const { name: typeName } = typeRef!; // members should always have a typeRef.. LATER fix typing to show this
|
|
161
|
-
const typeParameters = typeRef?.templateParams;
|
|
162
|
-
|
|
163
|
-
const varName = minimallyMangledName(memberName.name, globalNames);
|
|
164
|
-
member.mangledVarName = varName; // save new name so we can rewrite references to this member later
|
|
165
|
-
globalNames.add(varName);
|
|
166
|
-
|
|
167
|
-
const attributes =
|
|
168
|
-
member.attributes?.map(attributeToString).join(" ") ?? "";
|
|
169
|
-
const varTypes =
|
|
170
|
-
lowerPtrMember(typeName, typeParameters) ??
|
|
171
|
-
lowerStdTypeMember(typeName, typeParameters) ??
|
|
172
|
-
lowerStorageTextureMember(typeName, typeParameters);
|
|
173
|
-
if (!varTypes) {
|
|
174
|
-
console.log("unhandled case transforming member", typeName);
|
|
175
|
-
return syntheticVar(attributes, varName, "", "??");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const { storage: storageType, varType } = varTypes;
|
|
179
|
-
return syntheticVar(attributes, varName, storageType, varType);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
interface LoweredVarTypes {
|
|
184
|
-
storage: string;
|
|
185
|
-
varType: string;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function lowerPtrMember(
|
|
189
|
-
typeName: RefIdent,
|
|
190
|
-
typeParameters: TypeTemplateParameter[] | undefined,
|
|
191
|
-
): LoweredVarTypes | undefined {
|
|
192
|
-
if (typeName.originalName === "ptr") {
|
|
193
|
-
const origParams = typeParameters ?? [];
|
|
194
|
-
const newParams = [origParams[0]];
|
|
195
|
-
if (origParams[2]) newParams.push(origParams[2]);
|
|
196
|
-
const storage = typeListToString(newParams);
|
|
197
|
-
|
|
198
|
-
const varType = typeParamToString(origParams?.[1]);
|
|
199
|
-
return { storage, varType };
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function lowerStdTypeMember(
|
|
204
|
-
typeName: RefIdent,
|
|
205
|
-
typeParameters: TypeTemplateParameter[] | undefined,
|
|
206
|
-
): LoweredVarTypes | undefined {
|
|
207
|
-
if (typeof typeName !== "string") {
|
|
208
|
-
const varBaseType = typeName.std ? typeName.originalName : "??";
|
|
209
|
-
const params = typeParameters ? typeListToString(typeParameters) : "";
|
|
210
|
-
const varType = varBaseType + params;
|
|
211
|
-
|
|
212
|
-
return { varType, storage: "" };
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function lowerStorageTextureMember(
|
|
217
|
-
typeName: RefIdent,
|
|
218
|
-
typeParameters: TypeTemplateParameter[] | undefined,
|
|
219
|
-
): LoweredVarTypes | undefined {
|
|
220
|
-
if (textureStorage.test(typeName.originalName)) {
|
|
221
|
-
const params = typeParameters ? typeListToString(typeParameters) : "";
|
|
222
|
-
const varType = typeName + params;
|
|
223
|
-
return { varType, storage: "" };
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function syntheticVar(
|
|
228
|
-
attributes: string,
|
|
229
|
-
varName: string,
|
|
230
|
-
storageTemplate: string,
|
|
231
|
-
varType: string,
|
|
232
|
-
): SyntheticElem {
|
|
233
|
-
const varText = `${attributes} var${storageTemplate} ${varName} : ${varType};\n`;
|
|
234
|
-
|
|
235
|
-
const elem: SyntheticElem = {
|
|
236
|
-
kind: "synthetic",
|
|
237
|
-
text: varText,
|
|
238
|
-
};
|
|
239
|
-
return elem;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
interface MemberRefToStruct extends StructTrace {
|
|
243
|
-
memberRef: SimpleMemberRef; // e.g. the memberRef 'b.particles'
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
interface StructTrace {
|
|
247
|
-
struct: StructElem; // e.g. the struct Bindings
|
|
248
|
-
intermediates: DeclarationElem[]; // e.g. the fn param b:Bindings from 'fn(b:Bindings)'
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** find all simple member references in the module that refer to binding structs */
|
|
252
|
-
export function findRefsToBindingStructs(
|
|
253
|
-
moduleElem: ModuleElem,
|
|
254
|
-
): MemberRefToStruct[] {
|
|
255
|
-
const members: SimpleMemberRef[] = [];
|
|
256
|
-
visitAst(moduleElem, elem => {
|
|
257
|
-
if (elem.kind === "memberRef") members.push(elem);
|
|
258
|
-
});
|
|
259
|
-
return filterMap(members, refersToBindingStruct);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** @return true if this memberRef refers to a binding struct */
|
|
263
|
-
function refersToBindingStruct(
|
|
264
|
-
memberRef: SimpleMemberRef,
|
|
265
|
-
): MemberRefToStruct | undefined {
|
|
266
|
-
const found = traceToStruct(memberRef.name.ident);
|
|
267
|
-
|
|
268
|
-
if (found?.struct.bindingStruct) {
|
|
269
|
-
return { memberRef, ...found };
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** If this identifier ultimately refers to a struct type, return the struct declaration */
|
|
274
|
-
function traceToStruct(ident: RefIdent): StructTrace | undefined {
|
|
275
|
-
const decl = findDecl(ident);
|
|
276
|
-
const declElem = decl.declElem;
|
|
277
|
-
// for now only handle the case where the reference points at a fn parameter
|
|
278
|
-
if (declElem && declElem.kind === "param") {
|
|
279
|
-
const name = declElem.name.typeRef?.name;
|
|
280
|
-
if (typeof name !== "string") {
|
|
281
|
-
if (name?.std) {
|
|
282
|
-
return undefined;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const paramDecl = findDecl(name as Ident);
|
|
286
|
-
const structElem = paramDecl.declElem;
|
|
287
|
-
if (structElem?.kind === "struct") {
|
|
288
|
-
return { struct: structElem, intermediates: [declElem] };
|
|
289
|
-
}
|
|
290
|
-
return undefined;
|
|
291
|
-
}
|
|
292
|
-
} else {
|
|
293
|
-
// LATER presumably handle other cases? Should this be more general, e.g. traceToType()?
|
|
294
|
-
// elemLog(
|
|
295
|
-
// ident.refIdentElem!,
|
|
296
|
-
// `unhandled case in traceToStruct: decl ${declElem.kind} not yet implemented`,
|
|
297
|
-
// );
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/** Mutate the member reference elem to instead contain synthetic elem text.
|
|
302
|
-
* The new text is the mangled var name of the struct member that the memberRef refers to. */
|
|
303
|
-
export function transformBindingReference(
|
|
304
|
-
memberRef: SimpleMemberRef,
|
|
305
|
-
struct: StructElem,
|
|
306
|
-
): SyntheticElem {
|
|
307
|
-
const refName = memberRef.member.name;
|
|
308
|
-
const structMember = struct.members.find(m => m.name.name === refName)!;
|
|
309
|
-
if (!structMember || !structMember.mangledVarName) {
|
|
310
|
-
if (debug) console.log(`missing mangledVarName for ${refName}`);
|
|
311
|
-
return { kind: "synthetic", text: refName };
|
|
312
|
-
}
|
|
313
|
-
const { extraComponents } = memberRef;
|
|
314
|
-
const extraText = extraComponents ? contentsToString(extraComponents) : "";
|
|
315
|
-
|
|
316
|
-
const text = structMember.mangledVarName + extraText;
|
|
317
|
-
const synthElem: SyntheticElem = { kind: "synthetic", text };
|
|
318
|
-
memberRef.contents = [synthElem];
|
|
319
|
-
return synthElem;
|
|
320
|
-
}
|