typegpu 0.11.4 → 0.11.5

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/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.11.4";
2
+ var version = "0.11.5";
3
3
 
4
4
  //#endregion
5
5
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegpu",
3
- "version": "0.11.4",
3
+ "version": "0.11.5",
4
4
  "description": "A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.",
5
5
  "keywords": [
6
6
  "compute",
package/resolutionCtx.js CHANGED
@@ -12,12 +12,12 @@ import { safeStringify } from "./shared/stringify.js";
12
12
  import { getBestConversion } from "./tgsl/conversion.js";
13
13
  import { bool } from "./data/numeric.js";
14
14
  import { coerceToSnippet, concretize, numericLiteralToSnippet } from "./tgsl/generationHelpers.js";
15
+ import { sanitizePrimer, validateIdentifier } from "./nameUtils.js";
15
16
  import { createIoSchema } from "./core/function/ioSchema.js";
16
17
  import { AutoStruct } from "./data/autoStruct.js";
17
18
  import { EntryInputRouter } from "./core/function/entryInputRouter.js";
18
19
  import { accessProp } from "./tgsl/accessProp.js";
19
20
  import { isTgpuFn } from "./core/function/tgpuFn.js";
20
- import { getUniqueName } from "./core/resolve/namespace.js";
21
21
  import { ConfigurableImpl } from "./core/root/configurableImpl.js";
22
22
  import { naturalsExcept } from "./shared/generators.js";
23
23
  import { TgpuBindGroupImpl, bindGroupLayout } from "./tgpuBindGroupLayout.js";
@@ -50,6 +50,9 @@ var ItemStateStackImpl = class {
50
50
  get topFunctionScope() {
51
51
  return this._stack.findLast((e) => e.type === "functionScope");
52
52
  }
53
+ get topBlockScope() {
54
+ return this._stack.findLast((e) => e.type === "blockScope");
55
+ }
53
56
  pushItem() {
54
57
  this._itemDepth++;
55
58
  this._stack.push({
@@ -70,7 +73,9 @@ var ItemStateStackImpl = class {
70
73
  argAccess,
71
74
  returnType,
72
75
  externalMap,
73
- reportedReturnTypes: /* @__PURE__ */ new Set()
76
+ reportedReturnTypes: /* @__PURE__ */ new Set(),
77
+ placeholderForVariable: /* @__PURE__ */ new Map(),
78
+ modifiedVariables: /* @__PURE__ */ new Set()
74
79
  };
75
80
  this._stack.push(scope);
76
81
  return scope;
@@ -78,6 +83,7 @@ var ItemStateStackImpl = class {
78
83
  pushBlockScope() {
79
84
  this._stack.push({
80
85
  type: "blockScope",
86
+ takenLocalIdentifiers: /* @__PURE__ */ new Set(),
81
87
  declarations: /* @__PURE__ */ new Map(),
82
88
  externals: /* @__PURE__ */ new Map()
83
89
  });
@@ -116,6 +122,16 @@ var ItemStateStackImpl = class {
116
122
  }
117
123
  }
118
124
  }
125
+ isIdentifierTakenLocally(id) {
126
+ for (let i = this._stack.length - 1; i >= 0; --i) {
127
+ const layer = this._stack[i];
128
+ if (layer?.type === "functionScope") return false;
129
+ if (layer?.type === "blockScope") {
130
+ if (layer.takenLocalIdentifiers.has(id)) return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
119
135
  defineBlockVariable(id, snippet) {
120
136
  if (snippet.dataType === UnknownData) throw Error(`Tried to define variable '${id}' of unknown type`);
121
137
  for (let i = this._stack.length - 1; i >= 0; --i) {
@@ -232,17 +248,41 @@ var ResolutionCtxImpl = class {
232
248
  fixedBindings = [];
233
249
  enableExtensions;
234
250
  expectedType;
251
+ /**
252
+ * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy.
253
+ */
254
+ #lastUniqueId = 0;
235
255
  constructor(opts) {
236
256
  this.enableExtensions = opts.enableExtensions;
237
257
  this.gen = opts.shaderGenerator ?? wgslGenerator_default;
238
258
  this.#logGenerator = opts.root ? new LogGeneratorImpl(opts.root) : new LogGeneratorNullImpl();
239
259
  this.#namespaceInternal = opts.namespace[$internal];
240
260
  }
241
- getUniqueName(resource) {
242
- return getUniqueName(this.#namespaceInternal, resource);
261
+ isIdentifierTaken(name) {
262
+ return this.#namespaceInternal.takenGlobalIdentifiers.has(name) || this._itemStateStack.isIdentifierTakenLocally(name);
243
263
  }
244
- makeNameValid(name) {
245
- return this.#namespaceInternal.nameRegistry.makeValid(name);
264
+ makeUniqueIdentifier(primer = "item", scope) {
265
+ if (scope === "block" && (/* @__PURE__ */ validateIdentifier(primer)).success && !this.isIdentifierTaken(primer)) {
266
+ this.reserveIdentifier(primer, "block");
267
+ return primer;
268
+ }
269
+ const base = /* @__PURE__ */ sanitizePrimer(primer);
270
+ let index = 0;
271
+ const random = this.#namespaceInternal.strategy === "random";
272
+ let name = random ? `${base}_${this.#lastUniqueId++}` : base;
273
+ while (this.isIdentifierTaken(name)) name = random ? `${base}_${this.#lastUniqueId++}` : `${base}_${++index}`;
274
+ this.reserveIdentifier(name, scope);
275
+ return name;
276
+ }
277
+ reserveIdentifier(name, scope) {
278
+ if (scope === "block") {
279
+ const blockScope = this._itemStateStack.topBlockScope;
280
+ if (blockScope) {
281
+ blockScope.takenLocalIdentifiers.add(name);
282
+ return;
283
+ }
284
+ }
285
+ this.#namespaceInternal.takenGlobalIdentifiers.add(name);
246
286
  }
247
287
  get pre() {
248
288
  return this._indentController.pre;
@@ -281,11 +321,9 @@ var ResolutionCtxImpl = class {
281
321
  scope.reportedReturnTypes.add(dataType);
282
322
  }
283
323
  pushBlockScope() {
284
- this.#namespaceInternal.nameRegistry.pushBlockScope();
285
324
  this._itemStateStack.pushBlockScope();
286
325
  }
287
326
  popBlockScope() {
288
- this.#namespaceInternal.nameRegistry.popBlockScope();
289
327
  this._itemStateStack.pop("blockScope");
290
328
  }
291
329
  setBlockExternals(externals) {
@@ -301,28 +339,27 @@ var ResolutionCtxImpl = class {
301
339
  return this.#logGenerator.logResources;
302
340
  }
303
341
  fnToWgsl(options) {
304
- let fnScopePushed = false;
305
342
  try {
306
- this.#namespaceInternal.nameRegistry.pushFunctionScope();
343
+ const scope = this._itemStateStack.pushFunctionScope(options.functionType, {}, options.returnType, options.externalMap);
344
+ this._itemStateStack.pushBlockScope();
307
345
  const args = [];
308
- const argAccess = {};
309
346
  if (options.entryInput) {
310
347
  const { dataSchema, positionalArgs } = options.entryInput;
311
348
  const firstParam = options.params[0];
312
- const structArg = dataSchema ? createArgument(this.makeNameValid("_arg_0"), dataSchema) : void 0;
349
+ const structArg = dataSchema ? createArgument(this.makeUniqueIdentifier("_arg_0", "block"), dataSchema) : void 0;
313
350
  if (structArg) args.push(structArg);
314
351
  if (firstParam?.type === FuncParameterType.destructuredObject) for (const { name, alias } of firstParam.props) {
315
352
  const argInfo = positionalArgs.find((a) => a.schemaKey === name);
316
353
  if (argInfo) {
317
- const arg = createArgument(this.makeNameValid(alias), argInfo.type);
354
+ const arg = createArgument(this.makeUniqueIdentifier(alias, "block"), argInfo.type);
318
355
  args.push(arg);
319
- argAccess[alias] = arg.access;
320
- } else if (structArg) argAccess[alias] = createArgumentPropAccess(structArg.access, name);
356
+ scope.argAccess[alias] = arg.access;
357
+ } else if (structArg) scope.argAccess[alias] = createArgumentPropAccess(structArg.access, name);
321
358
  }
322
359
  else if (firstParam?.type === FuncParameterType.identifier) {
323
360
  const proxyEntries = [];
324
361
  for (const a of positionalArgs) {
325
- const arg = createArgument(this.makeNameValid(`_arg_${a.schemaKey}`), a.type);
362
+ const arg = createArgument(this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, "block"), a.type);
326
363
  args.push(arg);
327
364
  proxyEntries.push({
328
365
  schemaKey: a.schemaKey,
@@ -330,31 +367,31 @@ var ResolutionCtxImpl = class {
330
367
  });
331
368
  }
332
369
  const router = new EntryInputRouter(structArg?.access, proxyEntries);
333
- argAccess[firstParam.name] = () => snip("N/A", router, "argument");
370
+ scope.argAccess[firstParam.name] = () => snip("N/A", router, "argument");
334
371
  } else for (const a of positionalArgs) {
335
- const argName = this.makeNameValid(`_arg_${a.schemaKey}`);
372
+ const argName = this.makeUniqueIdentifier(`_arg_${a.schemaKey}`, "block");
336
373
  const arg = createArgument(argName, a.type);
337
374
  args.push(arg);
338
- argAccess[argName] = arg.access;
375
+ scope.argAccess[argName] = arg.access;
339
376
  }
340
377
  } else for (const [i, argType] of options.argTypes.entries()) {
341
378
  const astParam = options.params[i];
342
379
  const origin = isPtr(argType) ? argType.addressSpace === "storage" ? argType.access === "read" ? "readonly" : "mutable" : argType.addressSpace : "argument";
343
380
  switch (astParam?.type) {
344
381
  case FuncParameterType.identifier: {
345
- const arg = createArgument(this.makeNameValid(astParam.name), argType, origin);
382
+ const arg = createArgument(this.makeUniqueIdentifier(astParam.name, "block"), argType, origin);
346
383
  args.push(arg);
347
- argAccess[astParam.name] = arg.access;
384
+ scope.argAccess[astParam.name] = arg.access;
348
385
  break;
349
386
  }
350
387
  case FuncParameterType.destructuredObject: {
351
- const objArg = createArgument(this.makeNameValid(`_arg_${i}`), argType, origin);
388
+ const objArg = createArgument(this.makeUniqueIdentifier(`_arg_${i}`, "block"), argType, origin);
352
389
  args.push(objArg);
353
- for (const { name, alias } of astParam.props) argAccess[alias] = createArgumentPropAccess(objArg.access, name);
390
+ for (const { name, alias } of astParam.props) scope.argAccess[alias] = createArgumentPropAccess(objArg.access, name);
354
391
  break;
355
392
  }
356
393
  case void 0: if (!(argType instanceof AutoStruct)) args.push({
357
- name: this.makeNameValid(`_arg_${i}`),
394
+ name: this.makeUniqueIdentifier(`_arg_${i}`, "block"),
358
395
  access: () => {
359
396
  throw new Error(`Unreachable: Accessing an argument that wasn't named in the function signature`);
360
397
  },
@@ -363,8 +400,6 @@ var ResolutionCtxImpl = class {
363
400
  });
364
401
  }
365
402
  }
366
- const scope = this._itemStateStack.pushFunctionScope(options.functionType, argAccess, options.returnType, options.externalMap);
367
- fnScopePushed = true;
368
403
  let returnType;
369
404
  const code = this.gen.functionDefinition({
370
405
  functionType: options.functionType,
@@ -395,8 +430,8 @@ var ResolutionCtxImpl = class {
395
430
  returnType
396
431
  };
397
432
  } finally {
398
- if (fnScopePushed) this._itemStateStack.pop("functionScope");
399
- this.#namespaceInternal.nameRegistry.popFunctionScope();
433
+ this._itemStateStack.pop("blockScope");
434
+ this._itemStateStack.pop("functionScope");
400
435
  }
401
436
  }
402
437
  addDeclaration(declaration) {
package/std/copy.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { DualFn } from "../types.js";
2
+
3
+ //#region src/std/copy.d.ts
4
+ declare function cpuCopy<T>(e: T): T;
5
+ declare const copy: DualFn<typeof cpuCopy>;
6
+ //#endregion
7
+ export { copy };
package/std/copy.js ADDED
@@ -0,0 +1,27 @@
1
+ import { WORKAROUND_getSchema, isMatInstance, isVecInstance } from "../data/wgslTypes.js";
2
+ import { stitch } from "../core/resolve/stitch.js";
3
+ import { dualImpl } from "../core/function/dualImpl.js";
4
+
5
+ //#region src/std/copy.ts
6
+ function cpuCopy(e) {
7
+ if (isVecInstance(e) || isMatInstance(e)) return WORKAROUND_getSchema(e)(e);
8
+ if (Array.isArray(e)) return e.map(cpuCopy);
9
+ if (typeof e === "object" && e !== null) return Object.fromEntries(Object.entries(e).map(([key, value]) => [key, cpuCopy(value)]));
10
+ return e;
11
+ }
12
+ const copy = dualImpl({
13
+ name: "copy",
14
+ signature: (arg) => {
15
+ return {
16
+ argTypes: [arg],
17
+ returnType: arg
18
+ };
19
+ },
20
+ normalImpl: cpuCopy,
21
+ codegenImpl(_ctx, [a]) {
22
+ return stitch`${a}`;
23
+ }
24
+ });
25
+
26
+ //#endregion
27
+ export { copy };
package/std/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { identity2, identity3, identity4, rotationX4, rotationY4, rotationZ4, scaling4, translation4 } from "../data/matrix.js";
2
+ import { copy } from "./copy.js";
2
3
  import { discard } from "./discard.js";
3
4
  import { abs, acos, acosh, asin, asinh, atan, atan2, atanh, ceil, clamp, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, degrees, determinant, distance, dot, dot4I8Packed, dot4U8Packed, exp, exp2, extractBits, faceForward, firstLeadingBit, firstTrailingBit, floor, fma, fract, frexp, insertBits, inverseSqrt, ldexp, length, log, log2, max, min, mix, modf, normalize, pow, quantizeToF16, radians, reflect, refract, reverseBits, round, saturate, sign, sin, sinh, smoothstep, sqrt, step, tan, tanh, transpose, trunc } from "./numeric.js";
4
5
  import { add, bitShiftLeft, bitShiftRight, div, mod, mul, neg, sub } from "./operators.js";
@@ -16,7 +17,7 @@ import { range } from "./range.js";
16
17
 
17
18
  //#region src/std/index.d.ts
18
19
  declare namespace index_d_exports {
19
- export { abs, acos, acosh, add, all, allEq, and, any, arrayLength, asin, asinh, atan, atan2, atanh, atomicAdd, atomicAnd, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, bitShiftLeft, bitShiftRight, bitcastU32toF32, bitcastU32toI32, ceil, clamp, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, degrees, determinant, discard, distance, div, dot, dot4I8Packed, dot4U8Packed, dpdx, dpdxCoarse, dpdxFine, dpdy, dpdyCoarse, dpdyFine, eq, exp, exp2, extensionEnabled, extractBits, faceForward, firstLeadingBit, firstTrailingBit, floor, fma, fract, frexp, fwidth, fwidthCoarse, fwidthFine, ge, gt, identity2, identity3, identity4, insertBits, inverseSqrt, isCloseTo, ldexp, le, length, log, log2, lt, max, min, mix, mod, modf, mul, ne, neg, normalize, not, or, pack2x16float, pack4x8unorm, pow, quantizeToF16, radians, range, reflect, refract, reverseBits, rotateX4, rotateY4, rotateZ4, rotationX4, rotationY4, rotationZ4, round, saturate, scale4, scaling4, select, sign, sin, sinh, smoothstep, sqrt, step, storageBarrier, sub, subgroupAdd, subgroupAll, subgroupAnd, subgroupAny, subgroupBallot, subgroupBroadcast, subgroupBroadcastFirst, subgroupElect, subgroupExclusiveAdd, subgroupExclusiveMul, subgroupInclusiveAdd, subgroupInclusiveMul, subgroupMax, subgroupMin, subgroupMul, subgroupOr, subgroupShuffle, subgroupShuffleDown, subgroupShuffleUp, subgroupShuffleXor, subgroupXor, tan, tanh, textureBarrier, textureDimensions, textureGather, textureLoad, textureSample, textureSampleBaseClampToEdge, textureSampleBias, textureSampleCompare, textureSampleCompareLevel, textureSampleGrad, textureSampleLevel, textureStore, translate4, translation4, transpose, trunc, unpack2x16float, unpack4x8unorm, workgroupBarrier };
20
+ export { abs, acos, acosh, add, all, allEq, and, any, arrayLength, asin, asinh, atan, atan2, atanh, atomicAdd, atomicAnd, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, bitShiftLeft, bitShiftRight, bitcastU32toF32, bitcastU32toI32, ceil, clamp, copy, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, degrees, determinant, discard, distance, div, dot, dot4I8Packed, dot4U8Packed, dpdx, dpdxCoarse, dpdxFine, dpdy, dpdyCoarse, dpdyFine, eq, exp, exp2, extensionEnabled, extractBits, faceForward, firstLeadingBit, firstTrailingBit, floor, fma, fract, frexp, fwidth, fwidthCoarse, fwidthFine, ge, gt, identity2, identity3, identity4, insertBits, inverseSqrt, isCloseTo, ldexp, le, length, log, log2, lt, max, min, mix, mod, modf, mul, ne, neg, normalize, not, or, pack2x16float, pack4x8unorm, pow, quantizeToF16, radians, range, reflect, refract, reverseBits, rotateX4, rotateY4, rotateZ4, rotationX4, rotationY4, rotationZ4, round, saturate, scale4, scaling4, select, sign, sin, sinh, smoothstep, sqrt, step, storageBarrier, sub, subgroupAdd, subgroupAll, subgroupAnd, subgroupAny, subgroupBallot, subgroupBroadcast, subgroupBroadcastFirst, subgroupElect, subgroupExclusiveAdd, subgroupExclusiveMul, subgroupInclusiveAdd, subgroupInclusiveMul, subgroupMax, subgroupMin, subgroupMul, subgroupOr, subgroupShuffle, subgroupShuffleDown, subgroupShuffleUp, subgroupShuffleXor, subgroupXor, tan, tanh, textureBarrier, textureDimensions, textureGather, textureLoad, textureSample, textureSampleBaseClampToEdge, textureSampleBias, textureSampleCompare, textureSampleCompareLevel, textureSampleGrad, textureSampleLevel, textureStore, translate4, translation4, transpose, trunc, unpack2x16float, unpack4x8unorm, workgroupBarrier };
20
21
  }
21
22
  //#endregion
22
- export { abs, acos, acosh, add, all, allEq, and, any, arrayLength, asin, asinh, atan, atan2, atanh, atomicAdd, atomicAnd, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, bitShiftLeft, bitShiftRight, bitcastU32toF32, bitcastU32toI32, ceil, clamp, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, degrees, determinant, discard, distance, div, dot, dot4I8Packed, dot4U8Packed, dpdx, dpdxCoarse, dpdxFine, dpdy, dpdyCoarse, dpdyFine, eq, exp, exp2, extensionEnabled, extractBits, faceForward, firstLeadingBit, firstTrailingBit, floor, fma, fract, frexp, fwidth, fwidthCoarse, fwidthFine, ge, gt, identity2, identity3, identity4, index_d_exports, insertBits, inverseSqrt, isCloseTo, ldexp, le, length, log, log2, lt, max, min, mix, mod, modf, mul, ne, neg, normalize, not, or, pack2x16float, pack4x8unorm, pow, quantizeToF16, radians, range, reflect, refract, reverseBits, rotateX4, rotateY4, rotateZ4, rotationX4, rotationY4, rotationZ4, round, saturate, scale4, scaling4, select, sign, sin, sinh, smoothstep, sqrt, step, storageBarrier, sub, subgroupAdd, subgroupAll, subgroupAnd, subgroupAny, subgroupBallot, subgroupBroadcast, subgroupBroadcastFirst, subgroupElect, subgroupExclusiveAdd, subgroupExclusiveMul, subgroupInclusiveAdd, subgroupInclusiveMul, subgroupMax, subgroupMin, subgroupMul, subgroupOr, subgroupShuffle, subgroupShuffleDown, subgroupShuffleUp, subgroupShuffleXor, subgroupXor, tan, tanh, textureBarrier, textureDimensions, textureGather, textureLoad, textureSample, textureSampleBaseClampToEdge, textureSampleBias, textureSampleCompare, textureSampleCompareLevel, textureSampleGrad, textureSampleLevel, textureStore, translate4, translation4, transpose, trunc, unpack2x16float, unpack4x8unorm, workgroupBarrier };
23
+ export { abs, acos, acosh, add, all, allEq, and, any, arrayLength, asin, asinh, atan, atan2, atanh, atomicAdd, atomicAnd, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, bitShiftLeft, bitShiftRight, bitcastU32toF32, bitcastU32toI32, ceil, clamp, copy, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, degrees, determinant, discard, distance, div, dot, dot4I8Packed, dot4U8Packed, dpdx, dpdxCoarse, dpdxFine, dpdy, dpdyCoarse, dpdyFine, eq, exp, exp2, extensionEnabled, extractBits, faceForward, firstLeadingBit, firstTrailingBit, floor, fma, fract, frexp, fwidth, fwidthCoarse, fwidthFine, ge, gt, identity2, identity3, identity4, index_d_exports, insertBits, inverseSqrt, isCloseTo, ldexp, le, length, log, log2, lt, max, min, mix, mod, modf, mul, ne, neg, normalize, not, or, pack2x16float, pack4x8unorm, pow, quantizeToF16, radians, range, reflect, refract, reverseBits, rotateX4, rotateY4, rotateZ4, rotationX4, rotationY4, rotationZ4, round, saturate, scale4, scaling4, select, sign, sin, sinh, smoothstep, sqrt, step, storageBarrier, sub, subgroupAdd, subgroupAll, subgroupAnd, subgroupAny, subgroupBallot, subgroupBroadcast, subgroupBroadcastFirst, subgroupElect, subgroupExclusiveAdd, subgroupExclusiveMul, subgroupInclusiveAdd, subgroupInclusiveMul, subgroupMax, subgroupMin, subgroupMul, subgroupOr, subgroupShuffle, subgroupShuffleDown, subgroupShuffleUp, subgroupShuffleXor, subgroupXor, tan, tanh, textureBarrier, textureDimensions, textureGather, textureLoad, textureSample, textureSampleBaseClampToEdge, textureSampleBias, textureSampleCompare, textureSampleCompareLevel, textureSampleGrad, textureSampleLevel, textureStore, translate4, translation4, transpose, trunc, unpack2x16float, unpack4x8unorm, workgroupBarrier };
package/std/index.js CHANGED
@@ -7,6 +7,7 @@ import { range } from "./range.js";
7
7
  import { bitcastU32toF32, bitcastU32toI32 } from "./bitcast.js";
8
8
  import { pack2x16float, pack4x8unorm, unpack2x16float, unpack4x8unorm } from "./packing.js";
9
9
  import { all, allEq, and, any, eq, ge, gt, isCloseTo, le, lt, ne, not, or, select } from "./boolean.js";
10
+ import { copy } from "./copy.js";
10
11
  import { discard } from "./discard.js";
11
12
  import { rotateX4, rotateY4, rotateZ4, scale4, translate4 } from "./matrix.js";
12
13
  import { atomicAdd, atomicAnd, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, storageBarrier, textureBarrier, workgroupBarrier } from "./atomic.js";
@@ -46,6 +47,7 @@ var std_exports = /* @__PURE__ */ __export({
46
47
  bitcastU32toI32: () => bitcastU32toI32,
47
48
  ceil: () => ceil,
48
49
  clamp: () => clamp,
50
+ copy: () => copy,
49
51
  cos: () => cos,
50
52
  cosh: () => cosh,
51
53
  countLeadingZeros: () => countLeadingZeros,
@@ -179,4 +181,4 @@ var std_exports = /* @__PURE__ */ __export({
179
181
  });
180
182
 
181
183
  //#endregion
182
- export { abs, acos, acosh, add, all, allEq, and, any, arrayLength, asin, asinh, atan, atan2, atanh, atomicAdd, atomicAnd, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, bitShiftLeft, bitShiftRight, bitcastU32toF32, bitcastU32toI32, ceil, clamp, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, degrees, determinant, discard, distance, div, dot, dot4I8Packed, dot4U8Packed, dpdx, dpdxCoarse, dpdxFine, dpdy, dpdyCoarse, dpdyFine, eq, exp, exp2, extensionEnabled, extractBits, faceForward, firstLeadingBit, firstTrailingBit, floor, fma, fract, frexp, fwidth, fwidthCoarse, fwidthFine, ge, gt, identity2, identity3, identity4, insertBits, inverseSqrt, isCloseTo, ldexp, le, length, log, log2, lt, max, min, mix, mod, modf, mul, ne, neg, normalize, not, or, pack2x16float, pack4x8unorm, pow, quantizeToF16, radians, range, reflect, refract, reverseBits, rotateX4, rotateY4, rotateZ4, rotationX4, rotationY4, rotationZ4, round, saturate, scale4, scaling4, select, sign, sin, sinh, smoothstep, sqrt, std_exports, step, storageBarrier, sub, subgroupAdd, subgroupAll, subgroupAnd, subgroupAny, subgroupBallot, subgroupBroadcast, subgroupBroadcastFirst, subgroupElect, subgroupExclusiveAdd, subgroupExclusiveMul, subgroupInclusiveAdd, subgroupInclusiveMul, subgroupMax, subgroupMin, subgroupMul, subgroupOr, subgroupShuffle, subgroupShuffleDown, subgroupShuffleUp, subgroupShuffleXor, subgroupXor, tan, tanh, textureBarrier, textureDimensions, textureGather, textureLoad, textureSample, textureSampleBaseClampToEdge, textureSampleBias, textureSampleCompare, textureSampleCompareLevel, textureSampleGrad, textureSampleLevel, textureStore, translate4, translation4, transpose, trunc, unpack2x16float, unpack4x8unorm, workgroupBarrier };
184
+ export { abs, acos, acosh, add, all, allEq, and, any, arrayLength, asin, asinh, atan, atan2, atanh, atomicAdd, atomicAnd, atomicLoad, atomicMax, atomicMin, atomicOr, atomicStore, atomicSub, atomicXor, bitShiftLeft, bitShiftRight, bitcastU32toF32, bitcastU32toI32, ceil, clamp, copy, cos, cosh, countLeadingZeros, countOneBits, countTrailingZeros, cross, degrees, determinant, discard, distance, div, dot, dot4I8Packed, dot4U8Packed, dpdx, dpdxCoarse, dpdxFine, dpdy, dpdyCoarse, dpdyFine, eq, exp, exp2, extensionEnabled, extractBits, faceForward, firstLeadingBit, firstTrailingBit, floor, fma, fract, frexp, fwidth, fwidthCoarse, fwidthFine, ge, gt, identity2, identity3, identity4, insertBits, inverseSqrt, isCloseTo, ldexp, le, length, log, log2, lt, max, min, mix, mod, modf, mul, ne, neg, normalize, not, or, pack2x16float, pack4x8unorm, pow, quantizeToF16, radians, range, reflect, refract, reverseBits, rotateX4, rotateY4, rotateZ4, rotationX4, rotationY4, rotationZ4, round, saturate, scale4, scaling4, select, sign, sin, sinh, smoothstep, sqrt, std_exports, step, storageBarrier, sub, subgroupAdd, subgroupAll, subgroupAnd, subgroupAny, subgroupBallot, subgroupBroadcast, subgroupBroadcastFirst, subgroupElect, subgroupExclusiveAdd, subgroupExclusiveMul, subgroupInclusiveAdd, subgroupInclusiveMul, subgroupMax, subgroupMin, subgroupMul, subgroupOr, subgroupShuffle, subgroupShuffleDown, subgroupShuffleUp, subgroupShuffleXor, subgroupXor, tan, tanh, textureBarrier, textureDimensions, textureGather, textureLoad, textureSample, textureSampleBaseClampToEdge, textureSampleBias, textureSampleCompare, textureSampleCompareLevel, textureSampleGrad, textureSampleLevel, textureStore, translate4, translation4, transpose, trunc, unpack2x16float, unpack4x8unorm, workgroupBarrier };
package/tgpuUnstable.js CHANGED
@@ -2,9 +2,9 @@ import { __export } from "./_virtual/rolldown_runtime.js";
2
2
  import { comptime } from "./core/function/comptime.js";
3
3
  import { constant } from "./core/constant/tgpuConstant.js";
4
4
  import { fn } from "./core/function/tgpuFn.js";
5
- import { namespace } from "./core/resolve/namespace.js";
6
5
  import { slot } from "./core/slot/slot.js";
7
6
  import { privateVar, workgroupVar } from "./core/variable/tgpuVariable.js";
7
+ import { namespace } from "./core/resolve/namespace.js";
8
8
  import { computeFn } from "./core/function/tgpuComputeFn.js";
9
9
  import { vertexLayout } from "./core/vertexLayout/vertexLayout.js";
10
10
  import { lazy } from "./core/slot/lazy.js";
@@ -158,7 +158,10 @@ function getBestConversion(types, targetTypes) {
158
158
  if (implicitResult) return implicitResult;
159
159
  }
160
160
  function applyActionToSnippet(ctx, snippet, action, targetType) {
161
- if (action.action === "none") return snip(snippet.value, targetType, snippet.origin);
161
+ if (action.action === "none") {
162
+ if (targetType === snippet.dataType) return snippet;
163
+ return snip(snippet.value, targetType, snippet.origin);
164
+ }
162
165
  switch (action.action) {
163
166
  case "ref": return snip(new RefOperator(snippet, targetType), targetType, snippet.origin);
164
167
  case "deref": return derefSnippet(snippet);
@@ -13,9 +13,14 @@ declare class WgslGenerator implements ShaderGenerator {
13
13
  initGenerator(ctx: GenerationCtx): void;
14
14
  protected get ctx(): GenerationCtx;
15
15
  _block([_, statements]: tinyest.Block, externalMap?: ExternalMap): string;
16
+ _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string;
16
17
  refVariable(id: string, dataType: StorableData): string;
17
- blockVariable(varType: 'var' | 'let' | 'const', id: string, dataType: BaseData | UnknownData, origin: Origin): Snippet;
18
- protected emitVarDecl(pre: string, keyword: 'var' | 'let' | 'const', name: string, _dataType: BaseData | UnknownData, rhsStr: string): string;
18
+ blockVariable(varType: 'var' | 'let' | 'const' | '<deferred>', id: string, dataType: BaseData | UnknownData, origin: Origin): Snippet;
19
+ /**
20
+ * Creates a variable declaration string.
21
+ * `keyword` may be a placeholder filled in later.
22
+ */
23
+ protected emitVarDecl(pre: string, keyword: 'var' | 'let' | 'const' | `#VAR_${number}#`, name: string, _dataType: BaseData | UnknownData, rhsStr: string): string;
19
24
  _identifier(id: string): Snippet;
20
25
  /**
21
26
  * A wrapper for `generateExpression` that updates `ctx.expectedType`
@@ -33,6 +38,19 @@ declare class WgslGenerator implements ShaderGenerator {
33
38
  typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet;
34
39
  _return(statement: tinyest.Return): string;
35
40
  _statement(statement: tinyest.Statement): string;
41
+ /**
42
+ * Attempts a member access lookup to mark a variable as modified.
43
+ * @example
44
+ * // given `let a; a = 1;`
45
+ * tryMarkModified('a') // `a` is marked in the function scope
46
+ *
47
+ * // given `const obj; obj.prop = 1;`
48
+ * tryMarkModified('obj.prop') // `obj` is marked in the function scope
49
+ *
50
+ * // given `this.buffer.$;`
51
+ * tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it
52
+ */
53
+ private tryMarkModified;
36
54
  }
37
55
  //#endregion
38
56
  export { WgslGenerator };
@@ -7,7 +7,7 @@ import { ResolutionError, WgslTypeError, invariant } from "../errors.js";
7
7
  import { isGPUCallable, isKnownAtComptime } from "../types.js";
8
8
  import { stitch } from "../core/resolve/stitch.js";
9
9
  import { createPtrFromOrigin, implicitFrom, ptrFn } from "../data/ptr.js";
10
- import { RefOperator } from "../data/ref.js";
10
+ import { RefOperator, _ref } from "../data/ref.js";
11
11
  import { safeStringify } from "../shared/stringify.js";
12
12
  import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
13
13
  import { bool, i32, u32 } from "../data/numeric.js";
@@ -23,7 +23,7 @@ import { isGenericFn } from "../core/function/tgpuFn.js";
23
23
  import { arrayOf } from "../data/array.js";
24
24
  import { pow } from "../std/numeric.js";
25
25
  import { resolveData } from "../core/resolve/resolveData.js";
26
- import { UnrollableIterable } from "../core/unroll/tgpuUnroll.js";
26
+ import { UnrollableIterable, unroll } from "../core/unroll/tgpuUnroll.js";
27
27
  import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
28
28
  import { isTgpuRange } from "../std/range.js";
29
29
  import { getElementSnippet, getElementType, getLoopVarKind, getRangeSnippets } from "./forOfUtils.js";
@@ -165,8 +165,11 @@ ${this.ctx.pre}}`;
165
165
  this.ctx.popBlockScope();
166
166
  }
167
167
  }
168
+ _blockStatement(block, externalMap) {
169
+ return `${this.ctx.pre}${this._block(block, externalMap)}`;
170
+ }
168
171
  refVariable(id, dataType) {
169
- const varName = this.ctx.makeNameValid(id);
172
+ const varName = this.ctx.makeUniqueIdentifier(id, "block");
170
173
  const ptrType = ptrFn(dataType);
171
174
  const snippet = snip(new RefOperator(snip(varName, dataType, "function"), ptrType), ptrType, "function");
172
175
  this.ctx.defineVariable(id, snippet);
@@ -181,10 +184,14 @@ ${this.ctx.pre}}`;
181
184
  else if (!naturallyEphemeral) varOrigin = isEphemeralOrigin(origin) ? "this-function" : origin;
182
185
  else if (origin === "constant" && varType === "const") varOrigin = "constant";
183
186
  else varOrigin = "runtime";
184
- const snippet = snip(this.ctx.makeNameValid(id), dataType, varOrigin);
187
+ const snippet = snip(this.ctx.makeUniqueIdentifier(id, "block"), dataType, varOrigin);
185
188
  this.ctx.defineVariable(id, snippet);
186
189
  return snippet;
187
190
  }
191
+ /**
192
+ * Creates a variable declaration string.
193
+ * `keyword` may be a placeholder filled in later.
194
+ */
188
195
  emitVarDecl(pre, keyword, name, _dataType, rhsStr) {
189
196
  return `${pre}${keyword} ${name} = ${rhsStr};`;
190
197
  }
@@ -252,22 +259,14 @@ ${this.ctx.pre}}`;
252
259
  const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value;
253
260
  const type = operatorToType(convLhs.dataType, op, convRhs.dataType);
254
261
  if (exprType === NODE.assignmentExpr) {
255
- if (convLhs.origin === "constant" || convLhs.origin === "constant-tgpu-const-ref" || convLhs.origin === "runtime-tgpu-const-ref") {
256
- if (isKnownAtComptime(convLhs)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because the left side is defined outside of the shader, and therefore is immutable during its execution. Try using tgpu.privateVar or buffers.`);
257
- throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because the left side is a constant.`);
258
- }
259
- if (lhsExpr.origin === "argument") throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because non-pointer arguments cannot be mutated.`);
262
+ validateSnippetMutation(convLhs, expression);
263
+ this.tryMarkModified(lhs);
260
264
  if (op === "=" && rhsExpr.origin === "argument" && !isNaturallyEphemeral(rhsExpr.dataType)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because argument references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(rhsExpr.dataType).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
261
265
  if (op === "=" && !isEphemeralSnippet(rhsExpr)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(rhsExpr.dataType).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
262
266
  }
263
267
  return snip(parenthesizedOps.includes(op) ? `(${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr})` : `${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr}`, type, "runtime");
264
268
  }
265
- if (expression[0] === NODE.postUpdate) {
266
- const [_, op, arg] = expression;
267
- const argExpr = this._expression(arg);
268
- const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
269
- return snip(`${argStr}${op}`, argExpr.dataType, "runtime");
270
- }
269
+ if (expression[0] === NODE.postUpdate) throw new Error(`'${stringifyNode(expression)}' is invalid because update is only allowed as a statement.`);
271
270
  if (expression[0] === NODE.unaryExpr) {
272
271
  const [_, op, arg] = expression;
273
272
  const argExpr = this._expression(arg);
@@ -320,6 +319,7 @@ ${this.ctx.pre}}`;
320
319
  const rhs = this._expression(argNodes[0]);
321
320
  return callee.value.operator(this.ctx, [callee.value.lhs, rhs]);
322
321
  }
322
+ if ((callee.value === _ref || callee.value === unroll) && argNodes[0]) this.tryMarkModified(argNodes[0]);
323
323
  if (isGPUCallable(callee.value)) {
324
324
  const callable = callee.value[$gpuCallable];
325
325
  const strictSignature = callable.strictSignature;
@@ -423,7 +423,14 @@ ${this.ctx.pre}}`;
423
423
  assertExhaustive(expression);
424
424
  }
425
425
  functionDefinition(options) {
426
- const body = this._block(options.body);
426
+ let body = this._block(options.body);
427
+ const scope = this.ctx.topFunctionScope;
428
+ invariant(scope, "Expected function scope to be present");
429
+ const replacements = Object.fromEntries(scope.placeholderForVariable.entries().map(([variable, placeholder]) => [placeholder, scope.modifiedVariables.has(variable) ? "var" : "let"]));
430
+ if (Object.keys(replacements).length > 0) {
431
+ const regex = new RegExp(Object.keys(replacements).join("|"), "gi");
432
+ body = body.replace(regex, (match) => replacements[match] ?? "#ERR");
433
+ }
427
434
  const returnType = options.determineReturnType();
428
435
  const argList = options.args.filter((arg) => arg.used || options.functionType === "normal").map((arg) => {
429
436
  return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
@@ -481,7 +488,7 @@ Try 'return ${typeStr}(${str});' instead.
481
488
  if (!Array.isArray(node)) node = blockifySingleStatement(node);
482
489
  if (node[0] === NODE.block && node[1].length === 1 && node[1][0][0] === NODE.if) return this._statement(node[1][0]);
483
490
  if (node[0] === NODE.if) return this._statement(node);
484
- return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`;
491
+ return this._blockStatement(blockifySingleStatement(node));
485
492
  }
486
493
  const consequent = this._block(blockifySingleStatement(consNode));
487
494
  const alternate = !altNode ? void 0 : this._block(blockifySingleStatement(altNode));
@@ -491,7 +498,7 @@ ${this.ctx.pre}if (${condition}) ${consequent}
491
498
  ${this.ctx.pre}else ${alternate}`;
492
499
  }
493
500
  if (statement[0] === NODE.let || statement[0] === NODE.const) {
494
- let varType = "var";
501
+ let varType = "<deferred>";
495
502
  const [stmtType, rawId, rawValue] = statement;
496
503
  const eq = rawValue !== void 0 ? this._expression(rawValue) : void 0;
497
504
  if (!eq) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
@@ -523,7 +530,10 @@ ${this.ctx.pre}else ${alternate}`;
523
530
  invariant(ptrType !== void 0, `Creating pointer type from origin ${eq.origin}`);
524
531
  dataType = ptrType;
525
532
  }
526
- if (!(eq.value instanceof RefOperator)) dataType = implicitFrom(dataType);
533
+ if (!(eq.value instanceof RefOperator)) {
534
+ dataType = implicitFrom(dataType);
535
+ this.tryMarkModified(rawValue);
536
+ }
527
537
  }
528
538
  } else if (stmtType === NODE.const) {
529
539
  if (eq.origin === "argument") varType = "let";
@@ -542,9 +552,16 @@ ${this.ctx.pre}else ${alternate}`;
542
552
  const snippet = this.blockVariable(varType, rawId, concretize(dataType), eq.origin);
543
553
  const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false);
544
554
  const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
545
- return this.emitVarDecl(this.ctx.pre, varType, snippet.value, concretize(dataType), rhsStr);
555
+ let emittedVarType;
556
+ if (varType === "<deferred>") {
557
+ const scope = this.ctx.topFunctionScope;
558
+ invariant(scope, `Expected function scope to be present for ${rawId}`);
559
+ emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
560
+ scope.placeholderForVariable.set(snippet, emittedVarType);
561
+ } else emittedVarType = varType;
562
+ return this.emitVarDecl(this.ctx.pre, emittedVarType, snippet.value, concretize(dataType), rhsStr);
546
563
  }
547
- if (statement[0] === NODE.block) return `${this.ctx.pre}${this._block(statement)}`;
564
+ if (statement[0] === NODE.block) return this._blockStatement(statement);
548
565
  if (statement[0] === NODE.for) {
549
566
  const [_, init, condition, update, body] = statement;
550
567
  const prevUnrollingFlag = this.#unrolling;
@@ -581,6 +598,7 @@ ${this.ctx.pre}else ${alternate}`;
581
598
  if (statement[0] === NODE.forOf) {
582
599
  const [_, loopVar, iterable, body] = statement;
583
600
  if (loopVar[0] !== NODE.const) throw new WgslTypeError("Only `for (const ... of ... )` loops are supported");
601
+ this.tryMarkModified(iterable);
584
602
  let ctxIndent = false;
585
603
  const prevUnrollingFlag = this.#unrolling;
586
604
  try {
@@ -599,21 +617,21 @@ ${this.ctx.pre}else ${alternate}`;
599
617
  const { value } = iterableSnippet;
600
618
  const elements = isTgpuRange(value) ? value.map((i) => coerceToSnippet(i)) : value instanceof ArrayExpression ? value.elements : Array.from({ length }, (_$1, i) => getElementSnippet(iterableSnippet, snip(i, u32, "constant")));
601
619
  if (isEphemeralSnippet(elements[0]) && !isNaturallyEphemeral(elements[0]?.dataType)) throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
602
- return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block(blockified, { [originalLoopVarName]: e })}`).join("\n");
620
+ return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { [originalLoopVarName]: e })}`).join("\n");
603
621
  }
604
622
  this.#unrolling = false;
605
- const index = this.ctx.makeNameValid("i");
623
+ const index = this.ctx.makeUniqueIdentifier("i", "block");
606
624
  const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
607
625
  let bodyStr = "";
608
626
  if (isTgpuRange(iterableSnippet.value)) bodyStr = this._block(blockified, { [originalLoopVarName]: snip(index, range.start.dataType, "runtime") });
609
627
  else {
610
628
  this.ctx.indent();
611
629
  ctxIndent = true;
612
- const loopVarName = this.ctx.makeNameValid(originalLoopVarName);
630
+ const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, "block");
613
631
  const elementSnippet = getElementSnippet(iterableSnippet, snip(index, u32, "runtime"));
614
632
  const loopVarKind = getLoopVarKind(elementSnippet);
615
633
  const elementType = getElementType(elementSnippet, iterableSnippet);
616
- bodyStr = `{\n${stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`}\n${this.ctx.pre}${this._block(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin) })}\n`;
634
+ bodyStr = `{\n${stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin) })}\n`;
617
635
  this.ctx.dedent();
618
636
  bodyStr += `${this.ctx.pre}}`;
619
637
  ctxIndent = false;
@@ -625,6 +643,14 @@ ${this.ctx.pre}else ${alternate}`;
625
643
  this.ctx.popBlockScope();
626
644
  }
627
645
  }
646
+ if (statement[0] === NODE.postUpdate) {
647
+ const [_, op, arg] = statement;
648
+ const argExpr = this._expression(arg);
649
+ const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
650
+ validateSnippetMutation(argExpr, statement);
651
+ this.tryMarkModified(arg);
652
+ return `${this.ctx.pre}${argStr}${op};`;
653
+ }
628
654
  if (statement[0] === NODE.continue) {
629
655
  if (this.#unrolling) throw new WgslTypeError("Cannot unroll loop containing `continue`");
630
656
  return `${this.ctx.pre}continue;`;
@@ -637,7 +663,35 @@ ${this.ctx.pre}else ${alternate}`;
637
663
  const resolved = expr.value && this.ctx.resolve(expr.value).value;
638
664
  return resolved ? `${this.ctx.pre}${resolved};` : "";
639
665
  }
666
+ /**
667
+ * Attempts a member access lookup to mark a variable as modified.
668
+ * @example
669
+ * // given `let a; a = 1;`
670
+ * tryMarkModified('a') // `a` is marked in the function scope
671
+ *
672
+ * // given `const obj; obj.prop = 1;`
673
+ * tryMarkModified('obj.prop') // `obj` is marked in the function scope
674
+ *
675
+ * // given `this.buffer.$;`
676
+ * tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it
677
+ */
678
+ tryMarkModified(expr) {
679
+ if (!expr) return;
680
+ const maybeObject = extractObject(expr);
681
+ if (maybeObject !== void 0) {
682
+ const snippet = this.ctx.getById(maybeObject);
683
+ const scope = this.ctx.topFunctionScope;
684
+ if (snippet && scope && scope.placeholderForVariable.has(snippet)) scope.modifiedVariables.add(snippet);
685
+ }
686
+ }
640
687
  };
688
+ function validateSnippetMutation(mutated, expr) {
689
+ if (mutated.origin === "constant" || mutated.origin === "constant-tgpu-const-ref" || mutated.origin === "runtime-tgpu-const-ref") {
690
+ if (isKnownAtComptime(mutated)) throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is defined outside of the shader, and therefore is immutable during its execution. Try using tgpu.privateVar or buffers.`);
691
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
692
+ }
693
+ if (mutated.origin === "argument") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
694
+ }
641
695
  function assertExhaustive(value) {
642
696
  throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
643
697
  }
@@ -649,6 +703,11 @@ function parseNumericString(str) {
649
703
  function blockifySingleStatement(statement) {
650
704
  return typeof statement !== "object" || statement[0] !== NODE.block ? [NODE.block, [statement]] : statement;
651
705
  }
706
+ function extractObject(expr) {
707
+ let object = expr;
708
+ while (Array.isArray(object) && (object[0] === NODE.memberAccess || object[0] === NODE.indexAccess)) object = object[1];
709
+ if (typeof object === "string") return object;
710
+ }
652
711
  const wgslGenerator = new WgslGenerator();
653
712
  var wgslGenerator_default = wgslGenerator;
654
713