typegpu 0.11.9 → 0.12.0

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.
Files changed (362) hide show
  1. package/builtin.d.ts +51 -53
  2. package/builtin.js +30 -34
  3. package/common/fullScreenTriangle.d.ts +2 -10
  4. package/common/fullScreenTriangle.js +20 -28
  5. package/common/index.d.ts +2 -9
  6. package/common/index.js +3 -12
  7. package/common/writeSoA.d.ts +11 -12
  8. package/common/writeSoA.js +88 -76
  9. package/core/buffer/buffer.d.ts +79 -62
  10. package/core/buffer/buffer.js +279 -205
  11. package/core/buffer/bufferBinding.d.ts +71 -0
  12. package/core/buffer/bufferBinding.js +154 -0
  13. package/core/buffer/bufferUsage.d.ts +14 -45
  14. package/core/buffer/bufferUsage.js +1 -171
  15. package/core/buffer/laidOutBuffer.d.ts +25 -0
  16. package/core/buffer/laidOutBuffer.js +55 -0
  17. package/core/commandEncoder/attachments.d.ts +125 -0
  18. package/core/commandEncoder/attachments.js +29 -0
  19. package/core/commandEncoder/commandEncoder.d.ts +53 -0
  20. package/core/commandEncoder/commandEncoder.js +56 -0
  21. package/core/commandEncoder/computePass.d.ts +58 -0
  22. package/core/commandEncoder/computePass.js +63 -0
  23. package/core/commandEncoder/renderPass.d.ts +121 -0
  24. package/core/commandEncoder/renderPass.js +196 -0
  25. package/core/constant/tgpuConstant.d.ts +26 -26
  26. package/core/constant/tgpuConstant.js +76 -65
  27. package/core/declare/tgpuDeclare.d.ts +3 -6
  28. package/core/declare/tgpuDeclare.js +36 -36
  29. package/core/function/autoIO.d.ts +61 -33
  30. package/core/function/autoIO.js +68 -73
  31. package/core/function/comptime.d.ts +11 -13
  32. package/core/function/comptime.js +60 -52
  33. package/core/function/createCallableSchema.d.ts +16 -0
  34. package/core/function/createCallableSchema.js +50 -40
  35. package/core/function/dualImpl.d.ts +44 -0
  36. package/core/function/dualImpl.js +71 -50
  37. package/core/function/entryInputRouter.d.ts +26 -0
  38. package/core/function/entryInputRouter.js +31 -33
  39. package/core/function/extractArgs.d.ts +38 -0
  40. package/core/function/extractArgs.js +202 -200
  41. package/core/function/fnCore.d.ts +44 -5
  42. package/core/function/fnCore.js +148 -93
  43. package/core/function/fnTypes.d.ts +31 -29
  44. package/core/function/fnTypes.js +1 -0
  45. package/core/function/ioSchema.d.ts +12 -10
  46. package/core/function/ioSchema.js +65 -47
  47. package/core/function/shelllessImpl.d.ts +9 -12
  48. package/core/function/shelllessImpl.js +17 -22
  49. package/core/function/templateUtils.d.ts +2 -0
  50. package/core/function/templateUtils.js +9 -8
  51. package/core/function/tgpuComputeFn.d.ts +26 -28
  52. package/core/function/tgpuComputeFn.js +47 -48
  53. package/core/function/tgpuFn.d.ts +34 -37
  54. package/core/function/tgpuFn.js +179 -153
  55. package/core/function/tgpuFragmentFn.d.ts +52 -53
  56. package/core/function/tgpuFragmentFn.js +63 -56
  57. package/core/function/tgpuVertexFn.d.ts +36 -39
  58. package/core/function/tgpuVertexFn.js +56 -53
  59. package/core/pipeline/computePipeline.d.ts +141 -53
  60. package/core/pipeline/computePipeline.js +291 -225
  61. package/core/pipeline/connectAttachmentToShader.d.ts +4 -0
  62. package/core/pipeline/connectAttachmentToShader.js +25 -22
  63. package/core/pipeline/connectTargetsToShader.d.ts +3 -0
  64. package/core/pipeline/connectTargetsToShader.js +29 -27
  65. package/core/pipeline/drawState.d.ts +52 -0
  66. package/core/pipeline/drawState.js +225 -0
  67. package/core/pipeline/limitsOverflow.d.ts +2 -0
  68. package/core/pipeline/limitsOverflow.js +13 -10
  69. package/core/pipeline/performanceTracker.d.ts +14 -0
  70. package/core/pipeline/performanceTracker.js +34 -0
  71. package/core/pipeline/pipelineUtils.d.ts +18 -0
  72. package/core/pipeline/pipelineUtils.js +54 -25
  73. package/core/pipeline/priors.d.ts +1 -0
  74. package/core/pipeline/priors.js +12 -0
  75. package/core/pipeline/renderPipeline.d.ts +246 -261
  76. package/core/pipeline/renderPipeline.js +487 -480
  77. package/core/pipeline/timeable.d.ts +28 -18
  78. package/core/pipeline/timeable.js +90 -52
  79. package/core/pipeline/typeGuards.d.ts +17 -0
  80. package/core/pipeline/typeGuards.js +52 -22
  81. package/core/querySet/querySet.d.ts +26 -21
  82. package/core/querySet/querySet.js +127 -103
  83. package/core/rawCodeSnippet/tgpuRawCodeSnippet.d.ts +13 -18
  84. package/core/rawCodeSnippet/tgpuRawCodeSnippet.js +89 -90
  85. package/core/resolve/externals.d.ts +25 -8
  86. package/core/resolve/externals.js +78 -54
  87. package/core/resolve/namespace.d.ts +22 -25
  88. package/core/resolve/namespace.js +16 -21
  89. package/core/resolve/resolvableString.d.ts +7 -0
  90. package/core/resolve/resolvableString.js +13 -0
  91. package/core/resolve/resolveData.d.ts +10 -0
  92. package/core/resolve/resolveData.js +162 -121
  93. package/core/resolve/stitch.d.ts +8 -0
  94. package/core/resolve/stitch.js +23 -21
  95. package/core/resolve/tgpuResolve.d.ts +55 -53
  96. package/core/resolve/tgpuResolve.js +106 -53
  97. package/core/root/configurableImpl.d.ts +10 -0
  98. package/core/root/configurableImpl.js +17 -19
  99. package/core/root/init.d.ts +76 -38
  100. package/core/root/init.js +346 -455
  101. package/core/root/rootTypes.d.ts +461 -636
  102. package/core/root/rootTypes.js +1 -0
  103. package/core/sampler/sampler.d.ts +50 -31
  104. package/core/sampler/sampler.js +120 -113
  105. package/core/simulate/tgpuSimulate.d.ts +9 -12
  106. package/core/simulate/tgpuSimulate.js +68 -71
  107. package/core/slot/accessor.d.ts +36 -13
  108. package/core/slot/accessor.js +138 -91
  109. package/core/slot/internalSlots.d.ts +2 -0
  110. package/core/slot/internalSlots.js +1 -6
  111. package/core/slot/lazy.d.ts +2 -6
  112. package/core/slot/lazy.js +71 -39
  113. package/core/slot/slot.d.ts +2 -6
  114. package/core/slot/slot.js +45 -38
  115. package/core/slot/slotTypes.d.ts +78 -80
  116. package/core/slot/slotTypes.js +11 -16
  117. package/core/texture/externalTexture.d.ts +20 -11
  118. package/core/texture/externalTexture.js +46 -46
  119. package/core/texture/texture.d.ts +152 -97
  120. package/core/texture/texture.js +407 -314
  121. package/core/texture/textureFormats.d.ts +42 -23
  122. package/core/texture/textureFormats.js +104 -75
  123. package/core/texture/textureProps.d.ts +7 -10
  124. package/core/texture/textureProps.js +1 -0
  125. package/core/texture/textureUtils.d.ts +8 -0
  126. package/core/texture/textureUtils.js +185 -179
  127. package/core/texture/usageExtension.d.ts +21 -18
  128. package/core/texture/usageExtension.js +13 -17
  129. package/core/unroll/tgpuUnroll.d.ts +20 -19
  130. package/core/unroll/tgpuUnroll.js +82 -91
  131. package/core/valueProxyUtils.d.ts +3 -0
  132. package/core/valueProxyUtils.js +52 -41
  133. package/core/variable/tgpuVariable.d.ts +26 -27
  134. package/core/variable/tgpuVariable.js +93 -96
  135. package/core/vertexLayout/connectAttributesToShader.d.ts +9 -0
  136. package/core/vertexLayout/connectAttributesToShader.js +61 -56
  137. package/core/vertexLayout/vertexAttribute.d.ts +21 -17
  138. package/core/vertexLayout/vertexAttribute.js +1 -0
  139. package/core/vertexLayout/vertexLayout.d.ts +24 -18
  140. package/core/vertexLayout/vertexLayout.js +130 -99
  141. package/core/whitespaces.d.ts +2 -0
  142. package/core/whitespaces.js +16 -0
  143. package/data/alignIO.d.ts +7 -0
  144. package/data/alignIO.js +13 -13
  145. package/data/alignmentOf.d.ts +5 -8
  146. package/data/alignmentOf.js +90 -75
  147. package/data/array.d.ts +6 -9
  148. package/data/array.js +48 -40
  149. package/data/atomic.d.ts +2 -7
  150. package/data/atomic.js +26 -23
  151. package/data/attributes.d.ts +19 -21
  152. package/data/attributes.js +192 -132
  153. package/data/autoStruct.d.ts +36 -3
  154. package/data/autoStruct.js +84 -81
  155. package/data/compiledIO.d.ts +5 -0
  156. package/data/compiledIO.js +231 -215
  157. package/data/dataIO.d.ts +13 -11
  158. package/data/dataIO.js +643 -589
  159. package/data/dataTypes.d.ts +83 -65
  160. package/data/dataTypes.js +66 -68
  161. package/data/deepEqual.d.ts +2 -7
  162. package/data/deepEqual.js +80 -55
  163. package/data/disarray.d.ts +6 -10
  164. package/data/disarray.js +52 -46
  165. package/data/float16Conversion.d.ts +4 -0
  166. package/data/float16Conversion.js +7 -0
  167. package/data/getLongestContiguousPrefix.d.ts +4 -8
  168. package/data/getLongestContiguousPrefix.js +6 -11
  169. package/data/index.d.ts +30 -31
  170. package/data/index.js +39 -172
  171. package/data/instanceToSchema.d.ts +24 -27
  172. package/data/instanceToSchema.js +1 -0
  173. package/data/isContiguous.d.ts +4 -8
  174. package/data/isContiguous.js +6 -11
  175. package/data/matrix.d.ts +21 -21
  176. package/data/matrix.js +513 -505
  177. package/data/numberOps.d.ts +6 -0
  178. package/data/numberOps.js +26 -20
  179. package/data/numeric.d.ts +21 -12
  180. package/data/numeric.js +287 -197
  181. package/data/offsetUtils.d.ts +9 -13
  182. package/data/offsetUtils.js +204 -144
  183. package/data/offsets.d.ts +8 -0
  184. package/data/offsets.js +33 -33
  185. package/data/partialIO.d.ts +13 -8
  186. package/data/partialIO.js +124 -113
  187. package/data/ptr.d.ts +12 -12
  188. package/data/ptr.js +42 -44
  189. package/data/ref.d.ts +40 -27
  190. package/data/ref.js +125 -95
  191. package/data/sampler.d.ts +99 -101
  192. package/data/sampler.js +17 -21
  193. package/data/schemaCallWrapper.d.ts +19 -0
  194. package/data/schemaCallWrapper.js +31 -26
  195. package/data/schemaMemoryLayout.d.ts +8 -0
  196. package/data/schemaMemoryLayout.js +213 -185
  197. package/data/sizeOf.d.ts +4 -8
  198. package/data/sizeOf.js +6 -11
  199. package/data/snippet.d.ts +96 -20
  200. package/data/snippet.js +75 -86
  201. package/data/struct.d.ts +4 -7
  202. package/data/struct.js +51 -39
  203. package/data/texture.d.ts +253 -250
  204. package/data/texture.js +238 -200
  205. package/data/unstruct.d.ts +2 -7
  206. package/data/unstruct.js +43 -37
  207. package/data/vector.d.ts +33 -21
  208. package/data/vector.js +234 -221
  209. package/data/vectorImpl.d.ts +231 -0
  210. package/data/vectorImpl.js +563 -515
  211. package/data/vectorOps.d.ts +63 -0
  212. package/data/vectorOps.js +642 -630
  213. package/data/vertexFormatData.d.ts +181 -184
  214. package/data/vertexFormatData.js +104 -106
  215. package/data/wgslTypes.d.ts +665 -600
  216. package/data/wgslTypes.js +220 -198
  217. package/errors.d.ts +50 -24
  218. package/errors.js +146 -126
  219. package/execMode.d.ts +16 -0
  220. package/execMode.js +47 -40
  221. package/extension.d.ts +13 -8
  222. package/extension.js +10 -16
  223. package/getGPUValue.d.ts +1 -0
  224. package/getGPUValue.js +2 -7
  225. package/index.d.ts +28 -68
  226. package/index.js +8 -28
  227. package/indexNamedExports.d.ts +69 -42
  228. package/indexNamedExports.js +25 -0
  229. package/internal.d.ts +16 -0
  230. package/internal.js +12 -0
  231. package/mathUtils.d.ts +5 -0
  232. package/mathUtils.js +7 -11
  233. package/memo.d.ts +11 -0
  234. package/memo.js +20 -22
  235. package/minify.d.ts +5 -0
  236. package/minify.js +93 -0
  237. package/nameUtils.d.ts +29 -0
  238. package/nameUtils.js +427 -394
  239. package/package.json +5 -1
  240. package/resolutionCtx.d.ts +162 -22
  241. package/resolutionCtx.js +911 -629
  242. package/serial/registry.d.ts +15 -0
  243. package/serial/registry.js +106 -0
  244. package/serial/restore.d.ts +49 -0
  245. package/serial/restore.js +68 -0
  246. package/serial/schema.d.ts +58 -0
  247. package/serial/schema.js +209 -0
  248. package/serial/types.d.ts +5 -0
  249. package/serial/types.js +1 -0
  250. package/shared/env.d.ts +9 -0
  251. package/shared/env.js +9 -12
  252. package/shared/generators.d.ts +4 -0
  253. package/shared/generators.js +10 -12
  254. package/shared/meta.d.ts +29 -14
  255. package/shared/meta.js +71 -55
  256. package/shared/normalizeMetadata.d.ts +32 -27
  257. package/shared/normalizeMetadata.js +23 -36
  258. package/shared/repr.d.ts +73 -55
  259. package/shared/repr.js +1 -0
  260. package/shared/soul.d.ts +17 -0
  261. package/shared/soul.js +1 -0
  262. package/shared/stringify.d.ts +2 -0
  263. package/shared/stringify.js +29 -20
  264. package/shared/symbols.d.ts +26 -20
  265. package/shared/symbols.js +79 -79
  266. package/shared/tseynit.d.ts +2 -0
  267. package/shared/tseynit.js +160 -75
  268. package/shared/utilityTypes.d.ts +30 -14
  269. package/shared/utilityTypes.js +2 -6
  270. package/shared/vertexFormat.d.ts +41 -43
  271. package/shared/vertexFormat.js +59 -62
  272. package/std/array.d.ts +2 -7
  273. package/std/array.js +18 -23
  274. package/std/atomic.d.ts +14 -18
  275. package/std/atomic.js +96 -95
  276. package/std/bitcast.d.ts +193 -10
  277. package/std/bitcast.js +309 -39
  278. package/std/boolean.d.ts +19 -33
  279. package/std/boolean.js +296 -288
  280. package/std/copy.d.ts +2 -7
  281. package/std/copy.js +27 -24
  282. package/std/derivative.d.ts +11 -15
  283. package/std/derivative.js +56 -79
  284. package/std/discard.d.ts +1 -6
  285. package/std/discard.js +7 -14
  286. package/std/environment.d.ts +19 -14
  287. package/std/environment.js +71 -51
  288. package/std/extensions.d.ts +2 -8
  289. package/std/extensions.js +10 -11
  290. package/std/index.d.ts +20 -24
  291. package/std/index.js +32 -187
  292. package/std/matrix.d.ts +6 -12
  293. package/std/matrix.js +60 -69
  294. package/std/numeric.d.ts +143 -146
  295. package/std/numeric.js +858 -749
  296. package/std/operators.d.ts +19 -27
  297. package/std/operators.js +227 -193
  298. package/std/packing.d.ts +5 -11
  299. package/std/packing.js +67 -75
  300. package/std/range.d.ts +6 -11
  301. package/std/range.js +48 -36
  302. package/std/subgroup.d.ts +33 -37
  303. package/std/subgroup.js +180 -217
  304. package/std/texture.d.ts +70 -38
  305. package/std/texture.js +176 -172
  306. package/taskQueue.d.ts +6 -0
  307. package/taskQueue.js +30 -0
  308. package/tgpu.d.ts +16 -0
  309. package/tgpu.js +17 -44
  310. package/tgpuBindGroupLayout.d.ts +132 -152
  311. package/tgpuBindGroupLayout.js +346 -268
  312. package/tgpuLogger.d.ts +34 -0
  313. package/tgpuLogger.js +63 -0
  314. package/tgpuUnstable.d.ts +16 -22
  315. package/tgpuUnstable.js +13 -35
  316. package/tgsl/accessIndex.d.ts +2 -0
  317. package/tgsl/accessIndex.js +76 -33
  318. package/tgsl/accessProp.d.ts +2 -0
  319. package/tgsl/accessProp.js +142 -101
  320. package/tgsl/accessStructProp.d.ts +5 -0
  321. package/tgsl/accessStructProp.js +13 -15
  322. package/tgsl/consoleLog/deserializers.d.ts +13 -0
  323. package/tgsl/consoleLog/deserializers.js +119 -103
  324. package/tgsl/consoleLog/logGenerator.d.ts +21 -0
  325. package/tgsl/consoleLog/logGenerator.js +85 -80
  326. package/tgsl/consoleLog/serializers.d.ts +21 -0
  327. package/tgsl/consoleLog/serializers.js +119 -100
  328. package/tgsl/consoleLog/types.d.ts +40 -39
  329. package/tgsl/consoleLog/types.js +1 -0
  330. package/tgsl/conversion.d.ts +33 -0
  331. package/tgsl/conversion.js +271 -216
  332. package/tgsl/forOfUtils.d.ts +15 -0
  333. package/tgsl/forOfUtils.js +85 -70
  334. package/tgsl/generationHelpers.d.ts +22 -39
  335. package/tgsl/generationHelpers.js +85 -58
  336. package/tgsl/infixDispatch.d.ts +70 -0
  337. package/tgsl/infixDispatch.js +48 -46
  338. package/tgsl/jsPolyfills.d.ts +16 -19
  339. package/tgsl/jsPolyfills.js +44 -42
  340. package/tgsl/makeDereferenceable.d.ts +48 -0
  341. package/tgsl/makeDereferenceable.js +86 -0
  342. package/tgsl/makeResolvable.d.ts +22 -0
  343. package/tgsl/makeResolvable.js +33 -0
  344. package/tgsl/shaderGenerator.d.ts +77 -17
  345. package/tgsl/shaderGenerator.js +1 -0
  346. package/tgsl/shellless.d.ts +6 -9
  347. package/tgsl/shellless.js +73 -43
  348. package/tgsl/wgslGenerator.d.ts +66 -59
  349. package/tgsl/wgslGenerator.js +1344 -760
  350. package/types.d.ts +287 -222
  351. package/types.js +61 -44
  352. package/unwrapper.d.ts +31 -26
  353. package/unwrapper.js +1 -0
  354. package/wgslExtensions.d.ts +3 -5
  355. package/wgslExtensions.js +12 -16
  356. package/_virtual/rolldown_runtime.js +0 -13
  357. package/core/buffer/bufferShorthand.d.ts +0 -50
  358. package/core/buffer/bufferShorthand.js +0 -55
  359. package/core/pipeline/applyPipelineState.js +0 -37
  360. package/package.js +0 -5
  361. package/tgsl/shaderGenerator_members.d.ts +0 -33
  362. package/tgsl/shaderGenerator_members.js +0 -12
@@ -1,805 +1,1389 @@
1
- import { $gpuCallable, $internal, $providing, isMarkedInternal } from "../shared/symbols.js";
2
- import { Void, isBool, isNaturallyEphemeral, isNumericSchema, isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
3
- import { safeStringify } from "../shared/stringify.js";
4
- import { getName } from "../shared/meta.js";
5
- import { UnknownData, unptr } from "../data/dataTypes.js";
6
- import { fallthroughCopyOrigin, isAlias, snip } from "../data/snippet.js";
7
- import { ResolutionError, WgslTypeError, invariant } from "../errors.js";
8
- import { isGPUCallable, isKnownAtComptime } from "../types.js";
1
+ import * as tinyest from 'tinyest';
9
2
  import { stitch } from "../core/resolve/stitch.js";
10
- import { createPtrFromOrigin, implicitFrom, ptrFn } from "../data/ptr.js";
11
- import { RefOperator, _ref } from "../data/ref.js";
12
- import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
3
+ import { arrayOf } from "../data/array.js";
4
+ import { UnknownData, unptr } from "../data/dataTypes.js";
13
5
  import { bool, i32, u32 } from "../data/numeric.js";
14
- import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet } from "./generationHelpers.js";
15
6
  import { vec2u, vec3u, vec4u } from "../data/vector.js";
16
- import { getAttributesString } from "../data/attributes.js";
17
- import { AutoStruct } from "../data/autoStruct.js";
7
+ import { fallthroughCopyOrigin, isAlias, snip, } from "../data/snippet.js";
8
+ import * as wgsl from "../data/wgslTypes.js";
9
+ import { invariant, ResolutionError, WgslTypeError } from "../errors.js";
10
+ import { getName } from "../shared/meta.js";
11
+ import { $gpuCallable, $internal, $providing, isMarkedInternal } from "../shared/symbols.js";
12
+ import { safeStringify } from "../shared/stringify.js";
13
+ import { pow } from "../std/numeric.js";
18
14
  import { add, div, mul, neg, sub } from "../std/operators.js";
19
- import { isInfixDispatch } from "./infixDispatch.js";
20
- import { accessProp } from "./accessProp.js";
15
+ import { eq, ne, lt, le, gt, ge, not } from "../std/boolean.js";
16
+ import { isGPUCallable, isKnownAtComptime, } from "../types.js";
17
+ import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
18
+ import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet, } from "./generationHelpers.js";
21
19
  import { accessIndex } from "./accessIndex.js";
20
+ import { accessProp } from "./accessProp.js";
21
+ import { resolveData } from "../core/resolve/resolveData.js";
22
+ import { createPtrFromOrigin, implicitFrom, ptrFn } from "../data/ptr.js";
23
+ import { _ref, RefOperator } from "../data/ref.js";
22
24
  import { constant } from "../core/constant/tgpuConstant.js";
25
+ import { unroll, UnrollableIterable } from "../core/unroll/tgpuUnroll.js";
23
26
  import { isGenericFn } from "../core/function/tgpuFn.js";
24
- import { arrayOf } from "../data/array.js";
25
- import { pow } from "../std/numeric.js";
26
- import { resolveData } from "../core/resolve/resolveData.js";
27
- import { UnrollableIterable, unroll } from "../core/unroll/tgpuUnroll.js";
27
+ import { AutoStruct } from "../data/autoStruct.js";
28
28
  import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
29
+ import * as forOfUtils from "./forOfUtils.js";
29
30
  import { isTgpuRange } from "../std/range.js";
30
- import { getElementSnippet, getElementType, getLoopVarKind, getRangeSnippets } from "./forOfUtils.js";
31
31
  import { stringifyNode } from "../shared/tseynit.js";
32
+ import { getAttributesString } from "../data/attributes.js";
32
33
  import { validSelectBranchTypes } from "../std/boolean.js";
33
- import * as tinyest from "tinyest";
34
-
35
- //#region src/tgsl/wgslGenerator.ts
34
+ import { isInfixDispatch } from "./infixDispatch.js";
35
+ import { logger } from "../tgpuLogger.js";
36
36
  const { NodeTypeCatalog: NODE } = tinyest;
37
37
  const parenthesizedOps = [
38
- "==",
39
- "!=",
40
- "===",
41
- "!==",
42
- "<",
43
- "<=",
44
- ">",
45
- ">=",
46
- "<<",
47
- ">>",
48
- "+",
49
- "-",
50
- "*",
51
- "/",
52
- "%",
53
- "|",
54
- "^",
55
- "&",
56
- "&&",
57
- "||"
58
- ];
59
- const binaryLogicalOps = [
60
- "&&",
61
- "||",
62
- "==",
63
- "!=",
64
- "===",
65
- "!==",
66
- "<",
67
- "<=",
68
- ">",
69
- ">="
70
- ];
71
- const bitShiftOps = [
72
- "<<",
73
- ">>",
74
- "<<=",
75
- ">>="
38
+ '==',
39
+ '!=',
40
+ '===',
41
+ '!==',
42
+ '<',
43
+ '<=',
44
+ '>',
45
+ '>=',
46
+ '<<',
47
+ '>>',
48
+ '>>>',
49
+ '+',
50
+ '-',
51
+ '*',
52
+ '/',
53
+ '%',
54
+ '|',
55
+ '^',
56
+ '&',
76
57
  ];
58
+ const binaryLogicalOps = ['&&', '||', '==', '!=', '===', '!==', '<', '<=', '>', '>='];
59
+ const binaryRelationalOpToStdMap = {
60
+ '===': eq.toString(),
61
+ '!==': ne.toString(),
62
+ '<': lt.toString(),
63
+ '<=': le.toString(),
64
+ '>': gt.toString(),
65
+ '>=': ge.toString(),
66
+ };
67
+ const bitShiftOps = ['<<', '>>', '<<=', '>>=', '>>>', '>>>='];
77
68
  const OP_MAP = {
78
- "===": "==",
79
- "!==": "!=",
80
- get ">>>"() {
81
- throw new Error("The `>>>` operator is unsupported in TypeGPU functions.");
82
- },
83
- get in() {
84
- throw new Error("The `in` operator is unsupported in TypeGPU functions.");
85
- },
86
- get instanceof() {
87
- throw new Error("The `instanceof` operator is unsupported in TypeGPU functions.");
88
- },
89
- get "|>"() {
90
- throw new Error("The `|>` operator is unsupported in TypeGPU functions.");
91
- },
92
- get "??"() {
93
- throw new Error("The `??` operator is unsupported in TypeGPU functions.");
94
- },
95
- get ">>>="() {
96
- throw new Error("The `>>>=` operator is unsupported in TypeGPU functions.");
97
- },
98
- get "**="() {
99
- throw new Error("The `**=` operator is unsupported in TypeGPU functions.");
100
- },
101
- get "??="() {
102
- throw new Error("The `??=` operator is unsupported in TypeGPU functions.");
103
- },
104
- get "&&="() {
105
- throw new Error("The `&&=` operator is unsupported in TypeGPU functions.");
106
- },
107
- get "||="() {
108
- throw new Error("The `||=` operator is unsupported in TypeGPU functions.");
109
- }
69
+ //
70
+ // binary
71
+ //
72
+ '===': '==',
73
+ '!==': '!=',
74
+ '>>>': '>>',
75
+ get in() {
76
+ throw new Error('The `in` operator is unsupported in TypeGPU functions.');
77
+ },
78
+ get instanceof() {
79
+ throw new Error('The `instanceof` operator is unsupported in TypeGPU functions.');
80
+ },
81
+ get '|>'() {
82
+ throw new Error('The `|>` operator is unsupported in TypeGPU functions.');
83
+ },
84
+ //
85
+ // logical
86
+ //
87
+ get '??'() {
88
+ throw new Error('The `??` operator is unsupported in TypeGPU functions.');
89
+ },
90
+ //
91
+ // assignment
92
+ //
93
+ '>>>=': '>>=',
94
+ get '**='() {
95
+ throw new Error('The `**=` operator is unsupported in TypeGPU functions.');
96
+ },
97
+ get '??='() {
98
+ throw new Error('The `??=` operator is unsupported in TypeGPU functions.');
99
+ },
100
+ get '&&='() {
101
+ throw new Error('The `&&=` operator is unsupported in TypeGPU functions.');
102
+ },
103
+ get '||='() {
104
+ throw new Error('The `||=` operator is unsupported in TypeGPU functions.');
105
+ },
110
106
  };
111
107
  function operatorToType(lhs, op, rhs) {
112
- if (!rhs) {
113
- if (op === "!") return bool;
114
- return lhs;
115
- }
116
- if (binaryLogicalOps.includes(op)) return bool;
117
- if (op === "=") return rhs;
118
- return lhs;
108
+ if (!rhs) {
109
+ if (op === '!') {
110
+ return bool;
111
+ }
112
+ return lhs;
113
+ }
114
+ if (binaryLogicalOps.includes(op)) {
115
+ return bool;
116
+ }
117
+ if (op === '=') {
118
+ return rhs;
119
+ }
120
+ return lhs;
119
121
  }
120
122
  const unaryOpCodeToCodegen = {
121
- "-": neg[$gpuCallable].call.bind(neg),
122
- void: () => snip(void 0, Void, "constant"),
123
- "!": (ctx, [argExpr]) => {
124
- if (argExpr === void 0) throw new Error("The unary operator `!` expects 1 argument, but 0 were provided.");
125
- if (isKnownAtComptime(argExpr)) return snip(!argExpr.value, bool, "constant");
126
- const { value, dataType } = argExpr;
127
- const argStr = ctx.resolve(value, dataType).value;
128
- if (isBool(dataType)) return snip(`!${argStr}`, bool, "runtime");
129
- if (isNumericSchema(dataType)) {
130
- const resultStr = `!bool(${argStr})`;
131
- return snip(dataType.type === "f32" ? `(((bitcast<u32>(${argStr}) & 0x7fffffff) > 0x7f800000) || ${resultStr})` : dataType.type === "f16" ? `(((bitcast<u32>(${argStr}) & 0x7fff) > 0x7c00) || ${resultStr})` : resultStr, bool, "runtime");
132
- }
133
- return snip(false, bool, "constant");
134
- }
123
+ '-': neg[$gpuCallable].call.bind(neg),
124
+ void: () => snip(undefined, wgsl.Void, 'constant', false),
125
+ '!': (ctx, [argExpr]) => {
126
+ if (argExpr === undefined) {
127
+ throw new Error('The unary operator `!` expects 1 argument, but 0 were provided.');
128
+ }
129
+ if (isKnownAtComptime(argExpr)) {
130
+ return snip(!argExpr.value, bool, 'constant', false);
131
+ }
132
+ const argStr = ctx.resolveSnippet(argExpr).value;
133
+ if (!wgsl.isBool(argExpr.dataType)) {
134
+ throw new WgslTypeError(`Unary operator ! requires boolean operand. Got ${String(argExpr.dataType)}.${wgsl.isVecBool(argExpr.dataType)
135
+ ? ` For component-wise negation, use 'std.${not.toString()}'.`
136
+ : ''}`);
137
+ }
138
+ return snip(`!(${argStr})`, bool, 'runtime', argExpr.possibleSideEffects);
139
+ },
135
140
  };
136
141
  const binaryOpCodeToCodegen = {
137
- "+": add[$gpuCallable].call.bind(add),
138
- "-": sub[$gpuCallable].call.bind(sub),
139
- "*": mul[$gpuCallable].call.bind(mul),
140
- "/": div[$gpuCallable].call.bind(div),
141
- "**": pow[$gpuCallable].call.bind(pow)
142
+ '+': add[$gpuCallable].call.bind(add),
143
+ '-': sub[$gpuCallable].call.bind(sub),
144
+ '*': mul[$gpuCallable].call.bind(mul),
145
+ '/': div[$gpuCallable].call.bind(div),
146
+ '**': pow[$gpuCallable].call.bind(pow),
142
147
  };
143
148
  const usageToVarTemplateMap = {
144
- private: "private",
145
- workgroup: "workgroup",
146
- uniform: "uniform",
147
- mutable: "storage, read_write",
148
- readonly: "storage, read"
149
+ private: 'private',
150
+ workgroup: 'workgroup',
151
+ uniform: 'uniform',
152
+ mutable: 'storage, read_write',
153
+ readonly: 'storage, read',
149
154
  };
150
- var WgslGenerator = class {
151
- #ctx = void 0;
152
- #unrolling = false;
153
- initGenerator(ctx) {
154
- this.#ctx = ctx;
155
- }
156
- get ctx() {
157
- if (!this.#ctx) throw new Error("WGSL Generator has not yet been initialized. Please call initialize(ctx) before using the generator.");
158
- return this.#ctx;
159
- }
160
- _block([_, statements], externalMap) {
161
- this.ctx.pushBlockScope();
162
- if (externalMap) {
163
- const externals = Object.fromEntries(Object.entries(externalMap).map(([id, value]) => [id, coerceToSnippet(value)]));
164
- this.ctx.setBlockExternals(externals);
165
- }
166
- try {
167
- this.ctx.indent();
168
- const body = statements.map((statement) => this._statement(statement)).filter((statement) => statement.length > 0).join("\n");
169
- this.ctx.dedent();
170
- return `{
171
- ${body}
172
- ${this.ctx.pre}}`;
173
- } finally {
174
- this.ctx.popBlockScope();
175
- }
176
- }
177
- _blockStatement(block, externalMap) {
178
- return `${this.ctx.pre}${this._block(block, externalMap)}`;
179
- }
180
- refVariable(id, dataType) {
181
- const varName = this.ctx.makeUniqueIdentifier(id, "block");
182
- const ptrType = ptrFn(dataType);
183
- const snippet = snip(new RefOperator(snip(varName, dataType, "function"), ptrType), ptrType, "function");
184
- this.ctx.defineVariable(id, snippet);
185
- return varName;
186
- }
187
- /**
188
- * Creates a variable declaration string.
189
- * `keyword` may be a placeholder filled in later.
190
- */
191
- _emitVarDecl(keyword, name, _dataType, rhsStr) {
192
- return `${this.ctx.pre}${keyword} ${name} = ${rhsStr};`;
193
- }
194
- _identifier(id) {
195
- if (!id) throw new Error("Cannot resolve an empty identifier");
196
- if (id === "undefined") return snip(void 0, Void, "constant");
197
- const res = this.ctx.getById(id);
198
- if (!res) throw new Error(`Identifier ${id} not found`);
199
- return res;
200
- }
201
- /**
202
- * A wrapper for `generateExpression` that updates `ctx.expectedType`
203
- * and tries to convert the result when it does not match the expected type.
204
- */
205
- _typedExpression(expression, expectedType) {
206
- const prevExpectedType = this.ctx.expectedType;
207
- this.ctx.expectedType = expectedType;
208
- try {
209
- const result = this._expression(expression);
210
- if (expectedType instanceof AutoStruct) return result;
211
- return tryConvertSnippet(this.ctx, result, expectedType);
212
- } finally {
213
- this.ctx.expectedType = prevExpectedType;
214
- }
215
- }
216
- _expression(expression) {
217
- if (typeof expression === "string") return this._identifier(expression);
218
- if (typeof expression === "boolean") return snip(expression, bool, "constant");
219
- if (expression[0] === NODE.logicalExpr || expression[0] === NODE.binaryExpr || expression[0] === NODE.assignmentExpr) {
220
- const [exprType, lhs, op, rhs] = expression;
221
- const lhsExpr = this._expression(lhs);
222
- if ((op === "||" || op === "&&") && isKnownAtComptime(lhsExpr)) {
223
- if (!(op === "&&" ? lhsExpr.value : !lhsExpr.value)) return snip(op === "||", bool, "constant");
224
- const rhsExpr$1 = this._expression(rhs);
225
- if (rhsExpr$1.dataType === UnknownData) throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
226
- if (isKnownAtComptime(rhsExpr$1)) return snip(!!rhsExpr$1.value, bool, "constant");
227
- const convRhs$1 = tryConvertSnippet(this.ctx, rhsExpr$1, bool, false);
228
- const rhsStr$1 = this.ctx.resolve(convRhs$1.value, convRhs$1.dataType).value;
229
- return snip(rhsStr$1, bool, "runtime");
230
- }
231
- const rhsExpr = this._expression(rhs);
232
- if (rhsExpr.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
233
- if (op === "==") throw new Error("Please use the === operator instead of ==");
234
- if (op === "!=") throw new Error("Please use the !== operator instead of !=");
235
- if (op === "===" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value === rhsExpr.value, bool, "constant", false);
236
- if (op === "!==" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value !== rhsExpr.value, bool, "constant", false);
237
- if ((op === "<" || op === "<=" || op === ">" || op === ">=") && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) {
238
- const left = lhsExpr.value;
239
- const right = rhsExpr.value;
240
- if (typeof left !== "number" || typeof right !== "number") throw new WgslTypeError(`Inequality comparison '${op}' requires numeric operands, got '${typeof left}' and '${typeof right}'`);
241
- switch (op) {
242
- case "<": return snip(left < right, bool, "constant", false);
243
- case "<=": return snip(left <= right, bool, "constant", false);
244
- case ">": return snip(left > right, bool, "constant", false);
245
- case ">=": return snip(left >= right, bool, "constant", false);
246
- }
247
- }
248
- if (lhsExpr.dataType === UnknownData) throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
249
- if (rhsExpr.dataType === UnknownData) throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
250
- const codegen = binaryOpCodeToCodegen[op];
251
- if (codegen) return codegen(this.ctx, [lhsExpr, rhsExpr]);
252
- let convLhs;
253
- let convRhs;
254
- if (bitShiftOps.includes(op)) {
255
- let rhsTarget;
256
- if (isVec(lhsExpr.dataType)) {
257
- const cc = lhsExpr.dataType.componentCount;
258
- rhsTarget = cc === 2 ? vec2u : cc === 3 ? vec3u : vec4u;
259
- } else rhsTarget = u32;
260
- convRhs = tryConvertSnippet(this.ctx, rhsExpr, rhsTarget, false);
261
- convLhs = lhsExpr;
262
- } else {
263
- const forcedType = exprType === NODE.assignmentExpr ? [lhsExpr.dataType] : void 0;
264
- [convLhs, convRhs] = convertToCommonType(this.ctx, [lhsExpr, rhsExpr], forcedType) ?? [lhsExpr, rhsExpr];
265
- }
266
- const lhsStr = this.ctx.resolve(convLhs.value, convLhs.dataType).value;
267
- const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value;
268
- const type = operatorToType(convLhs.dataType, op, convRhs.dataType);
269
- if (exprType === NODE.assignmentExpr) {
270
- validateSnippetMutation(convLhs, expression);
271
- this.tryMarkModified(lhs);
272
- if (op === "=" && isAlias(rhsExpr) && !isNaturallyEphemeral(rhsExpr.dataType)) 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-----`);
273
- }
274
- return snip(parenthesizedOps.includes(op) ? `(${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr})` : `${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr}`, type, "runtime");
275
- }
276
- if (expression[0] === NODE.postUpdate) throw new Error(`'${stringifyNode(expression)}' is invalid because update is only allowed as a statement.`);
277
- if (expression[0] === NODE.unaryExpr) {
278
- const [_, op, arg] = expression;
279
- const argExpr = this._expression(arg);
280
- const codegen = unaryOpCodeToCodegen[op];
281
- if (codegen) return codegen(this.ctx, [argExpr]);
282
- const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
283
- const type = operatorToType(argExpr.dataType, op);
284
- return snip(`${op}${argStr}`, type, "runtime");
285
- }
286
- if (expression[0] === NODE.memberAccess) {
287
- const [_, targetNode, property] = expression;
288
- const accessed = accessProp(this._expression(targetNode), property);
289
- if (!accessed) throw new Error(`Property '${property}' not found on '${stringifyNode(targetNode)}'`);
290
- return accessed;
291
- }
292
- if (expression[0] === NODE.indexAccess) {
293
- const [_, targetNode, propertyNode] = expression;
294
- const target = this._expression(targetNode);
295
- const inProperty = this._expression(propertyNode);
296
- const accessed = accessIndex(target, convertToCommonType(this.ctx, [inProperty], [u32, i32], false)?.[0] ?? inProperty);
297
- if (!accessed) throw new Error(`Index access '${stringifyNode(expression)}' is invalid. If the value is an array, to address this, consider one of the following approaches: (1) declare the array using 'tgpu.const', (2) store the array in a buffer, or (3) define the array within the GPU function scope.`);
298
- return accessed;
299
- }
300
- if (expression[0] === NODE.numericLiteral) {
301
- const type = typeof expression[1] === "string" ? numericLiteralToSnippet(parseNumericString(expression[1])) : numericLiteralToSnippet(expression[1]);
302
- invariant(type, `Expected ${stringifyNode(expression)} to be valid numeric literal`);
303
- return type;
304
- }
305
- if (expression[0] === NODE.call) {
306
- const [_, calleeNode, argNodes] = expression;
307
- const _callee = this._expression(calleeNode);
308
- const callee = mathToStd.has(_callee.value) ? snip(mathToStd.get(_callee.value), UnknownData, "runtime") : _callee;
309
- if (supportedLogOps().includes(callee.value)) return this.ctx.generateLog(callee.value, argNodes.map((arg) => this._expression(arg)));
310
- if (isWgslStruct(callee.value)) {
311
- if (argNodes.length > 1) throw new WgslTypeError("Struct schemas should always be called with at most 1 argument");
312
- if (!argNodes[0]) return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value, "runtime");
313
- const arg = this._typedExpression(argNodes[0], callee.value);
314
- return snip(this.ctx.resolve(arg.value, callee.value).value, callee.value, "runtime");
315
- }
316
- if (isWgslArray(callee.value)) {
317
- if (argNodes.length > 1) throw new WgslTypeError("Array schemas should always be called with at most 1 argument");
318
- if (!argNodes[0]) return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value, "runtime");
319
- const arg = this._typedExpression(argNodes[0], callee.value);
320
- if (arg.value instanceof ArrayExpression) return snip(stitch`${this.ctx.resolve(callee.value).value}(${arg.value.elements})`, arg.dataType, "runtime");
321
- return snip(this.ctx.resolve(arg.value, callee.value).value, callee.value, "runtime");
322
- }
323
- if (callee.value === constant) throw new Error("Constants cannot be defined within TypeGPU function scope. To address this, move the constant definition outside the function scope.");
324
- if (isInfixDispatch(callee.value)) {
325
- if (!argNodes[0]) throw new WgslTypeError(`An infix operator '${getName(callee.value.operator)}' was called without any arguments`);
326
- const lhs = coerceToSnippet(callee.value.lhs);
327
- const rhs = this._expression(argNodes[0]);
328
- return callee.value.operator[$gpuCallable].call(this.ctx, [lhs, rhs]);
329
- }
330
- if ((callee.value === _ref || callee.value === unroll) && argNodes[0]) this.tryMarkModified(argNodes[0]);
331
- if (isGPUCallable(callee.value)) {
332
- const callable = callee.value[$gpuCallable];
333
- const strictSignature = callable.strictSignature;
334
- let convertedArguments;
335
- if (strictSignature) convertedArguments = argNodes.map((arg, i) => {
336
- const argType = strictSignature.argTypes[i];
337
- if (!argType) throw new WgslTypeError(`Call '${stringifyNode(expression)}' is invalid since the function expected fewer arguments`);
338
- return this._typedExpression(arg, argType);
339
- });
340
- else convertedArguments = argNodes.map((arg) => this._expression(arg));
341
- try {
342
- return callable.call(this.ctx, convertedArguments);
343
- } catch (err) {
344
- if (err instanceof ResolutionError) throw err;
345
- throw new ResolutionError(err, [{ toString: () => `fn:${getName(callee.value)}` }]);
346
- }
347
- }
348
- const isGeneric = isGenericFn(callee.value);
349
- if (!isMarkedInternal(callee.value) || isGeneric) {
350
- const slotPairs = isGeneric ? callee.value[$providing]?.pairs ?? [] : [];
351
- const callback = isGeneric ? callee.value[$internal].inner : callee.value;
352
- const shelllessCall = this.ctx.withRenamed(callback, getName(callee.value), () => this.ctx.withSlots(slotPairs, () => {
353
- const args = argNodes.map((arg) => this._expression(arg));
354
- const shellless = this.ctx.shelllessRepo.get(callback, args);
355
- if (!shellless) return;
356
- const converted = args.map((s, idx) => {
357
- const argType = shellless.argTypes[idx];
358
- return tryConvertSnippet(this.ctx, s, argType, false);
359
- });
360
- return this.ctx.withResetIndentLevel(() => {
361
- const snippet = this.ctx.resolve(shellless);
362
- return snip(stitch`${snippet.value}(${converted})`, snippet.dataType, "runtime");
363
- });
364
- }));
365
- if (shelllessCall) return shelllessCall;
366
- }
367
- const maybeMathMethod = Object.getOwnPropertyNames(Math).find((prop) => Math[prop] === callee.value);
368
- if (maybeMathMethod) throw new Error(`Unsupported Math functionality 'Math.${maybeMathMethod}()'. Use an std alternative, or implement the function manually.`);
369
- const maybeConsoleMethod = Object.getOwnPropertyNames(console).find((prop) => console[prop] === callee.value);
370
- if (maybeConsoleMethod) throw new Error(`Unsupported console functionality 'console.${maybeConsoleMethod}()'.`);
371
- throw new Error(`Function '${getName(callee.value) ?? String(callee.value)}' is not marked with the 'use gpu' directive and cannot be used in a shader`);
372
- }
373
- if (expression[0] === NODE.objectExpr) {
374
- const obj = expression[1];
375
- const structType = this.ctx.expectedType;
376
- if (structType instanceof AutoStruct) {
377
- const entries = Object.fromEntries(Object.entries(obj).map(([key, value]) => {
378
- let accessed = structType.accessProp(key);
379
- let expr;
380
- if (accessed) expr = this._typedExpression(value, accessed.type);
381
- else {
382
- expr = this._expression(value);
383
- if (expr.dataType === UnknownData) throw new WgslTypeError(stitch`Property ${key} in object literal has a value of unknown type: '${expr}'`);
384
- accessed = structType.provideProp(key, unptr(concretize(expr.dataType)));
385
- }
386
- return [accessed.prop, expr];
387
- }));
388
- const completeStruct = structType.completeStruct;
389
- const convertedSnippets = convertStructValues(this.ctx, completeStruct, entries);
390
- return snip(stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, completeStruct, "runtime");
391
- }
392
- if (isWgslStruct(structType)) {
393
- const entries = Object.fromEntries(Object.entries(structType.propTypes).map(([key, value]) => {
394
- const val = obj[key];
395
- if (val === void 0) throw new WgslTypeError(`Missing property ${key} in object literal for struct ${structType}`);
396
- return [key, this._typedExpression(val, value)];
397
- }));
398
- const convertedSnippets = convertStructValues(this.ctx, structType, entries);
399
- return snip(stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, "runtime");
400
- }
401
- throw new WgslTypeError(`No target type could be inferred for object '${stringifyNode(expression)}', please wrap the object in the corresponding schema.`);
402
- }
403
- if (expression[0] === NODE.arrayExpr) {
404
- const [_, valueNodes] = expression;
405
- const arrType = this.ctx.expectedType;
406
- let elemType;
407
- let values;
408
- if (isWgslArray(arrType)) {
409
- elemType = arrType.elementType;
410
- values = valueNodes.map((value) => this._typedExpression(value, elemType));
411
- if (values.length !== arrType.elementCount) throw new WgslTypeError(`Cannot create value of type '${arrType}' from an array of length: ${values.length}`);
412
- } else {
413
- const valuesSnippets = valueNodes.map((value) => this._expression(value));
414
- if (valuesSnippets.length === 0) throw new WgslTypeError("Cannot infer the type of an empty array literal.");
415
- const converted = convertToCommonType(this.ctx, valuesSnippets);
416
- if (!converted) throw new WgslTypeError(`Values '${stringifyNode(expression)}' cannot be automatically converted to a common type. Consider wrapping the array in an appropriate schema`);
417
- values = converted;
418
- elemType = concretize(values[0]?.dataType);
419
- }
420
- const arrayType = arrayOf(elemType, values.length);
421
- return snip(new ArrayExpression(arrayType, values), arrayType, "runtime");
422
- }
423
- if (expression[0] === NODE.conditionalExpr) {
424
- const [_, testNode, consequentNode, alternativeNode] = expression;
425
- const test = this._expression(testNode);
426
- if (isKnownAtComptime(test)) return test.value ? this._expression(consequentNode) : this._expression(alternativeNode);
427
- else {
428
- const consequent = this._expression(consequentNode);
429
- const alternative = this._expression(alternativeNode);
430
- const [con, alt] = convertToCommonType(this.ctx, [consequent, alternative], validSelectBranchTypes) ?? [];
431
- if (!con || !alt || consequent.possibleSideEffects || alternative.possibleSideEffects) throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid. For more complex branching, please use 'std.select' or if/else statements.`);
432
- return snip(stitch`select(${alt}, ${con}, ${test})`, con.dataType, "runtime", test.possibleSideEffects);
433
- }
434
- }
435
- if (expression[0] === NODE.stringLiteral) return snip(expression[1], UnknownData, "constant");
436
- if (expression[0] === NODE.preUpdate) throw new Error("Cannot use pre-updates in TypeGPU functions.");
437
- assertExhaustive(expression);
438
- }
439
- declareGlobalConst(options) {
440
- const resolvedDataType = this.ctx.resolve(options.dataType).value;
441
- const resolvedValue = this.ctx.resolveSnippet(options.init).value;
442
- this.ctx.addDeclaration(`const ${options.id}: ${resolvedDataType} = ${resolvedValue};`);
443
- return snip(options.id, options.dataType, "constant-immutable-def");
444
- }
445
- declareGlobalVar(options) {
446
- let pre = "";
447
- if (options.group !== void 0) pre += `@group(${options.group}) `;
448
- if (options.binding !== void 0) pre += `@binding(${options.binding}) `;
449
- if (options.scope in usageToVarTemplateMap) pre += `var<${usageToVarTemplateMap[options.scope]}> `;
450
- else pre += `var `;
451
- pre += `${options.id}: ${this.ctx.resolve(options.dataType).value}`;
452
- this.ctx.addDeclaration(options.init ? `${pre} = ${this.ctx.resolveSnippet(options.init).value};` : `${pre};`);
453
- return snip(options.id, options.dataType, options.scope);
454
- }
455
- functionDefinition(options) {
456
- let body = this._block(options.body);
457
- const scope = this.ctx.topFunctionScope;
458
- invariant(scope, "Expected function scope to be present");
459
- const replacements = Object.fromEntries([...scope.placeholderForVariable.entries()].map(([variable, placeholder]) => [placeholder, scope.modifiedVariables.has(variable) ? "var" : "let"]));
460
- if (Object.keys(replacements).length > 0) {
461
- const regex = new RegExp(Object.keys(replacements).join("|"), "gi");
462
- body = body.replace(regex, (match) => replacements[match] ?? "#ERR");
463
- }
464
- const returnType = options.determineReturnType();
465
- const argList = options.args.filter((arg) => arg.used || options.functionType === "normal").map((arg) => {
466
- return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
467
- }).join(", ");
468
- const head = returnType.type !== "void" ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` : `(${argList}) `;
469
- let attributes = "";
470
- if (options.functionType === "compute") {
471
- if (!options.workgroupSize) throw new Error("Compute shaders must have a workgroup size");
472
- attributes = `@compute @workgroup_size(${options.workgroupSize.join(", ")}) `;
473
- } else if (options.functionType === "vertex") attributes = `@vertex `;
474
- else if (options.functionType === "fragment") attributes = `@fragment `;
475
- return `${attributes}fn ${options.name}${head}${body}`;
476
- }
477
- /**
478
- * Generates a WGSL type string for the given data type, and adds necessary
479
- * definitions to the shader preamble. This shouldn't be called directly, only
480
- * through `ctx.resolve` to properly cache the result.
481
- */
482
- typeAnnotation(data) {
483
- return resolveData(this.ctx, data);
484
- }
485
- typeInstantiation(schema, args) {
486
- if (args.length === 1 && args[0]?.dataType === schema) return snip(stitch`${args[0]}`, schema, fallthroughCopyOrigin(args[0].origin));
487
- return snip(stitch`${this.ctx.resolve(schema).value}(${args})`, schema, "runtime");
488
- }
489
- numericLiteral(value, schema) {
490
- if (!Number.isFinite(value)) throw new Error(`Value '${value}' (${schema.type}) cannot be resolved due to WGSL's Finite Math Assumption (see: https://www.w3.org/TR/WGSL/#finite-math-assumption). This value might be a result of a comptime-evaluated operation.`);
491
- if (schema.type === "abstractInt") return snip(`${value}`, schema, "constant");
492
- if (schema.type === "u32") return snip(`${value}u`, schema, "constant");
493
- if (schema.type === "i32") return snip(`${value}i`, schema, "constant");
494
- const exp = value.toExponential();
495
- const decimal = schema.type === "abstractFloat" && Number.isInteger(value) ? `${value}.` : `${value}`;
496
- const base = exp.length < decimal.length ? exp : decimal;
497
- if (schema.type === "f32") return snip(`${base}f`, schema, "constant");
498
- if (schema.type === "f16") return snip(`${base}h`, schema, "constant");
499
- return snip(base, schema, "constant");
500
- }
501
- _return(statement) {
502
- const returnNode = statement[1];
503
- if (returnNode !== void 0) {
504
- const expectedReturnType = this.ctx.topFunctionReturnType;
505
- let returnSnippet = expectedReturnType ? this._typedExpression(returnNode, expectedReturnType) : this._expression(returnNode);
506
- if (returnSnippet.value instanceof RefOperator) throw new WgslTypeError(`Cannot return '${stringifyNode(returnNode)}' because it is a d.ref`);
507
- if (returnSnippet.origin === "argument" && !isNaturallyEphemeral(returnSnippet.dataType) && this.ctx.topFunctionScope?.functionType === "normal") throw new WgslTypeError(`'${stringifyNode(statement)}' is invalid, cannot return references to arguments. Copy the argument before returning it.`);
508
- if (!expectedReturnType && isAlias(returnSnippet) && !isNaturallyEphemeral(returnSnippet.dataType) && returnSnippet.origin !== "local-def") {
509
- const str = stringifyNode(returnNode);
510
- const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
511
- throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
155
+ /**
156
+ * The block depth that we can expect when generating code in the function scope, not in any nested blocks.
157
+ */
158
+ const functionInitialBlockDepth = 2;
159
+ export class WgslGenerator {
160
+ #ctx = undefined;
161
+ // used to detect `continue` and `break` nodes in loop body, as well as label
162
+ // unrolled blocks with comments
163
+ #unrollingChain = [];
164
+ static {
165
+ WgslGenerator.prototype.languageKey = 'wgsl';
166
+ }
167
+ initGenerator(ctx) {
168
+ if (this.#ctx !== undefined) {
169
+ throw new Error(`Cannot initialize shader generators twice. Create one generator per resolution.`);
170
+ }
171
+ this.#ctx = ctx;
172
+ }
173
+ get ctx() {
174
+ if (!this.#ctx) {
175
+ throw new Error('WGSL Generator has not yet been initialized. Please call initialize(ctx) before using the generator.');
176
+ }
177
+ return this.#ctx;
178
+ }
179
+ _block([_, statementNodes], allowInlining, externalMap) {
180
+ this.ctx.pushBlockScope();
181
+ try {
182
+ if (externalMap) {
183
+ const externals = Object.fromEntries(Object.entries(externalMap).map(([id, value]) => [id, coerceToSnippet(value)]));
184
+ this.ctx.setBlockExternals(externals);
185
+ }
186
+ let body = '';
187
+ /**
188
+ * True if any of the statements in the block define variables that would
189
+ * be scoped to the currently generated block. If not, we can safely inline it.
190
+ */
191
+ let definesInNearestScope = false;
192
+ let endsWithControlFlow;
193
+ this.ctx.indent();
194
+ for (const statementNode of statementNodes) {
195
+ const statement = this._statement(statementNode);
196
+ if (statement.code.length > 0) {
197
+ body += `${statement.code}\n`;
198
+ }
199
+ definesInNearestScope ||= statement.definesInNearestScope ?? false;
200
+ if (statement.endsWithControlFlow !== undefined) {
201
+ endsWithControlFlow = statement.endsWithControlFlow;
202
+ break;
203
+ }
204
+ }
205
+ this.ctx.dedent();
206
+ const willInline = allowInlining && !definesInNearestScope;
207
+ // Omitting the 'return;' at the end of the statement list if
208
+ // the 'return;' would be placed in the function body outside
209
+ // of any nested block.
210
+ if (this.ctx.blockDepth === functionInitialBlockDepth) {
211
+ body = body.replace(/[ ]*return\s*;\s*$/u, '');
212
+ }
213
+ if (body === '') {
214
+ return { code: '', endsWithControlFlow, definesInNearestScope: false };
215
+ }
216
+ if (willInline) {
217
+ return {
218
+ code: this.ctx.getDedented(body.trim()),
219
+ endsWithControlFlow,
220
+ definesInNearestScope,
221
+ };
222
+ }
223
+ return {
224
+ code: `{\n${body}${this.ctx.pre}}`,
225
+ endsWithControlFlow,
226
+ // all defines will be scoped to the newly generated block
227
+ definesInNearestScope: false,
228
+ };
229
+ }
230
+ finally {
231
+ this.ctx.popBlockScope();
232
+ }
233
+ }
234
+ _blockStatement(block, externalMap) {
235
+ const { code, ...properties } = this._block(block, /* allowInlining */ true, externalMap);
236
+ if (code === '') {
237
+ return { ...properties, code: '' };
238
+ }
239
+ return { ...properties, code: `${this.ctx.pre}${code}` };
240
+ }
241
+ refVariable(id, dataType) {
242
+ const varName = this.ctx.makeUniqueIdentifier(id, 'block');
243
+ const ptrType = ptrFn(dataType);
244
+ const snippet = snip(new RefOperator(snip(varName, dataType, 'function', false), ptrType), ptrType, 'function', false);
245
+ this.ctx.defineVariable(id, snippet);
246
+ return varName;
247
+ }
248
+ /**
249
+ * Creates a variable declaration string.
250
+ * `keyword` may be a placeholder filled in later.
251
+ */
252
+ _emitVarDecl(keyword, name, _dataType, rhsStr) {
253
+ return `${this.ctx.pre}${keyword} ${name} = ${rhsStr};`;
254
+ }
255
+ _identifier(id) {
256
+ if (!id) {
257
+ throw new Error('Cannot resolve an empty identifier');
258
+ }
259
+ if (id === 'undefined') {
260
+ return snip(undefined, wgsl.Void, 'constant', false);
261
+ }
262
+ const res = this.ctx.getById(id);
263
+ if (!res) {
264
+ throw new Error(`Identifier ${id} not found`);
265
+ }
266
+ return res;
267
+ }
268
+ _callShellless(callee, args) {
269
+ const isGeneric = isGenericFn(callee);
270
+ const slotPairs = isGeneric ? (callee[$providing]?.pairs ?? []) : [];
271
+ const callback = isGeneric ? callee[$internal].inner : callee;
272
+ const shelllessCall = this.ctx.withRenamed(callback, getName(callee), () => this.ctx.withSlots(slotPairs, () => {
273
+ const shellless = this.ctx.shelllessRepo.get(callback, args);
274
+ if (!shellless) {
275
+ return undefined;
276
+ }
277
+ const converted = args.map((s, idx) => {
278
+ const argType = shellless.argTypes[idx];
279
+ return tryConvertSnippet(this.ctx, s, argType, /* verbose */ false);
280
+ });
281
+ return this.ctx.withResetIndentLevel(() => {
282
+ const snippet = this.ctx.resolve(shellless);
283
+ return snip(stitch `${snippet.value}(${converted})`, snippet.dataType,
284
+ /* origin */ 'runtime');
285
+ });
286
+ }));
287
+ return shelllessCall;
288
+ }
289
+ /**
290
+ * A wrapper for `generateExpression` that updates `ctx.expectedType`
291
+ * and tries to convert the result when it does not match the expected type.
292
+ */
293
+ _typedExpression(expression, expectedType) {
294
+ const prevExpectedType = this.ctx.expectedType;
295
+ this.ctx.expectedType = expectedType;
296
+ try {
297
+ const result = this._expression(expression);
298
+ if (expectedType instanceof AutoStruct) {
299
+ // We provide a certain AutoStruct object to later
300
+ // investigate what props were accessed. No need to
301
+ // convert the result.
302
+ return result;
303
+ }
304
+ return tryConvertSnippet(this.ctx, result, expectedType);
305
+ }
306
+ finally {
307
+ this.ctx.expectedType = prevExpectedType;
308
+ }
309
+ }
310
+ _expression(expression) {
311
+ if (typeof expression === 'string') {
312
+ return this._identifier(expression);
313
+ }
314
+ if (typeof expression === 'boolean') {
315
+ return snip(expression, bool, /* origin */ 'constant', false);
316
+ }
317
+ if (expression[0] === NODE.logicalExpr) {
318
+ const [_, lhs, op, rhs] = expression;
319
+ const lhsExpr = this._expression(lhs);
320
+ // Short Circuit Evaluation
321
+ if (isKnownAtComptime(lhsExpr)) {
322
+ const castToBool = wgsl.isBool(this.ctx.expectedType);
323
+ const evalRhs = op === '&&' ? lhsExpr.value : !lhsExpr.value;
324
+ if (!evalRhs) {
325
+ return castToBool
326
+ ? snip(op === '||', bool, 'constant', false)
327
+ : coerceToSnippet(lhsExpr.value);
328
+ }
329
+ const rhsExpr = this._expression(rhs);
330
+ if (isKnownAtComptime(rhsExpr)) {
331
+ const rhsSnippet = coerceToSnippet(rhsExpr.value);
332
+ return castToBool ? tryConvertSnippet(this.ctx, rhsSnippet, bool, false) : rhsSnippet;
333
+ }
334
+ if (rhsExpr.dataType === UnknownData) {
335
+ throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
336
+ }
337
+ // we can skip lhs
338
+ return castToBool ? tryConvertSnippet(this.ctx, rhsExpr, bool, false) : rhsExpr;
339
+ }
340
+ const rhsExpr = this._expression(rhs);
341
+ // they are not known at comptime
342
+ if (lhsExpr.dataType === UnknownData) {
343
+ throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
344
+ }
345
+ if (!isKnownAtComptime(rhsExpr) && rhsExpr.dataType === UnknownData) {
346
+ throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
347
+ }
348
+ if (!wgsl.isBool(lhsExpr.dataType) || !wgsl.isBool(rhsExpr.dataType)) {
349
+ throw new WgslTypeError(`Logical expression '${op}' requires boolean operands. Got '${String(lhsExpr.dataType)}' and '${String(rhsExpr.dataType)}'.`);
350
+ }
351
+ const lhsStr = this.ctx.resolveSnippet(lhsExpr).value;
352
+ const rhsStr = this.ctx.resolveSnippet(rhsExpr).value;
353
+ // hardcoded parentheses - operators not present in `parenthesizedOps`
354
+ return snip(`(${lhsStr} ${op} ${rhsStr})`, bool, 'runtime', lhsExpr.possibleSideEffects || rhsExpr.possibleSideEffects);
355
+ }
356
+ if (expression[0] === NODE.binaryExpr || expression[0] === NODE.assignmentExpr) {
357
+ // Binary/Assignment Expression
358
+ const [exprType, lhs, op, rhs] = expression;
359
+ const lhsExpr = this._expression(lhs);
360
+ const rhsExpr = this._expression(rhs);
361
+ if (rhsExpr.value instanceof RefOperator) {
362
+ throw new WgslTypeError(stitch `Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
363
+ }
364
+ if (op === '==') {
365
+ throw new Error('Please use the === operator instead of ==');
366
+ }
367
+ if (op === '!=') {
368
+ throw new Error('Please use the !== operator instead of !=');
369
+ }
370
+ const stdBinaryRelationalOp = binaryRelationalOpToStdMap[op];
371
+ if (stdBinaryRelationalOp && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) {
372
+ const left = lhsExpr.value;
373
+ const right = rhsExpr.value;
374
+ switch (op) {
375
+ case '===':
376
+ return snip(left === right, bool, 'constant', false);
377
+ case '!==':
378
+ return snip(left !== right, bool, 'constant', false);
379
+ }
380
+ if (typeof left !== 'number' || typeof right !== 'number') {
381
+ const bothVectors = wgsl.isVec(lhsExpr.dataType) && wgsl.isVec(rhsExpr.dataType);
382
+ throw new WgslTypeError(`Comparison '${op}' requires numeric operands.${bothVectors
383
+ ? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
384
+ : ''}`);
385
+ }
386
+ switch (op) {
387
+ case '<':
388
+ return snip(left < right, bool, 'constant', false);
389
+ case '<=':
390
+ return snip(left <= right, bool, 'constant', false);
391
+ case '>':
392
+ return snip(left > right, bool, 'constant', false);
393
+ case '>=':
394
+ return snip(left >= right, bool, 'constant', false);
395
+ }
396
+ }
397
+ if (lhsExpr.dataType === UnknownData) {
398
+ throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
399
+ }
400
+ if (rhsExpr.dataType === UnknownData) {
401
+ throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
402
+ }
403
+ const codegen = binaryOpCodeToCodegen[op];
404
+ if (codegen) {
405
+ return codegen(this.ctx, [lhsExpr, rhsExpr]);
406
+ }
407
+ let convLhs;
408
+ let convRhs;
409
+ if (bitShiftOps.includes(op)) {
410
+ const lhsDataType = lhsExpr.dataType;
411
+ if (!wgsl.isInteger(lhsDataType) && !wgsl.isIntegerVec(lhsDataType)) {
412
+ throw new WgslTypeError(`Expression: ${stringifyNode(expression)}\nLeft-hand side of '${op}' must be an integer or vector of integers.\nGot ${this.ctx.resolve(lhsDataType).value}.`);
413
+ }
414
+ const lhsPrimitive = wgsl.isVec(lhsDataType) ? lhsDataType.primitive : lhsDataType;
415
+ if (['>>>', '>>>='].includes(op) && lhsPrimitive.type !== 'u32') {
416
+ throw new WgslTypeError(`Expression: ${stringifyNode(expression)}\nLeft-hand side of '${op}' must be an unsigned integer or vector of unsigned integers.\nGot ${this.ctx.resolve(lhsDataType).value}.\nUse ${op.slice(1)} instead.`);
417
+ }
418
+ if (['>>', '>>='].includes(op) && lhsPrimitive.type === 'u32') {
419
+ logger.warn('deprecated', `\nExpression: ${stringifyNode(expression)}\nUsing u32 or vecN<u32> as left-hand side of ${op} is deprecated.\nUse >${op} instead.`);
420
+ }
421
+ // rhs must be u32 (or vecN<u32> for vector lhs) according to the WGSL spec
422
+ let rhsTarget;
423
+ if (wgsl.isVec(lhsDataType)) {
424
+ const cc = lhsDataType.componentCount;
425
+ rhsTarget = cc === 2 ? vec2u : cc === 3 ? vec3u : vec4u;
426
+ }
427
+ else {
428
+ rhsTarget = u32;
429
+ }
430
+ convRhs = tryConvertSnippet(this.ctx, rhsExpr, rhsTarget, false);
431
+ convLhs = lhsExpr;
432
+ }
433
+ else {
434
+ const forcedType = exprType === NODE.assignmentExpr ? [lhsExpr.dataType] : undefined;
435
+ [convLhs, convRhs] = convertToCommonType(this.ctx, [lhsExpr, rhsExpr], forcedType) ?? [
436
+ lhsExpr,
437
+ rhsExpr,
438
+ ];
439
+ }
440
+ const type = operatorToType(convLhs.dataType, op, convRhs.dataType);
441
+ if (exprType === NODE.assignmentExpr) {
442
+ validateSnippetMutation(convLhs, expression);
443
+ this.tryMarkModified(lhs);
444
+ // Compound assignment operators are okay, e.g. +=, -=, *=, /=, ...
445
+ if (op === '=' && isAlias(rhsExpr) && !wgsl.isNaturallyEphemeral(rhsExpr.dataType)) {
446
+ throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(unptr(rhsExpr.dataType)).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
447
+ }
448
+ }
449
+ if (stdBinaryRelationalOp) {
450
+ const equalityCheck = ['===', '!=='].includes(op);
451
+ const correctOperandTypes = (wgsl.isNumericSchema(convLhs.dataType) && wgsl.isNumericSchema(convRhs.dataType)) ||
452
+ (equalityCheck && wgsl.isBool(convLhs.dataType) && wgsl.isBool(convRhs.dataType));
453
+ if (!correctOperandTypes) {
454
+ const bothVectors = wgsl.isVec(convLhs.dataType) && wgsl.isVec(convRhs.dataType);
455
+ throw new WgslTypeError(`Comparison '${op}' requires numeric${equalityCheck ? ' or boolean' : ''} operands. Got '${String(convLhs.dataType)}' and '${String(convRhs.dataType)}'.${bothVectors
456
+ ? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
457
+ : ''}`);
458
+ }
459
+ }
460
+ return snip(this.emitBinaryOp(convLhs, (OP_MAP[op] ?? op), convRhs), type,
461
+ // Result of an operation, so not a reference to anything
462
+ /* origin */ 'runtime', exprType === NODE.assignmentExpr ||
463
+ lhsExpr.possibleSideEffects ||
464
+ rhsExpr.possibleSideEffects);
465
+ }
466
+ if (expression[0] === NODE.postUpdate) {
467
+ throw new Error(`'${stringifyNode(expression)}' is invalid because update is only allowed as a statement.`);
468
+ }
469
+ if (expression[0] === NODE.unaryExpr) {
470
+ // Unary Expression
471
+ const [_, op, arg] = expression;
472
+ const argExpr = this._expression(arg);
473
+ const codegen = unaryOpCodeToCodegen[op];
474
+ if (codegen) {
475
+ return codegen(this.ctx, [argExpr]);
476
+ }
477
+ const argStr = this.ctx.resolveSnippet(argExpr).value;
478
+ const type = operatorToType(argExpr.dataType, op);
479
+ // Result of an operation, so not a reference to anything
480
+ return snip(`${op}${argStr}`, type, /* origin */ 'runtime', argExpr.possibleSideEffects);
481
+ }
482
+ if (expression[0] === NODE.memberAccess) {
483
+ // Member Access
484
+ const [_, targetNode, property] = expression;
485
+ const target = this._expression(targetNode);
486
+ const accessed = accessProp(target, property);
487
+ if (!accessed) {
488
+ throw new Error(`Property '${property}' not found on '${stringifyNode(targetNode)}'`);
489
+ }
490
+ return accessed;
491
+ }
492
+ if (expression[0] === NODE.indexAccess) {
493
+ // Index Access
494
+ const [_, targetNode, propertyNode] = expression;
495
+ const target = this._expression(targetNode);
496
+ const inProperty = this._expression(propertyNode);
497
+ const property = convertToCommonType(this.ctx, [inProperty], [u32, i32], /* verbose */ false)?.[0] ??
498
+ inProperty;
499
+ const accessed = accessIndex(target, property);
500
+ if (!accessed) {
501
+ throw new Error(`Index access '${stringifyNode(expression)}' is invalid. If the value is an array, to address this, consider one of the following approaches: (1) declare the array using 'tgpu.const', (2) store the array in a buffer, or (3) define the array within the GPU function scope.`);
502
+ }
503
+ return accessed;
504
+ }
505
+ if (expression[0] === NODE.numericLiteral) {
506
+ // Numeric Literal
507
+ const type = typeof expression[1] === 'string'
508
+ ? numericLiteralToSnippet(parseNumericString(expression[1]))
509
+ : numericLiteralToSnippet(expression[1]);
510
+ invariant(type, `Expected ${stringifyNode(expression)} to be valid numeric literal`);
511
+ return type;
512
+ }
513
+ if (expression[0] === NODE.call) {
514
+ // Function Call
515
+ const [_, calleeNode, argNodes] = expression;
516
+ const _callee = this._expression(calleeNode);
517
+ const callee = mathToStd.has(_callee.value)
518
+ ? snip(mathToStd.get(_callee.value), UnknownData, 'runtime', _callee.possibleSideEffects)
519
+ : _callee;
520
+ if (supportedLogOps().includes(callee.value)) {
521
+ return this.ctx.generateLog(callee.value, argNodes.map((arg) => this._expression(arg)));
522
+ }
523
+ if (wgsl.isWgslStruct(callee.value)) {
524
+ // Struct schema call.
525
+ if (argNodes.length > 1) {
526
+ throw new WgslTypeError('Struct schemas should always be called with at most 1 argument');
527
+ }
528
+ // No arguments `Struct()`, resolve struct name and return.
529
+ if (!argNodes[0]) {
530
+ // The schema becomes the data type.
531
+ return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value,
532
+ // A new struct, so not a reference.
533
+ /* origin */ 'runtime', false);
534
+ }
535
+ const arg = this._typedExpression(argNodes[0], callee.value);
536
+ // Either `Struct({ x: 1, y: 2 })`, or `Struct(otherStruct)`.
537
+ // In both cases, we just let the argument resolve everything.
538
+ return snip(this.ctx.resolveSnippet(arg).value, callee.value,
539
+ // A new struct, so not a reference.
540
+ /* origin */ 'runtime', arg.possibleSideEffects);
541
+ }
542
+ if (wgsl.isWgslArray(callee.value)) {
543
+ // Array schema call.
544
+ if (argNodes.length > 1) {
545
+ throw new WgslTypeError('Array schemas should always be called with at most 1 argument');
546
+ }
547
+ // No arguments `array<...>()`, resolve array type and return.
548
+ if (!argNodes[0]) {
549
+ // The schema becomes the data type.
550
+ return this.typeInstantiation(callee.value, []);
551
+ }
552
+ const arg = this._typedExpression(argNodes[0], callee.value);
553
+ // `d.arrayOf(...)([...])`.
554
+ // We don't resolve the ArrayExpression object itself to
555
+ // avoid reference checks (we're copying so it's fine)
556
+ if (arg.value instanceof ArrayExpression) {
557
+ return this.typeInstantiation(callee.value, arg.value.elements);
558
+ }
559
+ // `d.arrayOf(...)(otherArr)`.
560
+ // We just let the argument resolve everything.
561
+ return snip(this.ctx.resolveSnippet(arg).value, callee.value,
562
+ // A new array, so not a reference.
563
+ /* origin */ 'runtime', arg.possibleSideEffects);
564
+ }
565
+ if (callee.value === constant) {
566
+ throw new Error('Constants cannot be defined within TypeGPU function scope. To address this, move the constant definition outside the function scope.');
567
+ }
568
+ if (isInfixDispatch(callee.value)) {
569
+ if (!argNodes[0]) {
570
+ throw new WgslTypeError(`An infix operator '${getName(callee.value.operator)}' was called without any arguments`);
571
+ }
572
+ const lhs = coerceToSnippet(callee.value.lhs);
573
+ const rhs = this._expression(argNodes[0]);
574
+ const callable = callee.value.operator[$gpuCallable];
575
+ return callable.call(this.ctx, [lhs, rhs]);
576
+ }
577
+ if ((callee.value === _ref || callee.value === unroll) && argNodes[0]) {
578
+ this.tryMarkModified(argNodes[0]);
579
+ }
580
+ if (isGPUCallable(callee.value)) {
581
+ const callable = callee.value[$gpuCallable];
582
+ const strictSignature = callable.strictSignature;
583
+ let convertedArguments;
584
+ if (strictSignature) {
585
+ // The function's signature does not depend on the context, so it can be used to
586
+ // give a hint to the argument expressions that a specific type is expected.
587
+ convertedArguments = argNodes.map((arg, i) => {
588
+ const argType = strictSignature.argTypes[i];
589
+ if (!argType) {
590
+ throw new WgslTypeError(`Call '${stringifyNode(expression)}' is invalid since the function expected fewer arguments`);
591
+ }
592
+ return this._typedExpression(arg, argType);
593
+ });
594
+ }
595
+ else {
596
+ convertedArguments = argNodes.map((arg) => this._expression(arg));
597
+ }
598
+ try {
599
+ return callable.call(this.ctx, convertedArguments);
600
+ }
601
+ catch (err) {
602
+ if (err instanceof ResolutionError) {
603
+ throw err;
604
+ }
605
+ throw new ResolutionError(err, [
606
+ {
607
+ toString: () => `fn:${getName(callee.value)}`,
608
+ },
609
+ ]);
610
+ }
611
+ }
612
+ if (!isMarkedInternal(callee.value) || isGenericFn(callee.value)) {
613
+ const args = argNodes.map((arg) => this._expression(arg));
614
+ const result = this._callShellless(callee.value, args);
615
+ if (result) {
616
+ return result;
617
+ }
618
+ }
619
+ // try to throw a descriptive error
620
+ const maybeMathMethod = Object.getOwnPropertyNames(Math).find((prop) => Math[prop] === callee.value);
621
+ if (maybeMathMethod) {
622
+ throw new Error(`Unsupported Math functionality 'Math.${maybeMathMethod}()'. Use an std alternative, or implement the function manually.`);
623
+ }
624
+ const maybeConsoleMethod = Object.getOwnPropertyNames(console).find((prop) => console[prop] === callee.value);
625
+ if (maybeConsoleMethod) {
626
+ throw new Error(`Unsupported console functionality 'console.${maybeConsoleMethod}()'.`);
627
+ }
628
+ throw new Error(`Function '${getName(callee.value) ?? String(callee.value)}' is not marked with the 'use gpu' directive and cannot be used in a shader`);
629
+ }
630
+ if (expression[0] === NODE.objectExpr) {
631
+ // Object Literal
632
+ const obj = expression[1];
633
+ const structType = this.ctx.expectedType;
634
+ if (structType instanceof AutoStruct) {
635
+ const entries = Object.fromEntries(Object.entries(obj).map(([key, value]) => {
636
+ let accessed = structType.accessProp(key);
637
+ let expr;
638
+ if (accessed) {
639
+ // Generating the expression expecting a specific type
640
+ expr = this._typedExpression(value, accessed.type);
641
+ }
642
+ else {
643
+ // Generating the expression and inferring the type instead
644
+ expr = this._expression(value);
645
+ if (expr.dataType === UnknownData) {
646
+ throw new WgslTypeError(stitch `Property ${key} in object literal has a value of unknown type: '${expr}'`);
647
+ }
648
+ // Taking care of abstract numerics and implicit pointers
649
+ accessed = structType.provideProp(key, unptr(concretize(expr.dataType)));
650
+ }
651
+ return [accessed.prop, expr];
652
+ }));
653
+ const completeStruct = structType.completeStruct;
654
+ const convertedSnippets = convertStructValues(this.ctx, completeStruct, entries);
655
+ return snip(stitch `${this.ctx.resolve(structType).value}(${convertedSnippets})`, completeStruct,
656
+ /* origin */ 'runtime');
657
+ }
658
+ if (wgsl.isWgslStruct(structType)) {
659
+ const entries = Object.fromEntries(Object.entries(structType.propTypes).map(([key, value]) => {
660
+ const val = obj[key];
661
+ if (val === undefined) {
662
+ throw new WgslTypeError(`Missing property ${key} in object literal for struct ${structType}`);
663
+ }
664
+ const result = this._typedExpression(val, value);
665
+ return [key, result];
666
+ }));
667
+ const convertedSnippets = convertStructValues(this.ctx, structType, entries);
668
+ return snip(stitch `${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType,
669
+ /* origin */ 'runtime', convertedSnippets.some((s) => s.possibleSideEffects));
670
+ }
671
+ throw new WgslTypeError(`No target type could be inferred for object '${stringifyNode(expression)}', please wrap the object in the corresponding schema.`);
672
+ }
673
+ if (expression[0] === NODE.arrayExpr) {
674
+ const [_, valueNodes] = expression;
675
+ // Array Expression
676
+ const arrType = this.ctx.expectedType;
677
+ let elemType;
678
+ let values;
679
+ if (wgsl.isWgslArray(arrType)) {
680
+ elemType = arrType.elementType;
681
+ // The array is typed, so its elements should be as well.
682
+ values = valueNodes.map((value) => this._typedExpression(value, elemType));
683
+ // Since it's an expected type, we enforce the length
684
+ if (values.length !== arrType.elementCount) {
685
+ throw new WgslTypeError(`Cannot create value of type '${arrType}' from an array of length: ${values.length}`);
686
+ }
687
+ }
688
+ else {
689
+ // The array is not typed, so we try to guess the types.
690
+ const valuesSnippets = valueNodes.map((value) => this._expression(value));
691
+ if (valuesSnippets.length === 0) {
692
+ throw new WgslTypeError('Cannot infer the type of an empty array literal.');
693
+ }
694
+ const converted = convertToCommonType(this.ctx, valuesSnippets);
695
+ if (!converted) {
696
+ throw new WgslTypeError(`Values '${stringifyNode(expression)}' cannot be automatically converted to a common type. Consider wrapping the array in an appropriate schema`);
697
+ }
698
+ values = converted;
699
+ elemType = concretize(values[0]?.dataType);
700
+ }
701
+ const arrayType = arrayOf(elemType, values.length);
702
+ return snip(new ArrayExpression(arrayType, values), arrayType,
703
+ /* origin */ 'runtime', values.some((v) => v.possibleSideEffects));
704
+ }
705
+ if (expression[0] === NODE.conditionalExpr) {
706
+ // ternary operator
707
+ const [_, testNode, consequentNode, alternativeNode] = expression;
708
+ const test = this._expression(testNode);
709
+ if (isKnownAtComptime(test)) {
710
+ return test.value ? this._expression(consequentNode) : this._expression(alternativeNode);
711
+ }
712
+ else {
713
+ const convertedTest = tryConvertSnippet(this.ctx, test, bool, false);
714
+ const consequent = this._expression(consequentNode);
715
+ const alternative = this._expression(alternativeNode);
716
+ const [con, alt] = convertToCommonType(this.ctx, [consequent, alternative], validSelectBranchTypes) ?? [];
717
+ if (!con ||
718
+ !alt ||
719
+ consequent.possibleSideEffects ||
720
+ alternative.possibleSideEffects ||
721
+ (isAlias(consequent) && !wgsl.isNaturallyEphemeral(consequent.dataType)) ||
722
+ (isAlias(alternative) && !wgsl.isNaturallyEphemeral(alternative.dataType))) {
723
+ throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid. For more complex branching, please use 'std.select' or if/else statements.`);
724
+ }
725
+ return snip(stitch `select(${alt}, ${con}, ${convertedTest})`, con.dataType, 'runtime',
726
+ // this select has side-effects only if the condition has side-effects
727
+ test.possibleSideEffects);
728
+ }
729
+ }
730
+ if (expression[0] === NODE.stringLiteral) {
731
+ return snip(expression[1], UnknownData, /* origin */ 'constant', false);
732
+ }
733
+ if (expression[0] === NODE.preUpdate) {
734
+ throw new Error('Cannot use pre-updates in TypeGPU functions.');
735
+ }
736
+ assertExhaustive(expression);
737
+ }
738
+ declareGlobalConst(options) {
739
+ const resolvedDataType = this.ctx.resolve(options.dataType).value;
740
+ const resolvedValue = this.ctx.resolveSnippet(options.init).value;
741
+ this.ctx.addDeclaration(`const ${options.id}: ${resolvedDataType} = ${resolvedValue};`, options.id);
742
+ return snip(options.id, options.dataType, 'constant-immutable-def');
743
+ }
744
+ declareGlobalVar(options) {
745
+ let pre = '';
746
+ if (options.group !== undefined) {
747
+ pre += `@group(${options.group}) `;
748
+ }
749
+ if (options.binding !== undefined) {
750
+ pre += `@binding(${options.binding}) `;
751
+ }
752
+ if (options.scope in usageToVarTemplateMap) {
753
+ pre += `var<${usageToVarTemplateMap[options.scope]}> `;
754
+ }
755
+ else {
756
+ pre += `var `;
757
+ }
758
+ pre += `${options.id}: ${this.ctx.resolve(options.dataType).value}`;
759
+ this.ctx.addDeclaration(options.init ? `${pre} = ${this.ctx.resolveSnippet(options.init).value};` : `${pre};`, options.id);
760
+ return snip(options.id, options.dataType, options.scope);
761
+ }
762
+ functionDefinition(options) {
763
+ // Function body
764
+ invariant(this.ctx.blockDepth === functionInitialBlockDepth - 1, `Expecting exactly ${functionInitialBlockDepth - 1} block(s) before going into the first function block scope`);
765
+ let body = this._block(options.body, /* allowInlining */ false);
766
+ const scope = this.ctx.topFunctionScope;
767
+ invariant(scope, 'Expected function scope to be present');
768
+ const replacements = Object.fromEntries([...scope.placeholderForVariable.entries()].map(([variable, placeholder]) => [
769
+ placeholder,
770
+ scope.modifiedVariables.has(variable) ? 'var' : 'let',
771
+ ]));
772
+ if (Object.keys(replacements).length > 0) {
773
+ const regex = new RegExp(Object.keys(replacements).join('|'), 'gi');
774
+ body.code = body.code.replace(regex, (match) => replacements[match] ?? '#ERR');
775
+ }
776
+ // Only after generating the body can we determine the return type
777
+ const returnType = options.determineReturnType();
778
+ const argList = options.args
779
+ // Stripping out unused arguments in entry functions
780
+ .filter((arg) => arg.used || options.functionType === 'normal')
781
+ .map((arg) => {
782
+ return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
783
+ })
784
+ .join(', ');
785
+ const head = returnType.type !== 'void'
786
+ ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} `
787
+ : `(${argList}) `;
788
+ let attributes = '';
789
+ if (options.functionType === 'compute') {
790
+ if (!options.workgroupSize) {
791
+ throw new Error('Compute shaders must have a workgroup size');
792
+ }
793
+ attributes = `@compute @workgroup_size(${options.workgroupSize.join(', ')}) `;
794
+ }
795
+ else if (options.functionType === 'vertex') {
796
+ attributes = `@vertex `;
797
+ }
798
+ else if (options.functionType === 'fragment') {
799
+ attributes = `@fragment `;
800
+ }
801
+ return `${attributes}fn ${options.name}${head}${body.code || '{}'}`;
802
+ }
803
+ /**
804
+ * Generates a WGSL type string for the given data type, and adds necessary
805
+ * definitions to the shader preamble. This shouldn't be called directly, only
806
+ * through `ctx.resolve` to properly cache the result.
807
+ */
808
+ emitTypeAnnotation(data) {
809
+ return resolveData(this.ctx, data);
810
+ }
811
+ typeInstantiation(schema, args) {
812
+ if (args.length === 1 && args[0]?.dataType === schema) {
813
+ // Already of the desired type, e.g. `bool(false)` or `vec3f(vec3f(1, 2, 3))`
814
+ // We can make this snippet ephemeral, as we know it will be deep copied in JS
815
+ return snip(stitch `${args[0]}`, schema, fallthroughCopyOrigin(args[0].origin), args[0].possibleSideEffects);
816
+ }
817
+ // Creating a 'runtime' snippet, since it's instantiating a new value
818
+ return snip(stitch `${this.ctx.resolve(schema).value}(${args})`, schema, 'runtime', args.some((s) => s.possibleSideEffects));
819
+ }
820
+ numericLiteral(value, schema) {
821
+ if (!Number.isFinite(value)) {
822
+ throw new Error(`Value '${value}' (${schema.type}) cannot be resolved due to WGSL's Finite Math Assumption (see: https://www.w3.org/TR/WGSL/#finite-math-assumption). This value might be a result of a comptime-evaluated operation.`);
823
+ }
824
+ if (schema.type === 'abstractInt') {
825
+ return snip(`${value}`, schema, /* origin */ 'constant', false);
826
+ }
827
+ if (schema.type === 'u32') {
828
+ return snip(`${value}u`, schema, /* origin */ 'constant', false);
829
+ }
830
+ if (schema.type === 'i32') {
831
+ return snip(`${value}i`, schema, /* origin */ 'constant', false);
832
+ }
833
+ const exp = value.toExponential();
834
+ const decimal = schema.type === 'abstractFloat' && Number.isInteger(value) ? `${value}.` : `${value}`;
835
+ // Just picking the shorter one
836
+ const base = exp.length < decimal.length ? exp : decimal;
837
+ if (schema.type === 'f32') {
838
+ return snip(`${base}f`, schema, /* origin */ 'constant', false);
839
+ }
840
+ if (schema.type === 'f16') {
841
+ return snip(`${base}h`, schema, /* origin */ 'constant', false);
842
+ }
843
+ return snip(base, schema, /* origin */ 'constant', false);
844
+ }
845
+ emitCall(name, templateParams, args) {
846
+ const resolvedTemplateParams = templateParams
847
+ .map((arg) => this.ctx.resolveSnippet(arg).value)
848
+ .join(', ');
849
+ const resolvedArgs = args.map((arg) => this.ctx.resolveSnippet(arg).value).join(', ');
850
+ if (resolvedTemplateParams.length > 0) {
851
+ return `${name}<${resolvedTemplateParams}>(${resolvedArgs})`;
852
+ }
853
+ return `${name}(${resolvedArgs})`;
854
+ }
855
+ emitBinaryOp(lhs, op, rhs) {
856
+ const lhsStr = this.ctx.resolveSnippet(lhs).value;
857
+ const rhsStr = this.ctx.resolveSnippet(rhs).value;
858
+ return parenthesizedOps.includes(op)
859
+ ? `(${lhsStr} ${op} ${rhsStr})`
860
+ : `${lhsStr} ${op} ${rhsStr}`;
861
+ }
862
+ _return(statement) {
863
+ const returnNode = statement[1];
864
+ if (returnNode !== undefined) {
865
+ const expectedReturnType = this.ctx.topFunctionReturnType;
866
+ let returnSnippet = expectedReturnType
867
+ ? this._typedExpression(returnNode, expectedReturnType)
868
+ : this._expression(returnNode);
869
+ if (returnSnippet.value === undefined && wgsl.isVoid(returnSnippet.dataType)) {
870
+ this.ctx.reportReturnType(wgsl.Void);
871
+ return `${this.ctx.pre}return;`;
872
+ }
873
+ if (returnSnippet.value instanceof RefOperator) {
874
+ throw new WgslTypeError(`Cannot return '${stringifyNode(returnNode)}' because it is a d.ref`);
875
+ }
876
+ // Arguments cannot be returned from functions without copying. A simple example why is:
877
+ // const identity = (x) => {
878
+ // 'use gpu';
879
+ // return x;
880
+ // };
881
+ //
882
+ // const foo = (arg: d.v3f) => {
883
+ // 'use gpu';
884
+ // const marg = identity(arg);
885
+ // marg.x = 1; // 'marg's origin would be 'runtime', so we wouldn't be able to track this misuse.
886
+ // };
887
+ if (returnSnippet.origin === 'argument' &&
888
+ !wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
889
+ // Only restricting this use in non-entry functions, as the function
890
+ // is giving up ownership of all references anyway.
891
+ this.ctx.topFunctionScope?.functionType === 'normal') {
892
+ throw new WgslTypeError(`'${stringifyNode(statement)}' is invalid, cannot return references to arguments. Copy the argument before returning it.`);
893
+ }
894
+ if (
895
+ // The existence of `expectedReturnType` implies a function shell, which in turn implies that the
896
+ // value will be copied on return anyway
897
+ !expectedReturnType &&
898
+ isAlias(returnSnippet) &&
899
+ !wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
900
+ returnSnippet.origin !== 'local-def') {
901
+ const str = stringifyNode(returnNode);
902
+ const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
903
+ throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
512
904
  -----
513
905
  Try 'return ${typeStr}(${str});' instead.
514
906
  -----`);
515
- }
516
- returnSnippet = tryConvertSnippet(this.ctx, returnSnippet, unptr(returnSnippet.dataType), false);
517
- invariant(returnSnippet.dataType !== UnknownData, "Return type should be known");
518
- this.ctx.reportReturnType(returnSnippet.dataType);
519
- return stitch`${this.ctx.pre}return ${returnSnippet};`;
520
- }
521
- return `${this.ctx.pre}return;`;
522
- }
523
- _letStatement(statement) {
524
- const [_, rawId, eqNode] = statement;
525
- if (eqNode === void 0) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
526
- const eq = this._expression(eqNode);
527
- if (eq.value instanceof RefOperator) {
528
- const rhsStr$1 = stringifyNode(eqNode);
529
- throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, cannot initialize 'let' variables with d.ref()
907
+ }
908
+ returnSnippet = tryConvertSnippet(this.ctx, returnSnippet, unptr(returnSnippet.dataType), false);
909
+ invariant(returnSnippet.dataType !== UnknownData, 'Return type should be known');
910
+ this.ctx.reportReturnType(returnSnippet.dataType);
911
+ return stitch `${this.ctx.pre}return ${returnSnippet};`;
912
+ }
913
+ this.ctx.reportReturnType(wgsl.Void);
914
+ return `${this.ctx.pre}return;`;
915
+ }
916
+ _letStatement(statement) {
917
+ const [_, rawId, eqNode] = statement;
918
+ if (eqNode === undefined) {
919
+ throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
920
+ }
921
+ const eq = this._expression(eqNode);
922
+ if (eq.value instanceof RefOperator) {
923
+ const rhsStr = stringifyNode(eqNode);
924
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, cannot initialize 'let' variables with d.ref()
530
925
  -----
531
- - Try 'const ${rawId} = ${rhsStr$1}'.
926
+ - Try 'const ${rawId} = ${rhsStr}'.
532
927
  -----`);
533
- }
534
- const definitionDataType = eq.dataType;
535
- if (definitionDataType === UnknownData) {
536
- const rhsStr$1 = stringifyNode(eqNode);
537
- throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, cannot determine WGSL type of '${rhsStr$1}'
928
+ }
929
+ const definitionDataType = eq.dataType;
930
+ if (definitionDataType === UnknownData) {
931
+ const rhsStr = stringifyNode(eqNode);
932
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'
538
933
  -----
539
- - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let ${rawId} = Schema(${rhsStr$1})'
934
+ - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let ${rawId} = Schema(${rhsStr})'
540
935
  -----`);
541
- }
542
- if (isAlias(eq) && !isNaturallyEphemeral(eq.dataType)) {
543
- const rhsStr$1 = stringifyNode(eqNode);
544
- const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
545
- throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references cannot be assigned to 'let' variable declarations.
936
+ }
937
+ if (isAlias(eq) && !wgsl.isNaturallyEphemeral(eq.dataType)) {
938
+ // `let` declarations cannot store references
939
+ const rhsStr = stringifyNode(eqNode);
940
+ const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
941
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, because references cannot be assigned to 'let' variable declarations.
546
942
  -----
547
- - Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr$1})' if you need to reassign '${rawId}' later
548
- - Try 'const ${rawId} = ${rhsStr$1}' if you won't reassign '${rawId}' later.
943
+ - Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr})' if you need to reassign '${rawId}' later
944
+ - Try 'const ${rawId} = ${rhsStr}' if you won't reassign '${rawId}' later.
549
945
  -----`);
550
- }
551
- const concreteType = concretize(definitionDataType);
552
- const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, "block"), concreteType, "local-def", false);
553
- this.ctx.defineVariable(rawId, snippet);
554
- const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
555
- const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
556
- const scope = this.ctx.topFunctionScope;
557
- invariant(scope, `Expected function scope to be present for ${rawId}`);
558
- const emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
559
- scope.placeholderForVariable.set(snippet, emittedVarType);
560
- return this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr);
561
- }
562
- _constStatement(statement) {
563
- const [_, rawId, eqNode] = statement;
564
- if (eqNode === void 0) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
565
- const eq = this._expression(eqNode);
566
- if (eq.value instanceof RefOperator) {
567
- if (eq.dataType !== UnknownData) throw new WgslTypeError(`Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`);
568
- const refSnippet = eq.value.snippet;
569
- const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
570
- return stitch`${this.ctx.pre}var ${varName} = ${tryConvertSnippet(this.ctx, refSnippet, refSnippet.dataType, false)};`;
571
- }
572
- const rhsNaturallyEphemeral = isNaturallyEphemeral(eq.dataType);
573
- let varOrigin = "local-def";
574
- let varType = "<deferred>";
575
- let definitionDataType = eq.dataType;
576
- if (definitionDataType === UnknownData) {
577
- const rhsStr$1 = stringifyNode(eqNode);
578
- throw new WgslTypeError(`'const ${rawId} = ${rhsStr$1}' is invalid, cannot determine WGSL type of '${rhsStr$1}'
946
+ }
947
+ const concreteType = concretize(definitionDataType);
948
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
949
+ /* origin */ 'local-def', false);
950
+ this.ctx.defineVariable(rawId, snippet);
951
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
952
+ const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
953
+ // Even though the user defined a 'let' (expecting it to be reassigned), the
954
+ // reassignment might happen in a pruned branch, in which case we can generate
955
+ // more optimised code by emitting 'let' or 'const' instead of 'var'.
956
+ const scope = this.ctx.topFunctionScope;
957
+ invariant(scope, `Expected function scope to be present for ${rawId}`);
958
+ const emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
959
+ scope.placeholderForVariable.set(snippet, emittedVarType);
960
+ return {
961
+ code: this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr),
962
+ definesInNearestScope: true,
963
+ };
964
+ }
965
+ _constStatement(statement) {
966
+ const [_, rawId, eqNode] = statement;
967
+ if (eqNode === undefined) {
968
+ throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
969
+ }
970
+ const eq = this._expression(eqNode);
971
+ if (eq.value instanceof RefOperator) {
972
+ // We're assigning a newly created `d.ref()`
973
+ if (eq.dataType !== UnknownData) {
974
+ throw new WgslTypeError(`Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`);
975
+ }
976
+ const refSnippet = eq.value.snippet;
977
+ const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
978
+ return {
979
+ code: stitch `${this.ctx.pre}var ${varName} = ${tryConvertSnippet(this.ctx, refSnippet, refSnippet.dataType, false)};`,
980
+ definesInNearestScope: true,
981
+ };
982
+ }
983
+ const rhsNaturallyEphemeral = wgsl.isNaturallyEphemeral(eq.dataType);
984
+ let varOrigin = 'local-def';
985
+ let varType = '<deferred>';
986
+ let definitionDataType = eq.dataType;
987
+ if (definitionDataType === UnknownData) {
988
+ const rhsStr = stringifyNode(eqNode);
989
+ throw new WgslTypeError(`'const ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'
579
990
  -----
580
- - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const ${rawId} = Schema(${rhsStr$1})'
991
+ - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const ${rawId} = Schema(${rhsStr})'
581
992
  -----`);
582
- }
583
- if (eq.origin === "argument") {
584
- varType = "let";
585
- varOrigin = rhsNaturallyEphemeral ? "local-def" : "argument";
586
- } else if (eq.origin === "constant-immutable-def") {
587
- varType = "const";
588
- varOrigin = "constant-immutable-def";
589
- } else if (eq.origin === "runtime-immutable-def") {
590
- varType = "let";
591
- varOrigin = "runtime-immutable-def";
592
- } else if (rhsNaturallyEphemeral) {
593
- varType = eq.origin === "constant" ? "const" : "let";
594
- varOrigin = "local-def";
595
- } else if (!isAlias(eq)) {
596
- varType = "<deferred>";
597
- varOrigin = "local-def";
598
- } else {
599
- varType = "let";
600
- varOrigin = eq.origin;
601
- if (!isPtr(eq.dataType)) {
602
- const ptrType = createPtrFromOrigin(eq.origin, concretize(eq.dataType));
603
- invariant(ptrType !== void 0, `Creating pointer type from origin ${eq.origin}`);
604
- definitionDataType = ptrType;
605
- }
606
- definitionDataType = implicitFrom(definitionDataType);
607
- this.tryMarkModified(eqNode);
608
- }
609
- const concreteType = concretize(definitionDataType);
610
- const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, "block"), concreteType, varOrigin, false);
611
- this.ctx.defineVariable(rawId, snippet);
612
- const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
613
- const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
614
- let emittedVarType;
615
- if (varType === "<deferred>") {
616
- const scope = this.ctx.topFunctionScope;
617
- invariant(scope, `Expected function scope to be present for ${rawId}`);
618
- emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
619
- scope.placeholderForVariable.set(snippet, emittedVarType);
620
- } else emittedVarType = varType;
621
- return this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr);
622
- }
623
- _statement(statement) {
624
- if (typeof statement === "string") {
625
- const id = this._identifier(statement);
626
- const resolved$1 = id.value && this.ctx.resolve(id.value).value;
627
- return resolved$1 ? `${this.ctx.pre}${resolved$1};` : "";
628
- }
629
- if (typeof statement === "boolean") return `${this.ctx.pre}${statement ? "true" : "false"};`;
630
- if (statement[0] === NODE.return) return this._return(statement);
631
- if (statement[0] === NODE.if) {
632
- const [_, condNode, consNode, altNode] = statement;
633
- const condition = this._typedExpression(condNode, bool);
634
- if (typeof condition.value === "boolean") {
635
- let node = condition.value ? consNode : altNode;
636
- if (node === void 0) return "";
637
- if (!Array.isArray(node)) node = blockifySingleStatement(node);
638
- if (node[0] === NODE.block && node[1].length === 1 && node[1][0][0] === NODE.if) return this._statement(node[1][0]);
639
- if (node[0] === NODE.if) return this._statement(node);
640
- return this._blockStatement(blockifySingleStatement(node));
641
- }
642
- const consequent = this._block(blockifySingleStatement(consNode));
643
- const alternate = !altNode ? void 0 : this._block(blockifySingleStatement(altNode));
644
- if (!alternate) return stitch`${this.ctx.pre}if (${condition}) ${consequent}`;
645
- return stitch`\
646
- ${this.ctx.pre}if (${condition}) ${consequent}
647
- ${this.ctx.pre}else ${alternate}`;
648
- }
649
- if (statement[0] === NODE.let) return this._letStatement(statement);
650
- if (statement[0] === NODE.const) return this._constStatement(statement);
651
- if (statement[0] === NODE.block) return this._blockStatement(statement);
652
- if (statement[0] === NODE.for) {
653
- const [_, init, condition, update, body] = statement;
654
- const prevUnrollingFlag = this.#unrolling;
655
- this.#unrolling = false;
656
- try {
657
- this.ctx.pushBlockScope();
658
- const [initStatement, conditionExpr, updateStatement] = this.ctx.withResetIndentLevel(() => [
659
- init ? this._statement(init) : void 0,
660
- condition ? this._typedExpression(condition, bool) : void 0,
661
- update ? this._statement(update) : void 0
662
- ]);
663
- const initStr = initStatement ? initStatement.slice(0, -1) : "";
664
- const updateStr = updateStatement ? updateStatement.slice(0, -1) : "";
665
- const bodyStr = this._block(blockifySingleStatement(body));
666
- return stitch`${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr}`;
667
- } finally {
668
- this.#unrolling = prevUnrollingFlag;
669
- this.ctx.popBlockScope();
670
- }
671
- }
672
- if (statement[0] === NODE.while) {
673
- const prevUnrollingFlag = this.#unrolling;
674
- this.#unrolling = false;
675
- try {
676
- const [_, condition, body] = statement;
677
- const condSnippet = this._typedExpression(condition, bool);
678
- const conditionStr = this.ctx.resolve(condSnippet.value).value;
679
- const bodyStr = this._block(blockifySingleStatement(body));
680
- return `${this.ctx.pre}while (${conditionStr}) ${bodyStr}`;
681
- } finally {
682
- this.#unrolling = prevUnrollingFlag;
683
- }
684
- }
685
- if (statement[0] === NODE.forOf) {
686
- const [_, loopVar, iterable, body] = statement;
687
- if (loopVar[0] !== NODE.const) throw new WgslTypeError("Only `for (const ... of ... )` loops are supported");
688
- this.tryMarkModified(iterable);
689
- let ctxIndent = false;
690
- const prevUnrollingFlag = this.#unrolling;
691
- try {
692
- this.ctx.pushBlockScope();
693
- const iterableExpr = this._expression(iterable);
694
- const shouldUnroll = iterableExpr.value instanceof UnrollableIterable;
695
- const iterableSnippet = shouldUnroll ? iterableExpr.value.snippet : iterableExpr;
696
- const range = getRangeSnippets(this.ctx, iterableSnippet, shouldUnroll);
697
- const originalLoopVarName = loopVar[1];
698
- const blockified = blockifySingleStatement(body);
699
- if (shouldUnroll) {
700
- if (!isKnownAtComptime(range.end)) throw new Error("Cannot unroll loop. Length of iterable is unknown at comptime.");
701
- this.#unrolling = true;
702
- const length = range.end.value;
703
- if (length === 0) return "";
704
- const { value } = iterableSnippet;
705
- 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")));
706
- const firstElement = elements[0];
707
- if (!isAlias(firstElement) && !isNaturallyEphemeral(firstElement.dataType)) throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
708
- return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { [originalLoopVarName]: e })}`).join("\n");
709
- }
710
- this.#unrolling = false;
711
- const index = this.ctx.makeUniqueIdentifier("i", "block");
712
- const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
713
- let bodyStr = "";
714
- if (isTgpuRange(iterableSnippet.value)) bodyStr = this._block(blockified, { [originalLoopVarName]: snip(index, range.start.dataType, "runtime") });
715
- else {
716
- this.ctx.indent();
717
- ctxIndent = true;
718
- const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, "block");
719
- const elementSnippet = getElementSnippet(iterableSnippet, snip(index, u32, "runtime"));
720
- const loopVarKind = getLoopVarKind(elementSnippet);
721
- const elementType = getElementType(elementSnippet, iterableSnippet);
722
- 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`;
723
- this.ctx.dedent();
724
- bodyStr += `${this.ctx.pre}}`;
725
- ctxIndent = false;
726
- }
727
- return stitch`${forHeaderStr} ${bodyStr.trim()}`;
728
- } finally {
729
- if (ctxIndent) this.ctx.dedent();
730
- this.#unrolling = prevUnrollingFlag;
731
- this.ctx.popBlockScope();
732
- }
733
- }
734
- if (statement[0] === NODE.postUpdate) {
735
- const [_, op, arg] = statement;
736
- const argExpr = this._expression(arg);
737
- const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
738
- validateSnippetMutation(argExpr, statement);
739
- this.tryMarkModified(arg);
740
- return `${this.ctx.pre}${argStr}${op};`;
741
- }
742
- if (statement[0] === NODE.continue) {
743
- if (this.#unrolling) throw new WgslTypeError("Cannot unroll loop containing `continue`");
744
- return `${this.ctx.pre}continue;`;
745
- }
746
- if (statement[0] === NODE.break) {
747
- if (this.#unrolling) throw new WgslTypeError("Cannot unroll loop containing `break`");
748
- return `${this.ctx.pre}break;`;
749
- }
750
- const expr = this._expression(statement);
751
- const resolved = expr.value && this.ctx.resolve(expr.value).value;
752
- return resolved ? `${this.ctx.pre}${resolved};` : "";
753
- }
754
- /**
755
- * Attempts a member access lookup to mark a variable as modified.
756
- * @example
757
- * // given `let a; a = 1;`
758
- * tryMarkModified('a') // `a` is marked in the function scope
759
- *
760
- * // given `const obj; obj.prop = 1;`
761
- * tryMarkModified('obj.prop') // `obj` is marked in the function scope
762
- *
763
- * // given `this.buffer.$;`
764
- * tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it
765
- */
766
- tryMarkModified(expr) {
767
- if (!expr) return;
768
- const maybeObject = extractObject(expr);
769
- if (maybeObject !== void 0) {
770
- const snippet = this.ctx.getById(maybeObject);
771
- const scope = this.ctx.topFunctionScope;
772
- if (snippet && scope && scope.placeholderForVariable.has(snippet)) scope.modifiedVariables.add(snippet);
773
- }
774
- }
775
- };
993
+ }
994
+ if (eq.origin === 'argument') {
995
+ // Arguments are immutable, so we 'let' them be (kill me)
996
+ varType = 'let';
997
+ // When we declare a new variable with a naturally ephemeral value (e.g. a scalar)
998
+ // the variable now loses the restrictions of an argument, and becomes just a regular
999
+ // variable. For vectors and other non-naturally ephemeral values, the restrictions of
1000
+ // arguments are kept.
1001
+ varOrigin = rhsNaturallyEphemeral ? 'local-def' : 'argument';
1002
+ }
1003
+ else if (eq.origin === 'constant-immutable-def') {
1004
+ varType = 'const';
1005
+ varOrigin = 'constant-immutable-def';
1006
+ }
1007
+ else if (eq.origin === 'runtime-immutable-def') {
1008
+ varType = 'let';
1009
+ varOrigin = 'runtime-immutable-def';
1010
+ }
1011
+ else if (rhsNaturallyEphemeral) {
1012
+ varType = eq.origin === 'constant' ? 'const' : 'let';
1013
+ // Constants are also local declarations. We lose some information here, meaning
1014
+ // when we look at a variable's snippet, we cannot tell if it's a constant or not.
1015
+ // This is mostly because we plan to determine this fact later, after all of the
1016
+ // function code has been processed, so at least currently, we lose that info.
1017
+ varOrigin = 'local-def';
1018
+ }
1019
+ else if (!isAlias(eq)) {
1020
+ // Not a reference, but also not naturally ephemeral, so we cannot guarantee it won't be mutated.
1021
+ // We defer the decision for now.
1022
+ varType = '<deferred>';
1023
+ varOrigin = 'local-def';
1024
+ }
1025
+ else {
1026
+ return this._aliasConstStatement(rawId, eqNode, eq);
1027
+ }
1028
+ const concreteType = concretize(definitionDataType);
1029
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
1030
+ /* origin */ varOrigin, false);
1031
+ this.ctx.defineVariable(rawId, snippet);
1032
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
1033
+ const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
1034
+ let emittedVarType;
1035
+ if (varType === '<deferred>') {
1036
+ const scope = this.ctx.topFunctionScope;
1037
+ invariant(scope, `Expected function scope to be present for ${rawId}`);
1038
+ emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
1039
+ scope.placeholderForVariable.set(snippet, emittedVarType);
1040
+ }
1041
+ else {
1042
+ emittedVarType = varType;
1043
+ }
1044
+ return {
1045
+ code: this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr),
1046
+ definesInNearestScope: true,
1047
+ };
1048
+ }
1049
+ /**
1050
+ * Handles `const x = <rhs>;` declarations in which the right-hand side aliases memory
1051
+ * that outlives the expression (a buffer, a local variable, an array element, ...).
1052
+ *
1053
+ * In WGSL we store an *implicit* pointer to that memory, so mutations done through `x`
1054
+ * affect the original. Languages without pointers (e.g. GLSL) override this.
1055
+ */
1056
+ _aliasConstStatement(rawId, eqNode, eq) {
1057
+ // Assigning a reference to a `const` variable means we store the pointer
1058
+ // of the rhs.
1059
+ let definitionDataType = eq.dataType;
1060
+ if (!wgsl.isPtr(definitionDataType)) {
1061
+ const ptrType = createPtrFromOrigin(eq.origin, concretize(definitionDataType));
1062
+ invariant(ptrType !== undefined, `Creating pointer type from origin ${eq.origin}`);
1063
+ definitionDataType = ptrType;
1064
+ }
1065
+ // Making the pointer implicit, meaning the fact it's a pointer isn't
1066
+ // reflected in the JS source code.
1067
+ definitionDataType = implicitFrom(definitionDataType);
1068
+ this.tryMarkModified(eqNode);
1069
+ const concreteType = concretize(definitionDataType);
1070
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
1071
+ // we pass on the origin
1072
+ /* origin */ eq.origin, false);
1073
+ this.ctx.defineVariable(rawId, snippet);
1074
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
1075
+ const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
1076
+ return {
1077
+ code: this._emitVarDecl('let', snippet.value, concreteType, rhsStr),
1078
+ definesInNearestScope: true,
1079
+ };
1080
+ }
1081
+ _statement(statement) {
1082
+ if (typeof statement === 'string') {
1083
+ const id = this._identifier(statement);
1084
+ const resolved = id.value !== undefined && id.value !== null ? this.ctx.resolveSnippet(id).value : '';
1085
+ return { code: resolved ? `${this.ctx.pre}${resolved};` : '', definesInNearestScope: false };
1086
+ }
1087
+ if (typeof statement === 'boolean') {
1088
+ return {
1089
+ code: `${this.ctx.pre}${statement ? 'true' : 'false'};`,
1090
+ definesInNearestScope: false,
1091
+ };
1092
+ }
1093
+ if (statement[0] === NODE.return) {
1094
+ return {
1095
+ code: this._return(statement),
1096
+ endsWithControlFlow: 'return',
1097
+ definesInNearestScope: false,
1098
+ };
1099
+ }
1100
+ if (statement[0] === NODE.if) {
1101
+ const [_, condNode, consNode, altNode] = statement;
1102
+ const condition = this._typedExpression(condNode, bool);
1103
+ if (typeof condition.value === 'boolean') {
1104
+ // the condition is known at comptime
1105
+ let node = condition.value ? consNode : altNode;
1106
+ if (node === undefined) {
1107
+ return { code: '', definesInNearestScope: false };
1108
+ }
1109
+ if (!Array.isArray(node)) {
1110
+ node = blockifySingleStatement(node);
1111
+ }
1112
+ if (node[0] === NODE.block && node[1].length === 1 && node[1][0][0] === NODE.if) {
1113
+ // simplify 'if (true) { if (A) {B} } else {C}' to 'if (A) {B}'
1114
+ return this._statement(node[1][0]);
1115
+ }
1116
+ if (node[0] === NODE.if) {
1117
+ // simplify 'if (false) {A} else if (B) {C}' to 'if (B) {C}'
1118
+ return this._statement(node);
1119
+ }
1120
+ // simplify 'if (true) {A} else {B}' to '{A}'
1121
+ return this._blockStatement(blockifySingleStatement(node));
1122
+ }
1123
+ const consequent = this._block(blockifySingleStatement(consNode), /* allowInlining */ false);
1124
+ const alternate = !altNode
1125
+ ? undefined
1126
+ : this._block(blockifySingleStatement(altNode), /* allowInlining */ false).code;
1127
+ if (!alternate) {
1128
+ return {
1129
+ code: stitch `${this.ctx.pre}if (${condition}) ${consequent.code || '{}'}`,
1130
+ definesInNearestScope: false,
1131
+ };
1132
+ }
1133
+ return {
1134
+ code: stitch `\
1135
+ ${this.ctx.pre}if (${condition}) ${consequent.code || '{}'}
1136
+ ${this.ctx.pre}else ${alternate}`,
1137
+ definesInNearestScope: false,
1138
+ };
1139
+ }
1140
+ if (statement[0] === NODE.let) {
1141
+ return this._letStatement(statement);
1142
+ }
1143
+ if (statement[0] === NODE.const) {
1144
+ return this._constStatement(statement);
1145
+ }
1146
+ if (statement[0] === NODE.block) {
1147
+ return this._blockStatement(statement);
1148
+ }
1149
+ if (statement[0] === NODE.for) {
1150
+ const [_, init, condition, update, body] = statement;
1151
+ const prevUnrollingChain = this.#unrollingChain;
1152
+ this.#unrollingChain = [];
1153
+ try {
1154
+ this.ctx.pushBlockScope();
1155
+ const [initStatement, conditionExpr, updateStatement] = this.ctx.withResetIndentLevel(() => [
1156
+ init ? this._statement(init).code : undefined,
1157
+ condition ? this._typedExpression(condition, bool) : undefined,
1158
+ update ? this._statement(update).code : undefined,
1159
+ ]);
1160
+ const initStr = initStatement ? initStatement.slice(0, -1) : '';
1161
+ const updateStr = updateStatement ? updateStatement.slice(0, -1) : '';
1162
+ const bodyStr = this._block(blockifySingleStatement(body), /* allowInlining */ false).code;
1163
+ return {
1164
+ code: stitch `${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr || '{}'}`,
1165
+ definesInNearestScope: false,
1166
+ };
1167
+ }
1168
+ finally {
1169
+ this.#unrollingChain = prevUnrollingChain;
1170
+ this.ctx.popBlockScope();
1171
+ }
1172
+ }
1173
+ if (statement[0] === NODE.while) {
1174
+ const prevUnrollingChain = this.#unrollingChain;
1175
+ this.#unrollingChain = [];
1176
+ try {
1177
+ const [_, condition, body] = statement;
1178
+ const condSnippet = this._typedExpression(condition, bool);
1179
+ const conditionStr = this.ctx.resolveSnippet(condSnippet).value;
1180
+ const bodyStr = this._block(blockifySingleStatement(body), /* allowInlining */ false).code;
1181
+ return {
1182
+ code: `${this.ctx.pre}while (${conditionStr}) ${bodyStr || '{}'}`,
1183
+ definesInNearestScope: false,
1184
+ };
1185
+ }
1186
+ finally {
1187
+ this.#unrollingChain = prevUnrollingChain;
1188
+ }
1189
+ }
1190
+ if (statement[0] === NODE.forOf) {
1191
+ const [_, loopVar, iterable, body] = statement;
1192
+ if (loopVar[0] !== NODE.const) {
1193
+ throw new WgslTypeError('Only `for (const ... of ... )` loops are supported');
1194
+ }
1195
+ this.tryMarkModified(iterable); // overly-defensive, but let's not tempt fate
1196
+ let ctxIndent = false;
1197
+ const prevUnrollingChain = this.#unrollingChain;
1198
+ try {
1199
+ this.ctx.pushBlockScope();
1200
+ const iterableExpr = this._expression(iterable);
1201
+ const shouldUnroll = iterableExpr.value instanceof UnrollableIterable;
1202
+ const iterableSnippet = shouldUnroll ? iterableExpr.value.snippet : iterableExpr;
1203
+ const range = forOfUtils.getRangeSnippets(this.ctx, iterableSnippet, shouldUnroll);
1204
+ const originalLoopVarName = loopVar[1];
1205
+ const blockified = blockifySingleStatement(body);
1206
+ if (shouldUnroll) {
1207
+ if (!isKnownAtComptime(range.end)) {
1208
+ throw new Error('Cannot unroll loop. Length of iterable is unknown at comptime.');
1209
+ }
1210
+ const length = range.end.value;
1211
+ if (length === 0) {
1212
+ return { code: '', definesInNearestScope: false };
1213
+ }
1214
+ const { value } = iterableSnippet;
1215
+ const elements = isTgpuRange(value)
1216
+ ? value.map((i) => coerceToSnippet(i))
1217
+ : value instanceof ArrayExpression
1218
+ ? value.elements
1219
+ : Array.from({ length }, (_, i) => forOfUtils.getElementSnippet(iterableSnippet, snip(i, u32, 'constant')));
1220
+ const firstElement = elements[0];
1221
+ if (!isAlias(firstElement) && !wgsl.isNaturallyEphemeral(firstElement.dataType)) {
1222
+ throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
1223
+ }
1224
+ let blocksCode = '';
1225
+ let endsWithControlFlow;
1226
+ let definesInNearestScope = false;
1227
+ for (let i = 0; i < elements.length; i++) {
1228
+ const e = elements[i];
1229
+ this.#unrollingChain = [...prevUnrollingChain, i];
1230
+ const resolvedBlock = this._blockStatement(blockified, {
1231
+ [originalLoopVarName]: e,
1232
+ });
1233
+ definesInNearestScope ||= resolvedBlock.definesInNearestScope;
1234
+ blocksCode += `${this.ctx.pre}// unrolled iteration ${this.#unrollingChain.map((idx) => `#${idx}`).join(' / ')}\n${resolvedBlock.code}\n`;
1235
+ if (resolvedBlock.endsWithControlFlow !== undefined) {
1236
+ endsWithControlFlow = resolvedBlock.endsWithControlFlow;
1237
+ break;
1238
+ }
1239
+ }
1240
+ return {
1241
+ code: `${blocksCode}${this.ctx.pre}// ---`,
1242
+ endsWithControlFlow,
1243
+ definesInNearestScope,
1244
+ };
1245
+ }
1246
+ this.#unrollingChain = [];
1247
+ const index = this.ctx.makeUniqueIdentifier('i', 'block');
1248
+ const forHeaderStr = stitch `${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
1249
+ let bodyStr = '';
1250
+ if (isTgpuRange(iterableSnippet.value)) {
1251
+ bodyStr = this._block(blockified, /* allowInlining */ false, {
1252
+ [originalLoopVarName]: snip(index, range.start.dataType, 'runtime', false), // range.start, .end , .step have the same dataType
1253
+ }).code;
1254
+ }
1255
+ else {
1256
+ this.ctx.indent();
1257
+ ctxIndent = true;
1258
+ const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block');
1259
+ const elementSnippet = forOfUtils.getElementSnippet(iterableSnippet, snip(index, u32, 'runtime'));
1260
+ const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet);
1261
+ const elementType = forOfUtils.getElementType(elementSnippet, iterableSnippet);
1262
+ const loopVarDeclStr = stitch `${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`;
1263
+ bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, {
1264
+ [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin, false),
1265
+ }).code}\n`;
1266
+ this.ctx.dedent();
1267
+ bodyStr += `${this.ctx.pre}}`;
1268
+ ctxIndent = false;
1269
+ }
1270
+ return {
1271
+ code: stitch `${forHeaderStr} ${bodyStr.trim() || '{}'}`,
1272
+ definesInNearestScope: false,
1273
+ };
1274
+ }
1275
+ finally {
1276
+ if (ctxIndent) {
1277
+ this.ctx.dedent();
1278
+ }
1279
+ this.#unrollingChain = prevUnrollingChain;
1280
+ this.ctx.popBlockScope();
1281
+ }
1282
+ }
1283
+ if (statement[0] === NODE.postUpdate) {
1284
+ // Post-update statement
1285
+ const [_, op, arg] = statement;
1286
+ const argExpr = this._expression(arg);
1287
+ const argStr = this.ctx.resolveSnippet(argExpr).value;
1288
+ validateSnippetMutation(argExpr, statement);
1289
+ this.tryMarkModified(arg);
1290
+ return { code: `${this.ctx.pre}${argStr}${op};`, definesInNearestScope: false };
1291
+ }
1292
+ if (statement[0] === NODE.continue) {
1293
+ if (this.#unrollingChain.length > 0) {
1294
+ throw new WgslTypeError('Cannot unroll loop containing `continue`');
1295
+ }
1296
+ return {
1297
+ code: `${this.ctx.pre}continue;`,
1298
+ endsWithControlFlow: 'continue',
1299
+ definesInNearestScope: false,
1300
+ };
1301
+ }
1302
+ if (statement[0] === NODE.break) {
1303
+ if (this.#unrollingChain.length > 0) {
1304
+ throw new WgslTypeError('Cannot unroll loop containing `break`');
1305
+ }
1306
+ return {
1307
+ code: `${this.ctx.pre}break;`,
1308
+ endsWithControlFlow: 'break',
1309
+ definesInNearestScope: false,
1310
+ };
1311
+ }
1312
+ const expr = this._expression(statement);
1313
+ const resolved = expr.value !== undefined && expr.value !== null ? this.ctx.resolveSnippet(expr).value : '';
1314
+ return { code: resolved ? `${this.ctx.pre}${resolved};` : '', definesInNearestScope: false };
1315
+ }
1316
+ /**
1317
+ * Attempts a member access lookup to mark a variable as modified.
1318
+ * @example
1319
+ * // given `let a; a = 1;`
1320
+ * tryMarkModified('a') // `a` is marked in the function scope
1321
+ *
1322
+ * // given `const obj; obj.prop = 1;`
1323
+ * tryMarkModified('obj.prop') // `obj` is marked in the function scope
1324
+ *
1325
+ * // given `this.buffer.$;`
1326
+ * tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it
1327
+ */
1328
+ tryMarkModified(expr) {
1329
+ if (!expr) {
1330
+ return;
1331
+ }
1332
+ const maybeObject = extractObject(expr);
1333
+ if (maybeObject !== undefined) {
1334
+ const snippet = this.ctx.getById(maybeObject);
1335
+ const scope = this.ctx.topFunctionScope;
1336
+ if (snippet && scope && scope.placeholderForVariable.has(snippet)) {
1337
+ scope.modifiedVariables.add(snippet);
1338
+ }
1339
+ }
1340
+ }
1341
+ }
776
1342
  function validateSnippetMutation(mutated, expr) {
777
- if (mutated.origin === "constant" || mutated.origin === "constant-immutable-def" || mutated.origin === "runtime-immutable-def") {
778
- 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.`);
779
- throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
780
- }
781
- if (mutated.origin === "uniform") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because uniform buffers cannot be mutated.`);
782
- if (mutated.origin === "readonly") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because readonly buffers cannot be mutated.`);
783
- if (mutated.origin === "argument") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
1343
+ if (mutated.origin === 'constant' ||
1344
+ mutated.origin === 'constant-immutable-def' ||
1345
+ mutated.origin === 'runtime-immutable-def') {
1346
+ if (isKnownAtComptime(mutated)) {
1347
+ 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.`);
1348
+ }
1349
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
1350
+ }
1351
+ if (mutated.origin === 'uniform') {
1352
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because uniform buffers cannot be mutated.`);
1353
+ }
1354
+ if (mutated.origin === 'readonly') {
1355
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because readonly buffers cannot be mutated.`);
1356
+ }
1357
+ if (mutated.origin === 'argument') {
1358
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
1359
+ }
784
1360
  }
785
1361
  function assertExhaustive(value) {
786
- throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
1362
+ throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
787
1363
  }
788
1364
  function parseNumericString(str) {
789
- if (/^0x[0-9a-f]+$/i.test(str)) return Number.parseInt(str);
790
- if (/^0b[01]+$/i.test(str)) return Number.parseInt(str.slice(2), 2);
791
- return Number.parseFloat(str);
1365
+ // Hex literals
1366
+ if (/^0x[0-9a-f]+$/i.test(str)) {
1367
+ return Number.parseInt(str);
1368
+ }
1369
+ // Binary literals
1370
+ if (/^0b[01]+$/i.test(str)) {
1371
+ return Number.parseInt(str.slice(2), 2);
1372
+ }
1373
+ return Number.parseFloat(str);
792
1374
  }
793
1375
  function blockifySingleStatement(statement) {
794
- return typeof statement !== "object" || statement[0] !== NODE.block ? [NODE.block, [statement]] : statement;
1376
+ return typeof statement !== 'object' || statement[0] !== NODE.block
1377
+ ? [NODE.block, [statement]]
1378
+ : statement;
795
1379
  }
796
1380
  function extractObject(expr) {
797
- let object = expr;
798
- while (Array.isArray(object) && (object[0] === NODE.memberAccess || object[0] === NODE.indexAccess)) object = object[1];
799
- if (typeof object === "string") return object;
1381
+ let object = expr;
1382
+ while (Array.isArray(object) &&
1383
+ (object[0] === NODE.memberAccess || object[0] === NODE.indexAccess)) {
1384
+ object = object[1];
1385
+ }
1386
+ if (typeof object === 'string') {
1387
+ return object;
1388
+ }
800
1389
  }
801
- const wgslGenerator = new WgslGenerator();
802
- var wgslGenerator_default = wgslGenerator;
803
-
804
- //#endregion
805
- export { WgslGenerator, wgslGenerator_default as default };