typegpu 0.11.9 → 0.12.1

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 (366) hide show
  1. package/bin.mjs +28 -6
  2. package/builtin.d.ts +51 -53
  3. package/builtin.js +30 -34
  4. package/common/fullScreenTriangle.d.ts +2 -10
  5. package/common/fullScreenTriangle.js +20 -28
  6. package/common/index.d.ts +2 -9
  7. package/common/index.js +3 -12
  8. package/common/writeSoA.d.ts +11 -12
  9. package/common/writeSoA.js +88 -76
  10. package/core/buffer/buffer.d.ts +79 -62
  11. package/core/buffer/buffer.js +283 -205
  12. package/core/buffer/bufferBinding.d.ts +71 -0
  13. package/core/buffer/bufferBinding.js +154 -0
  14. package/core/buffer/bufferUsage.d.ts +14 -45
  15. package/core/buffer/bufferUsage.js +1 -171
  16. package/core/buffer/laidOutBuffer.d.ts +25 -0
  17. package/core/buffer/laidOutBuffer.js +55 -0
  18. package/core/commandEncoder/attachments.d.ts +125 -0
  19. package/core/commandEncoder/attachments.js +29 -0
  20. package/core/commandEncoder/commandEncoder.d.ts +53 -0
  21. package/core/commandEncoder/commandEncoder.js +56 -0
  22. package/core/commandEncoder/computePass.d.ts +58 -0
  23. package/core/commandEncoder/computePass.js +63 -0
  24. package/core/commandEncoder/renderPass.d.ts +121 -0
  25. package/core/commandEncoder/renderPass.js +196 -0
  26. package/core/constant/tgpuConstant.d.ts +26 -26
  27. package/core/constant/tgpuConstant.js +76 -65
  28. package/core/declare/tgpuDeclare.d.ts +3 -6
  29. package/core/declare/tgpuDeclare.js +36 -36
  30. package/core/function/autoIO.d.ts +61 -33
  31. package/core/function/autoIO.js +68 -73
  32. package/core/function/comptime.d.ts +11 -13
  33. package/core/function/comptime.js +60 -52
  34. package/core/function/createCallableSchema.d.ts +16 -0
  35. package/core/function/createCallableSchema.js +50 -40
  36. package/core/function/dualImpl.d.ts +44 -0
  37. package/core/function/dualImpl.js +71 -50
  38. package/core/function/entryInputRouter.d.ts +26 -0
  39. package/core/function/entryInputRouter.js +31 -33
  40. package/core/function/extractArgs.d.ts +38 -0
  41. package/core/function/extractArgs.js +202 -200
  42. package/core/function/fnCore.d.ts +44 -5
  43. package/core/function/fnCore.js +148 -93
  44. package/core/function/fnTypes.d.ts +31 -29
  45. package/core/function/fnTypes.js +1 -0
  46. package/core/function/ioSchema.d.ts +12 -10
  47. package/core/function/ioSchema.js +65 -47
  48. package/core/function/shelllessImpl.d.ts +9 -12
  49. package/core/function/shelllessImpl.js +17 -22
  50. package/core/function/templateUtils.d.ts +2 -0
  51. package/core/function/templateUtils.js +9 -8
  52. package/core/function/tgpuComputeFn.d.ts +26 -28
  53. package/core/function/tgpuComputeFn.js +47 -48
  54. package/core/function/tgpuFn.d.ts +34 -37
  55. package/core/function/tgpuFn.js +179 -153
  56. package/core/function/tgpuFragmentFn.d.ts +52 -53
  57. package/core/function/tgpuFragmentFn.js +63 -56
  58. package/core/function/tgpuVertexFn.d.ts +36 -39
  59. package/core/function/tgpuVertexFn.js +56 -53
  60. package/core/pipeline/computePipeline.d.ts +141 -53
  61. package/core/pipeline/computePipeline.js +291 -225
  62. package/core/pipeline/connectAttachmentToShader.d.ts +4 -0
  63. package/core/pipeline/connectAttachmentToShader.js +25 -22
  64. package/core/pipeline/connectTargetsToShader.d.ts +3 -0
  65. package/core/pipeline/connectTargetsToShader.js +29 -27
  66. package/core/pipeline/drawState.d.ts +52 -0
  67. package/core/pipeline/drawState.js +225 -0
  68. package/core/pipeline/performanceTracker.d.ts +14 -0
  69. package/core/pipeline/performanceTracker.js +34 -0
  70. package/core/pipeline/pipelineUtils.d.ts +18 -0
  71. package/core/pipeline/pipelineUtils.js +54 -25
  72. package/core/pipeline/priors.d.ts +1 -0
  73. package/core/pipeline/priors.js +12 -0
  74. package/core/pipeline/renderPipeline.d.ts +246 -261
  75. package/core/pipeline/renderPipeline.js +487 -480
  76. package/core/pipeline/timeable.d.ts +28 -18
  77. package/core/pipeline/timeable.js +90 -52
  78. package/core/pipeline/typeGuards.d.ts +17 -0
  79. package/core/pipeline/typeGuards.js +52 -22
  80. package/core/pipeline/webgpuLimitations.d.ts +10 -0
  81. package/core/pipeline/webgpuLimitations.js +81 -0
  82. package/core/querySet/querySet.d.ts +26 -21
  83. package/core/querySet/querySet.js +127 -103
  84. package/core/rawCodeSnippet/tgpuRawCodeSnippet.d.ts +13 -18
  85. package/core/rawCodeSnippet/tgpuRawCodeSnippet.js +89 -90
  86. package/core/resolve/externals.d.ts +25 -8
  87. package/core/resolve/externals.js +78 -54
  88. package/core/resolve/namespace.d.ts +22 -25
  89. package/core/resolve/namespace.js +16 -21
  90. package/core/resolve/resolvableString.d.ts +7 -0
  91. package/core/resolve/resolvableString.js +13 -0
  92. package/core/resolve/resolveData.d.ts +10 -0
  93. package/core/resolve/resolveData.js +162 -121
  94. package/core/resolve/stitch.d.ts +8 -0
  95. package/core/resolve/stitch.js +23 -21
  96. package/core/resolve/tgpuResolve.d.ts +55 -53
  97. package/core/resolve/tgpuResolve.js +106 -53
  98. package/core/root/configurableImpl.d.ts +10 -0
  99. package/core/root/configurableImpl.js +17 -19
  100. package/core/root/init.d.ts +76 -38
  101. package/core/root/init.js +346 -455
  102. package/core/root/rootTypes.d.ts +461 -636
  103. package/core/root/rootTypes.js +1 -0
  104. package/core/sampler/sampler.d.ts +50 -31
  105. package/core/sampler/sampler.js +120 -113
  106. package/core/simulate/tgpuSimulate.d.ts +9 -12
  107. package/core/simulate/tgpuSimulate.js +68 -71
  108. package/core/slot/accessor.d.ts +36 -13
  109. package/core/slot/accessor.js +138 -91
  110. package/core/slot/internalSlots.d.ts +2 -0
  111. package/core/slot/internalSlots.js +1 -6
  112. package/core/slot/lazy.d.ts +2 -6
  113. package/core/slot/lazy.js +71 -39
  114. package/core/slot/slot.d.ts +2 -6
  115. package/core/slot/slot.js +45 -38
  116. package/core/slot/slotTypes.d.ts +78 -80
  117. package/core/slot/slotTypes.js +11 -16
  118. package/core/texture/externalTexture.d.ts +20 -11
  119. package/core/texture/externalTexture.js +46 -46
  120. package/core/texture/texture.d.ts +152 -97
  121. package/core/texture/texture.js +407 -314
  122. package/core/texture/textureFormats.d.ts +42 -23
  123. package/core/texture/textureFormats.js +104 -75
  124. package/core/texture/textureProps.d.ts +7 -10
  125. package/core/texture/textureProps.js +1 -0
  126. package/core/texture/textureUtils.d.ts +8 -0
  127. package/core/texture/textureUtils.js +185 -179
  128. package/core/texture/usageExtension.d.ts +21 -18
  129. package/core/texture/usageExtension.js +13 -17
  130. package/core/unroll/tgpuUnroll.d.ts +20 -19
  131. package/core/unroll/tgpuUnroll.js +82 -91
  132. package/core/valueProxyUtils.d.ts +3 -0
  133. package/core/valueProxyUtils.js +52 -41
  134. package/core/variable/tgpuVariable.d.ts +26 -27
  135. package/core/variable/tgpuVariable.js +93 -96
  136. package/core/vertexLayout/connectAttributesToShader.d.ts +9 -0
  137. package/core/vertexLayout/connectAttributesToShader.js +61 -56
  138. package/core/vertexLayout/vertexAttribute.d.ts +21 -17
  139. package/core/vertexLayout/vertexAttribute.js +1 -0
  140. package/core/vertexLayout/vertexLayout.d.ts +24 -18
  141. package/core/vertexLayout/vertexLayout.js +130 -99
  142. package/core/whitespaces.d.ts +2 -0
  143. package/core/whitespaces.js +16 -0
  144. package/data/alignIO.d.ts +7 -0
  145. package/data/alignIO.js +13 -13
  146. package/data/alignmentOf.d.ts +5 -8
  147. package/data/alignmentOf.js +90 -75
  148. package/data/array.d.ts +17 -9
  149. package/data/array.js +57 -40
  150. package/data/atomic.d.ts +2 -7
  151. package/data/atomic.js +26 -23
  152. package/data/attributes.d.ts +19 -21
  153. package/data/attributes.js +192 -132
  154. package/data/autoStruct.d.ts +36 -3
  155. package/data/autoStruct.js +84 -81
  156. package/data/compiledIO.d.ts +5 -0
  157. package/data/compiledIO.js +231 -215
  158. package/data/dataIO.d.ts +13 -11
  159. package/data/dataIO.js +643 -589
  160. package/data/dataTypes.d.ts +83 -65
  161. package/data/dataTypes.js +66 -68
  162. package/data/deepEqual.d.ts +2 -7
  163. package/data/deepEqual.js +80 -55
  164. package/data/disarray.d.ts +6 -10
  165. package/data/disarray.js +52 -46
  166. package/data/float16Conversion.d.ts +4 -0
  167. package/data/float16Conversion.js +7 -0
  168. package/data/generalizeFn.d.ts +25 -0
  169. package/data/generalizeFn.js +89 -0
  170. package/data/getLongestContiguousPrefix.d.ts +4 -8
  171. package/data/getLongestContiguousPrefix.js +6 -11
  172. package/data/index.d.ts +30 -31
  173. package/data/index.js +39 -172
  174. package/data/instanceToSchema.d.ts +24 -27
  175. package/data/instanceToSchema.js +1 -0
  176. package/data/isContiguous.d.ts +4 -8
  177. package/data/isContiguous.js +6 -11
  178. package/data/matrix.d.ts +21 -21
  179. package/data/matrix.js +513 -505
  180. package/data/numberOps.d.ts +5 -0
  181. package/data/numberOps.js +21 -21
  182. package/data/numeric.d.ts +21 -12
  183. package/data/numeric.js +287 -197
  184. package/data/offsetUtils.d.ts +9 -13
  185. package/data/offsetUtils.js +204 -144
  186. package/data/offsets.d.ts +8 -0
  187. package/data/offsets.js +33 -33
  188. package/data/partialIO.d.ts +13 -8
  189. package/data/partialIO.js +124 -113
  190. package/data/ptr.d.ts +12 -12
  191. package/data/ptr.js +42 -44
  192. package/data/ref.d.ts +40 -27
  193. package/data/ref.js +125 -95
  194. package/data/sampler.d.ts +99 -101
  195. package/data/sampler.js +17 -21
  196. package/data/schemaCallWrapper.d.ts +19 -0
  197. package/data/schemaCallWrapper.js +31 -26
  198. package/data/schemaMemoryLayout.d.ts +8 -0
  199. package/data/schemaMemoryLayout.js +213 -185
  200. package/data/sizeOf.d.ts +4 -8
  201. package/data/sizeOf.js +6 -11
  202. package/data/snippet.d.ts +96 -20
  203. package/data/snippet.js +75 -86
  204. package/data/struct.d.ts +4 -7
  205. package/data/struct.js +51 -39
  206. package/data/texture.d.ts +253 -250
  207. package/data/texture.js +238 -200
  208. package/data/unstruct.d.ts +2 -7
  209. package/data/unstruct.js +43 -37
  210. package/data/vector.d.ts +33 -21
  211. package/data/vector.js +234 -221
  212. package/data/vectorImpl.d.ts +231 -0
  213. package/data/vectorImpl.js +563 -515
  214. package/data/vectorOps.d.ts +25 -0
  215. package/data/vectorOps.js +118 -669
  216. package/data/vertexFormatData.d.ts +181 -184
  217. package/data/vertexFormatData.js +104 -106
  218. package/data/wgslTypes.d.ts +665 -600
  219. package/data/wgslTypes.js +220 -198
  220. package/errors.d.ts +50 -24
  221. package/errors.js +146 -126
  222. package/execMode.d.ts +16 -0
  223. package/execMode.js +47 -40
  224. package/extension.d.ts +13 -8
  225. package/extension.js +10 -16
  226. package/getGPUValue.d.ts +1 -0
  227. package/getGPUValue.js +2 -7
  228. package/index.d.ts +28 -68
  229. package/index.js +8 -28
  230. package/indexNamedExports.d.ts +69 -42
  231. package/indexNamedExports.js +25 -0
  232. package/internal.d.ts +16 -0
  233. package/internal.js +12 -0
  234. package/mathUtils.d.ts +5 -0
  235. package/mathUtils.js +7 -11
  236. package/memo.d.ts +11 -0
  237. package/memo.js +20 -22
  238. package/minify.d.ts +5 -0
  239. package/minify.js +93 -0
  240. package/nameUtils.d.ts +29 -0
  241. package/nameUtils.js +427 -394
  242. package/package.json +5 -1
  243. package/resolutionCtx.d.ts +162 -22
  244. package/resolutionCtx.js +911 -629
  245. package/serial/registry.d.ts +15 -0
  246. package/serial/registry.js +106 -0
  247. package/serial/restore.d.ts +49 -0
  248. package/serial/restore.js +68 -0
  249. package/serial/schema.d.ts +58 -0
  250. package/serial/schema.js +209 -0
  251. package/serial/types.d.ts +5 -0
  252. package/serial/types.js +1 -0
  253. package/shared/env.d.ts +9 -0
  254. package/shared/env.js +9 -12
  255. package/shared/generators.d.ts +4 -0
  256. package/shared/generators.js +10 -12
  257. package/shared/meta.d.ts +29 -14
  258. package/shared/meta.js +71 -55
  259. package/shared/normalizeMetadata.d.ts +32 -27
  260. package/shared/normalizeMetadata.js +23 -36
  261. package/shared/repr.d.ts +73 -55
  262. package/shared/repr.js +1 -0
  263. package/shared/soul.d.ts +17 -0
  264. package/shared/soul.js +1 -0
  265. package/shared/stringify.d.ts +2 -0
  266. package/shared/stringify.js +29 -20
  267. package/shared/symbols.d.ts +26 -20
  268. package/shared/symbols.js +79 -79
  269. package/shared/tseynit.d.ts +2 -0
  270. package/shared/tseynit.js +160 -75
  271. package/shared/utilityTypes.d.ts +30 -14
  272. package/shared/utilityTypes.js +2 -6
  273. package/shared/vertexFormat.d.ts +41 -43
  274. package/shared/vertexFormat.js +59 -62
  275. package/std/array.d.ts +2 -7
  276. package/std/array.js +18 -23
  277. package/std/atomic.d.ts +14 -18
  278. package/std/atomic.js +96 -95
  279. package/std/bitcast.d.ts +193 -10
  280. package/std/bitcast.js +309 -39
  281. package/std/boolean.d.ts +19 -33
  282. package/std/boolean.js +296 -292
  283. package/std/copy.d.ts +2 -7
  284. package/std/copy.js +27 -24
  285. package/std/derivative.d.ts +11 -15
  286. package/std/derivative.js +56 -79
  287. package/std/discard.d.ts +1 -6
  288. package/std/discard.js +7 -14
  289. package/std/environment.d.ts +19 -14
  290. package/std/environment.js +71 -51
  291. package/std/extensions.d.ts +2 -8
  292. package/std/extensions.js +10 -11
  293. package/std/index.d.ts +20 -24
  294. package/std/index.js +32 -187
  295. package/std/matrix.d.ts +6 -12
  296. package/std/matrix.js +60 -69
  297. package/std/numeric.d.ts +150 -146
  298. package/std/numeric.js +771 -727
  299. package/std/operators.d.ts +19 -27
  300. package/std/operators.js +195 -194
  301. package/std/packing.d.ts +5 -11
  302. package/std/packing.js +67 -75
  303. package/std/range.d.ts +6 -11
  304. package/std/range.js +48 -36
  305. package/std/subgroup.d.ts +33 -37
  306. package/std/subgroup.js +180 -217
  307. package/std/texture.d.ts +70 -38
  308. package/std/texture.js +176 -172
  309. package/taskQueue.d.ts +6 -0
  310. package/taskQueue.js +30 -0
  311. package/tgpu.d.ts +16 -0
  312. package/tgpu.js +17 -44
  313. package/tgpuBindGroupLayout.d.ts +132 -152
  314. package/tgpuBindGroupLayout.js +346 -268
  315. package/tgpuLogger.d.ts +34 -0
  316. package/tgpuLogger.js +64 -0
  317. package/tgpuUnstable.d.ts +16 -22
  318. package/tgpuUnstable.js +13 -35
  319. package/tgsl/accessIndex.d.ts +2 -0
  320. package/tgsl/accessIndex.js +76 -33
  321. package/tgsl/accessProp.d.ts +2 -0
  322. package/tgsl/accessProp.js +142 -101
  323. package/tgsl/accessStructProp.d.ts +5 -0
  324. package/tgsl/accessStructProp.js +13 -15
  325. package/tgsl/consoleLog/deserializers.d.ts +13 -0
  326. package/tgsl/consoleLog/deserializers.js +119 -103
  327. package/tgsl/consoleLog/logGenerator.d.ts +21 -0
  328. package/tgsl/consoleLog/logGenerator.js +85 -80
  329. package/tgsl/consoleLog/serializers.d.ts +21 -0
  330. package/tgsl/consoleLog/serializers.js +119 -100
  331. package/tgsl/consoleLog/types.d.ts +40 -39
  332. package/tgsl/consoleLog/types.js +1 -0
  333. package/tgsl/conversion.d.ts +33 -0
  334. package/tgsl/conversion.js +271 -216
  335. package/tgsl/forOfUtils.d.ts +15 -0
  336. package/tgsl/forOfUtils.js +85 -70
  337. package/tgsl/generationHelpers.d.ts +22 -39
  338. package/tgsl/generationHelpers.js +85 -58
  339. package/tgsl/infixDispatch.d.ts +70 -0
  340. package/tgsl/infixDispatch.js +48 -46
  341. package/tgsl/jsPolyfills.d.ts +16 -19
  342. package/tgsl/jsPolyfills.js +44 -42
  343. package/tgsl/makeDereferenceable.d.ts +48 -0
  344. package/tgsl/makeDereferenceable.js +86 -0
  345. package/tgsl/makeResolvable.d.ts +22 -0
  346. package/tgsl/makeResolvable.js +33 -0
  347. package/tgsl/shaderGenerator.d.ts +77 -17
  348. package/tgsl/shaderGenerator.js +1 -0
  349. package/tgsl/shellless.d.ts +6 -9
  350. package/tgsl/shellless.js +73 -43
  351. package/tgsl/wgslGenerator.d.ts +66 -59
  352. package/tgsl/wgslGenerator.js +1345 -760
  353. package/types.d.ts +287 -222
  354. package/types.js +61 -44
  355. package/unwrapper.d.ts +31 -26
  356. package/unwrapper.js +1 -0
  357. package/wgslExtensions.d.ts +3 -5
  358. package/wgslExtensions.js +12 -16
  359. package/_virtual/rolldown_runtime.js +0 -13
  360. package/core/buffer/bufferShorthand.d.ts +0 -50
  361. package/core/buffer/bufferShorthand.js +0 -55
  362. package/core/pipeline/applyPipelineState.js +0 -37
  363. package/core/pipeline/limitsOverflow.js +0 -13
  364. package/package.js +0 -5
  365. package/tgsl/shaderGenerator_members.d.ts +0 -33
  366. package/tgsl/shaderGenerator_members.js +0 -12
@@ -1,805 +1,1390 @@
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
+ const allConstant = values.every((value) => value.origin === 'constant');
703
+ return snip(new ArrayExpression(arrayType, values), arrayType,
704
+ /* origin */ allConstant ? 'constant' : 'runtime', values.some((v) => v.possibleSideEffects));
705
+ }
706
+ if (expression[0] === NODE.conditionalExpr) {
707
+ // ternary operator
708
+ const [_, testNode, consequentNode, alternativeNode] = expression;
709
+ const test = this._expression(testNode);
710
+ if (isKnownAtComptime(test)) {
711
+ return test.value ? this._expression(consequentNode) : this._expression(alternativeNode);
712
+ }
713
+ else {
714
+ const convertedTest = tryConvertSnippet(this.ctx, test, bool, false);
715
+ const consequent = this._expression(consequentNode);
716
+ const alternative = this._expression(alternativeNode);
717
+ const [con, alt] = convertToCommonType(this.ctx, [consequent, alternative], validSelectBranchTypes) ?? [];
718
+ if (!con ||
719
+ !alt ||
720
+ consequent.possibleSideEffects ||
721
+ alternative.possibleSideEffects ||
722
+ (isAlias(consequent) && !wgsl.isNaturallyEphemeral(consequent.dataType)) ||
723
+ (isAlias(alternative) && !wgsl.isNaturallyEphemeral(alternative.dataType))) {
724
+ throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid. For more complex branching, please use 'std.select' or if/else statements.`);
725
+ }
726
+ return snip(stitch `select(${alt}, ${con}, ${convertedTest})`, con.dataType, 'runtime',
727
+ // this select has side-effects only if the condition has side-effects
728
+ test.possibleSideEffects);
729
+ }
730
+ }
731
+ if (expression[0] === NODE.stringLiteral) {
732
+ return snip(expression[1], UnknownData, /* origin */ 'constant', false);
733
+ }
734
+ if (expression[0] === NODE.preUpdate) {
735
+ throw new Error('Cannot use pre-updates in TypeGPU functions.');
736
+ }
737
+ assertExhaustive(expression);
738
+ }
739
+ declareGlobalConst(options) {
740
+ const resolvedDataType = this.ctx.resolve(options.dataType).value;
741
+ const resolvedValue = this.ctx.resolveSnippet(options.init).value;
742
+ this.ctx.addDeclaration(`const ${options.id}: ${resolvedDataType} = ${resolvedValue};`, options.id);
743
+ return snip(options.id, options.dataType, 'constant-immutable-def');
744
+ }
745
+ declareGlobalVar(options) {
746
+ let pre = '';
747
+ if (options.group !== undefined) {
748
+ pre += `@group(${options.group}) `;
749
+ }
750
+ if (options.binding !== undefined) {
751
+ pre += `@binding(${options.binding}) `;
752
+ }
753
+ if (options.scope in usageToVarTemplateMap) {
754
+ pre += `var<${usageToVarTemplateMap[options.scope]}> `;
755
+ }
756
+ else {
757
+ pre += `var `;
758
+ }
759
+ pre += `${options.id}: ${this.ctx.resolve(options.dataType).value}`;
760
+ this.ctx.addDeclaration(options.init ? `${pre} = ${this.ctx.resolveSnippet(options.init).value};` : `${pre};`, options.id);
761
+ return snip(options.id, options.dataType, options.scope);
762
+ }
763
+ functionDefinition(options) {
764
+ // Function body
765
+ invariant(this.ctx.blockDepth === functionInitialBlockDepth - 1, `Expecting exactly ${functionInitialBlockDepth - 1} block(s) before going into the first function block scope`);
766
+ let body = this._block(options.body, /* allowInlining */ false);
767
+ const scope = this.ctx.topFunctionScope;
768
+ invariant(scope, 'Expected function scope to be present');
769
+ const replacements = Object.fromEntries([...scope.placeholderForVariable.entries()].map(([variable, placeholder]) => [
770
+ placeholder,
771
+ scope.modifiedVariables.has(variable) ? 'var' : 'let',
772
+ ]));
773
+ if (Object.keys(replacements).length > 0) {
774
+ const regex = new RegExp(Object.keys(replacements).join('|'), 'gi');
775
+ body.code = body.code.replace(regex, (match) => replacements[match] ?? '#ERR');
776
+ }
777
+ // Only after generating the body can we determine the return type
778
+ const returnType = options.determineReturnType();
779
+ const argList = options.args
780
+ // Stripping out unused arguments in entry functions
781
+ .filter((arg) => arg.used || options.functionType === 'normal')
782
+ .map((arg) => {
783
+ return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
784
+ })
785
+ .join(', ');
786
+ const head = returnType.type !== 'void'
787
+ ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} `
788
+ : `(${argList}) `;
789
+ let attributes = '';
790
+ if (options.functionType === 'compute') {
791
+ if (!options.workgroupSize) {
792
+ throw new Error('Compute shaders must have a workgroup size');
793
+ }
794
+ attributes = `@compute @workgroup_size(${options.workgroupSize.join(', ')}) `;
795
+ }
796
+ else if (options.functionType === 'vertex') {
797
+ attributes = `@vertex `;
798
+ }
799
+ else if (options.functionType === 'fragment') {
800
+ attributes = `@fragment `;
801
+ }
802
+ return `${attributes}fn ${options.name}${head}${body.code || '{}'}`;
803
+ }
804
+ /**
805
+ * Generates a WGSL type string for the given data type, and adds necessary
806
+ * definitions to the shader preamble. This shouldn't be called directly, only
807
+ * through `ctx.resolve` to properly cache the result.
808
+ */
809
+ emitTypeAnnotation(data) {
810
+ return resolveData(this.ctx, data);
811
+ }
812
+ typeInstantiation(schema, args) {
813
+ if (args.length === 1 && args[0]?.dataType === schema) {
814
+ // Already of the desired type, e.g. `bool(false)` or `vec3f(vec3f(1, 2, 3))`
815
+ // We can make this snippet ephemeral, as we know it will be deep copied in JS
816
+ return snip(stitch `${args[0]}`, schema, fallthroughCopyOrigin(args[0].origin), args[0].possibleSideEffects);
817
+ }
818
+ // Creating a 'runtime' snippet, since it's instantiating a new value
819
+ return snip(stitch `${this.ctx.resolve(schema).value}(${args})`, schema, 'runtime', args.some((s) => s.possibleSideEffects));
820
+ }
821
+ numericLiteral(value, schema) {
822
+ if (!Number.isFinite(value)) {
823
+ 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.`);
824
+ }
825
+ if (schema.type === 'abstractInt') {
826
+ return snip(`${value}`, schema, /* origin */ 'constant', false);
827
+ }
828
+ if (schema.type === 'u32') {
829
+ return snip(`${value}u`, schema, /* origin */ 'constant', false);
830
+ }
831
+ if (schema.type === 'i32') {
832
+ return snip(`${value}i`, schema, /* origin */ 'constant', false);
833
+ }
834
+ const exp = value.toExponential();
835
+ const decimal = schema.type === 'abstractFloat' && Number.isInteger(value) ? `${value}.` : `${value}`;
836
+ // Just picking the shorter one
837
+ const base = exp.length < decimal.length ? exp : decimal;
838
+ if (schema.type === 'f32') {
839
+ return snip(`${base}f`, schema, /* origin */ 'constant', false);
840
+ }
841
+ if (schema.type === 'f16') {
842
+ return snip(`${base}h`, schema, /* origin */ 'constant', false);
843
+ }
844
+ return snip(base, schema, /* origin */ 'constant', false);
845
+ }
846
+ emitCall(name, templateParams, args) {
847
+ const resolvedTemplateParams = templateParams
848
+ .map((arg) => this.ctx.resolveSnippet(arg).value)
849
+ .join(', ');
850
+ const resolvedArgs = args.map((arg) => this.ctx.resolveSnippet(arg).value).join(', ');
851
+ if (resolvedTemplateParams.length > 0) {
852
+ return `${name}<${resolvedTemplateParams}>(${resolvedArgs})`;
853
+ }
854
+ return `${name}(${resolvedArgs})`;
855
+ }
856
+ emitBinaryOp(lhs, op, rhs) {
857
+ const lhsStr = this.ctx.resolveSnippet(lhs).value;
858
+ const rhsStr = this.ctx.resolveSnippet(rhs).value;
859
+ return parenthesizedOps.includes(op)
860
+ ? `(${lhsStr} ${op} ${rhsStr})`
861
+ : `${lhsStr} ${op} ${rhsStr}`;
862
+ }
863
+ _return(statement) {
864
+ const returnNode = statement[1];
865
+ if (returnNode !== undefined) {
866
+ const expectedReturnType = this.ctx.topFunctionReturnType;
867
+ let returnSnippet = expectedReturnType
868
+ ? this._typedExpression(returnNode, expectedReturnType)
869
+ : this._expression(returnNode);
870
+ if (returnSnippet.value === undefined && wgsl.isVoid(returnSnippet.dataType)) {
871
+ this.ctx.reportReturnType(wgsl.Void);
872
+ return `${this.ctx.pre}return;`;
873
+ }
874
+ if (returnSnippet.value instanceof RefOperator) {
875
+ throw new WgslTypeError(`Cannot return '${stringifyNode(returnNode)}' because it is a d.ref`);
876
+ }
877
+ // Arguments cannot be returned from functions without copying. A simple example why is:
878
+ // const identity = (x) => {
879
+ // 'use gpu';
880
+ // return x;
881
+ // };
882
+ //
883
+ // const foo = (arg: d.v3f) => {
884
+ // 'use gpu';
885
+ // const marg = identity(arg);
886
+ // marg.x = 1; // 'marg's origin would be 'runtime', so we wouldn't be able to track this misuse.
887
+ // };
888
+ if (returnSnippet.origin === 'argument' &&
889
+ !wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
890
+ // Only restricting this use in non-entry functions, as the function
891
+ // is giving up ownership of all references anyway.
892
+ this.ctx.topFunctionScope?.functionType === 'normal') {
893
+ throw new WgslTypeError(`'${stringifyNode(statement)}' is invalid, cannot return references to arguments. Copy the argument before returning it.`);
894
+ }
895
+ if (
896
+ // The existence of `expectedReturnType` implies a function shell, which in turn implies that the
897
+ // value will be copied on return anyway
898
+ !expectedReturnType &&
899
+ isAlias(returnSnippet) &&
900
+ !wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
901
+ returnSnippet.origin !== 'local-def') {
902
+ const str = stringifyNode(returnNode);
903
+ const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
904
+ throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
512
905
  -----
513
906
  Try 'return ${typeStr}(${str});' instead.
514
907
  -----`);
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()
908
+ }
909
+ returnSnippet = tryConvertSnippet(this.ctx, returnSnippet, unptr(returnSnippet.dataType), false);
910
+ invariant(returnSnippet.dataType !== UnknownData, 'Return type should be known');
911
+ this.ctx.reportReturnType(returnSnippet.dataType);
912
+ return stitch `${this.ctx.pre}return ${returnSnippet};`;
913
+ }
914
+ this.ctx.reportReturnType(wgsl.Void);
915
+ return `${this.ctx.pre}return;`;
916
+ }
917
+ _letStatement(statement) {
918
+ const [_, rawId, eqNode] = statement;
919
+ if (eqNode === undefined) {
920
+ throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
921
+ }
922
+ const eq = this._expression(eqNode);
923
+ if (eq.value instanceof RefOperator) {
924
+ const rhsStr = stringifyNode(eqNode);
925
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, cannot initialize 'let' variables with d.ref()
530
926
  -----
531
- - Try 'const ${rawId} = ${rhsStr$1}'.
927
+ - Try 'const ${rawId} = ${rhsStr}'.
532
928
  -----`);
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}'
929
+ }
930
+ const definitionDataType = eq.dataType;
931
+ if (definitionDataType === UnknownData) {
932
+ const rhsStr = stringifyNode(eqNode);
933
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'
538
934
  -----
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})'
935
+ - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let ${rawId} = Schema(${rhsStr})'
540
936
  -----`);
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.
937
+ }
938
+ if (isAlias(eq) && !wgsl.isNaturallyEphemeral(eq.dataType)) {
939
+ // `let` declarations cannot store references
940
+ const rhsStr = stringifyNode(eqNode);
941
+ const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
942
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, because references cannot be assigned to 'let' variable declarations.
546
943
  -----
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.
944
+ - Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr})' if you need to reassign '${rawId}' later
945
+ - Try 'const ${rawId} = ${rhsStr}' if you won't reassign '${rawId}' later.
549
946
  -----`);
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}'
947
+ }
948
+ const concreteType = concretize(definitionDataType);
949
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
950
+ /* origin */ 'local-def', false);
951
+ this.ctx.defineVariable(rawId, snippet);
952
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
953
+ const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
954
+ // Even though the user defined a 'let' (expecting it to be reassigned), the
955
+ // reassignment might happen in a pruned branch, in which case we can generate
956
+ // more optimised code by emitting 'let' or 'const' instead of 'var'.
957
+ const scope = this.ctx.topFunctionScope;
958
+ invariant(scope, `Expected function scope to be present for ${rawId}`);
959
+ const emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
960
+ scope.placeholderForVariable.set(snippet, emittedVarType);
961
+ return {
962
+ code: this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr),
963
+ definesInNearestScope: true,
964
+ };
965
+ }
966
+ _constStatement(statement) {
967
+ const [_, rawId, eqNode] = statement;
968
+ if (eqNode === undefined) {
969
+ throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
970
+ }
971
+ const eq = this._expression(eqNode);
972
+ if (eq.value instanceof RefOperator) {
973
+ // We're assigning a newly created `d.ref()`
974
+ if (eq.dataType !== UnknownData) {
975
+ throw new WgslTypeError(`Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`);
976
+ }
977
+ const refSnippet = eq.value.snippet;
978
+ const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
979
+ return {
980
+ code: stitch `${this.ctx.pre}var ${varName} = ${tryConvertSnippet(this.ctx, refSnippet, refSnippet.dataType, false)};`,
981
+ definesInNearestScope: true,
982
+ };
983
+ }
984
+ const rhsNaturallyEphemeral = wgsl.isNaturallyEphemeral(eq.dataType);
985
+ let varOrigin = 'local-def';
986
+ let varType = '<deferred>';
987
+ let definitionDataType = eq.dataType;
988
+ if (definitionDataType === UnknownData) {
989
+ const rhsStr = stringifyNode(eqNode);
990
+ throw new WgslTypeError(`'const ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'
579
991
  -----
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})'
992
+ - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const ${rawId} = Schema(${rhsStr})'
581
993
  -----`);
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
- };
994
+ }
995
+ if (eq.origin === 'argument') {
996
+ // Arguments are immutable, so we 'let' them be (kill me)
997
+ varType = 'let';
998
+ // When we declare a new variable with a naturally ephemeral value (e.g. a scalar)
999
+ // the variable now loses the restrictions of an argument, and becomes just a regular
1000
+ // variable. For vectors and other non-naturally ephemeral values, the restrictions of
1001
+ // arguments are kept.
1002
+ varOrigin = rhsNaturallyEphemeral ? 'local-def' : 'argument';
1003
+ }
1004
+ else if (eq.origin === 'constant-immutable-def') {
1005
+ varType = 'const';
1006
+ varOrigin = 'constant-immutable-def';
1007
+ }
1008
+ else if (eq.origin === 'runtime-immutable-def') {
1009
+ varType = 'let';
1010
+ varOrigin = 'runtime-immutable-def';
1011
+ }
1012
+ else if (rhsNaturallyEphemeral) {
1013
+ varType = eq.origin === 'constant' ? 'const' : 'let';
1014
+ // Constants are also local declarations. We lose some information here, meaning
1015
+ // when we look at a variable's snippet, we cannot tell if it's a constant or not.
1016
+ // This is mostly because we plan to determine this fact later, after all of the
1017
+ // function code has been processed, so at least currently, we lose that info.
1018
+ varOrigin = 'local-def';
1019
+ }
1020
+ else if (!isAlias(eq)) {
1021
+ // Not a reference, but also not naturally ephemeral, so we cannot guarantee it won't be mutated.
1022
+ // We defer the decision for now.
1023
+ varType = '<deferred>';
1024
+ varOrigin = 'local-def';
1025
+ }
1026
+ else {
1027
+ return this._aliasConstStatement(rawId, eqNode, eq);
1028
+ }
1029
+ const concreteType = concretize(definitionDataType);
1030
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
1031
+ /* origin */ varOrigin, false);
1032
+ this.ctx.defineVariable(rawId, snippet);
1033
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
1034
+ const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
1035
+ let emittedVarType;
1036
+ if (varType === '<deferred>') {
1037
+ const scope = this.ctx.topFunctionScope;
1038
+ invariant(scope, `Expected function scope to be present for ${rawId}`);
1039
+ emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
1040
+ scope.placeholderForVariable.set(snippet, emittedVarType);
1041
+ }
1042
+ else {
1043
+ emittedVarType = varType;
1044
+ }
1045
+ return {
1046
+ code: this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr),
1047
+ definesInNearestScope: true,
1048
+ };
1049
+ }
1050
+ /**
1051
+ * Handles `const x = <rhs>;` declarations in which the right-hand side aliases memory
1052
+ * that outlives the expression (a buffer, a local variable, an array element, ...).
1053
+ *
1054
+ * In WGSL we store an *implicit* pointer to that memory, so mutations done through `x`
1055
+ * affect the original. Languages without pointers (e.g. GLSL) override this.
1056
+ */
1057
+ _aliasConstStatement(rawId, eqNode, eq) {
1058
+ // Assigning a reference to a `const` variable means we store the pointer
1059
+ // of the rhs.
1060
+ let definitionDataType = eq.dataType;
1061
+ if (!wgsl.isPtr(definitionDataType)) {
1062
+ const ptrType = createPtrFromOrigin(eq.origin, concretize(definitionDataType));
1063
+ invariant(ptrType !== undefined, `Creating pointer type from origin ${eq.origin}`);
1064
+ definitionDataType = ptrType;
1065
+ }
1066
+ // Making the pointer implicit, meaning the fact it's a pointer isn't
1067
+ // reflected in the JS source code.
1068
+ definitionDataType = implicitFrom(definitionDataType);
1069
+ this.tryMarkModified(eqNode);
1070
+ const concreteType = concretize(definitionDataType);
1071
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
1072
+ // we pass on the origin
1073
+ /* origin */ eq.origin, false);
1074
+ this.ctx.defineVariable(rawId, snippet);
1075
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
1076
+ const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
1077
+ return {
1078
+ code: this._emitVarDecl('let', snippet.value, concreteType, rhsStr),
1079
+ definesInNearestScope: true,
1080
+ };
1081
+ }
1082
+ _statement(statement) {
1083
+ if (typeof statement === 'string') {
1084
+ const id = this._identifier(statement);
1085
+ const resolved = id.value !== undefined && id.value !== null ? this.ctx.resolveSnippet(id).value : '';
1086
+ return { code: resolved ? `${this.ctx.pre}${resolved};` : '', definesInNearestScope: false };
1087
+ }
1088
+ if (typeof statement === 'boolean') {
1089
+ return {
1090
+ code: `${this.ctx.pre}${statement ? 'true' : 'false'};`,
1091
+ definesInNearestScope: false,
1092
+ };
1093
+ }
1094
+ if (statement[0] === NODE.return) {
1095
+ return {
1096
+ code: this._return(statement),
1097
+ endsWithControlFlow: 'return',
1098
+ definesInNearestScope: false,
1099
+ };
1100
+ }
1101
+ if (statement[0] === NODE.if) {
1102
+ const [_, condNode, consNode, altNode] = statement;
1103
+ const condition = this._typedExpression(condNode, bool);
1104
+ if (typeof condition.value === 'boolean') {
1105
+ // the condition is known at comptime
1106
+ let node = condition.value ? consNode : altNode;
1107
+ if (node === undefined) {
1108
+ return { code: '', definesInNearestScope: false };
1109
+ }
1110
+ if (!Array.isArray(node)) {
1111
+ node = blockifySingleStatement(node);
1112
+ }
1113
+ if (node[0] === NODE.block && node[1].length === 1 && node[1][0][0] === NODE.if) {
1114
+ // simplify 'if (true) { if (A) {B} } else {C}' to 'if (A) {B}'
1115
+ return this._statement(node[1][0]);
1116
+ }
1117
+ if (node[0] === NODE.if) {
1118
+ // simplify 'if (false) {A} else if (B) {C}' to 'if (B) {C}'
1119
+ return this._statement(node);
1120
+ }
1121
+ // simplify 'if (true) {A} else {B}' to '{A}'
1122
+ return this._blockStatement(blockifySingleStatement(node));
1123
+ }
1124
+ const consequent = this._block(blockifySingleStatement(consNode), /* allowInlining */ false);
1125
+ const alternate = !altNode
1126
+ ? undefined
1127
+ : this._block(blockifySingleStatement(altNode), /* allowInlining */ false).code;
1128
+ if (!alternate) {
1129
+ return {
1130
+ code: stitch `${this.ctx.pre}if (${condition}) ${consequent.code || '{}'}`,
1131
+ definesInNearestScope: false,
1132
+ };
1133
+ }
1134
+ return {
1135
+ code: stitch `\
1136
+ ${this.ctx.pre}if (${condition}) ${consequent.code || '{}'}
1137
+ ${this.ctx.pre}else ${alternate}`,
1138
+ definesInNearestScope: false,
1139
+ };
1140
+ }
1141
+ if (statement[0] === NODE.let) {
1142
+ return this._letStatement(statement);
1143
+ }
1144
+ if (statement[0] === NODE.const) {
1145
+ return this._constStatement(statement);
1146
+ }
1147
+ if (statement[0] === NODE.block) {
1148
+ return this._blockStatement(statement);
1149
+ }
1150
+ if (statement[0] === NODE.for) {
1151
+ const [_, init, condition, update, body] = statement;
1152
+ const prevUnrollingChain = this.#unrollingChain;
1153
+ this.#unrollingChain = [];
1154
+ try {
1155
+ this.ctx.pushBlockScope();
1156
+ const [initStatement, conditionExpr, updateStatement] = this.ctx.withResetIndentLevel(() => [
1157
+ init ? this._statement(init).code : undefined,
1158
+ condition ? this._typedExpression(condition, bool) : undefined,
1159
+ update ? this._statement(update).code : undefined,
1160
+ ]);
1161
+ const initStr = initStatement ? initStatement.slice(0, -1) : '';
1162
+ const updateStr = updateStatement ? updateStatement.slice(0, -1) : '';
1163
+ const bodyStr = this._block(blockifySingleStatement(body), /* allowInlining */ false).code;
1164
+ return {
1165
+ code: stitch `${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr || '{}'}`,
1166
+ definesInNearestScope: false,
1167
+ };
1168
+ }
1169
+ finally {
1170
+ this.#unrollingChain = prevUnrollingChain;
1171
+ this.ctx.popBlockScope();
1172
+ }
1173
+ }
1174
+ if (statement[0] === NODE.while) {
1175
+ const prevUnrollingChain = this.#unrollingChain;
1176
+ this.#unrollingChain = [];
1177
+ try {
1178
+ const [_, condition, body] = statement;
1179
+ const condSnippet = this._typedExpression(condition, bool);
1180
+ const conditionStr = this.ctx.resolveSnippet(condSnippet).value;
1181
+ const bodyStr = this._block(blockifySingleStatement(body), /* allowInlining */ false).code;
1182
+ return {
1183
+ code: `${this.ctx.pre}while (${conditionStr}) ${bodyStr || '{}'}`,
1184
+ definesInNearestScope: false,
1185
+ };
1186
+ }
1187
+ finally {
1188
+ this.#unrollingChain = prevUnrollingChain;
1189
+ }
1190
+ }
1191
+ if (statement[0] === NODE.forOf) {
1192
+ const [_, loopVar, iterable, body] = statement;
1193
+ if (loopVar[0] !== NODE.const) {
1194
+ throw new WgslTypeError('Only `for (const ... of ... )` loops are supported');
1195
+ }
1196
+ this.tryMarkModified(iterable); // overly-defensive, but let's not tempt fate
1197
+ let ctxIndent = false;
1198
+ const prevUnrollingChain = this.#unrollingChain;
1199
+ try {
1200
+ this.ctx.pushBlockScope();
1201
+ const iterableExpr = this._expression(iterable);
1202
+ const shouldUnroll = iterableExpr.value instanceof UnrollableIterable;
1203
+ const iterableSnippet = shouldUnroll ? iterableExpr.value.snippet : iterableExpr;
1204
+ const range = forOfUtils.getRangeSnippets(this.ctx, iterableSnippet, shouldUnroll);
1205
+ const originalLoopVarName = loopVar[1];
1206
+ const blockified = blockifySingleStatement(body);
1207
+ if (shouldUnroll) {
1208
+ if (!isKnownAtComptime(range.end)) {
1209
+ throw new Error('Cannot unroll loop. Length of iterable is unknown at comptime.');
1210
+ }
1211
+ const length = range.end.value;
1212
+ if (length === 0) {
1213
+ return { code: '', definesInNearestScope: false };
1214
+ }
1215
+ const { value } = iterableSnippet;
1216
+ const elements = isTgpuRange(value)
1217
+ ? value.map((i) => coerceToSnippet(i))
1218
+ : value instanceof ArrayExpression
1219
+ ? value.elements
1220
+ : Array.from({ length }, (_, i) => forOfUtils.getElementSnippet(iterableSnippet, snip(i, u32, 'constant')));
1221
+ const firstElement = elements[0];
1222
+ if (!isAlias(firstElement) && !wgsl.isNaturallyEphemeral(firstElement.dataType)) {
1223
+ throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
1224
+ }
1225
+ let blocksCode = '';
1226
+ let endsWithControlFlow;
1227
+ let definesInNearestScope = false;
1228
+ for (let i = 0; i < elements.length; i++) {
1229
+ const e = elements[i];
1230
+ this.#unrollingChain = [...prevUnrollingChain, i];
1231
+ const resolvedBlock = this._blockStatement(blockified, {
1232
+ [originalLoopVarName]: e,
1233
+ });
1234
+ definesInNearestScope ||= resolvedBlock.definesInNearestScope;
1235
+ blocksCode += `${this.ctx.pre}// unrolled iteration ${this.#unrollingChain.map((idx) => `#${idx}`).join(' / ')}\n${resolvedBlock.code}\n`;
1236
+ if (resolvedBlock.endsWithControlFlow !== undefined) {
1237
+ endsWithControlFlow = resolvedBlock.endsWithControlFlow;
1238
+ break;
1239
+ }
1240
+ }
1241
+ return {
1242
+ code: `${blocksCode}${this.ctx.pre}// ---`,
1243
+ endsWithControlFlow,
1244
+ definesInNearestScope,
1245
+ };
1246
+ }
1247
+ this.#unrollingChain = [];
1248
+ const index = this.ctx.makeUniqueIdentifier('i', 'block');
1249
+ const forHeaderStr = stitch `${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
1250
+ let bodyStr = '';
1251
+ if (isTgpuRange(iterableSnippet.value)) {
1252
+ bodyStr = this._block(blockified, /* allowInlining */ false, {
1253
+ [originalLoopVarName]: snip(index, range.start.dataType, 'runtime', false), // range.start, .end , .step have the same dataType
1254
+ }).code;
1255
+ }
1256
+ else {
1257
+ this.ctx.indent();
1258
+ ctxIndent = true;
1259
+ const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block');
1260
+ const elementSnippet = forOfUtils.getElementSnippet(iterableSnippet, snip(index, u32, 'runtime'));
1261
+ const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet);
1262
+ const elementType = forOfUtils.getElementType(elementSnippet, iterableSnippet);
1263
+ const loopVarDeclStr = stitch `${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`;
1264
+ bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, {
1265
+ [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin, false),
1266
+ }).code}\n`;
1267
+ this.ctx.dedent();
1268
+ bodyStr += `${this.ctx.pre}}`;
1269
+ ctxIndent = false;
1270
+ }
1271
+ return {
1272
+ code: stitch `${forHeaderStr} ${bodyStr.trim() || '{}'}`,
1273
+ definesInNearestScope: false,
1274
+ };
1275
+ }
1276
+ finally {
1277
+ if (ctxIndent) {
1278
+ this.ctx.dedent();
1279
+ }
1280
+ this.#unrollingChain = prevUnrollingChain;
1281
+ this.ctx.popBlockScope();
1282
+ }
1283
+ }
1284
+ if (statement[0] === NODE.postUpdate) {
1285
+ // Post-update statement
1286
+ const [_, op, arg] = statement;
1287
+ const argExpr = this._expression(arg);
1288
+ const argStr = this.ctx.resolveSnippet(argExpr).value;
1289
+ validateSnippetMutation(argExpr, statement);
1290
+ this.tryMarkModified(arg);
1291
+ return { code: `${this.ctx.pre}${argStr}${op};`, definesInNearestScope: false };
1292
+ }
1293
+ if (statement[0] === NODE.continue) {
1294
+ if (this.#unrollingChain.length > 0) {
1295
+ throw new WgslTypeError('Cannot unroll loop containing `continue`');
1296
+ }
1297
+ return {
1298
+ code: `${this.ctx.pre}continue;`,
1299
+ endsWithControlFlow: 'continue',
1300
+ definesInNearestScope: false,
1301
+ };
1302
+ }
1303
+ if (statement[0] === NODE.break) {
1304
+ if (this.#unrollingChain.length > 0) {
1305
+ throw new WgslTypeError('Cannot unroll loop containing `break`');
1306
+ }
1307
+ return {
1308
+ code: `${this.ctx.pre}break;`,
1309
+ endsWithControlFlow: 'break',
1310
+ definesInNearestScope: false,
1311
+ };
1312
+ }
1313
+ const expr = this._expression(statement);
1314
+ const resolved = expr.value !== undefined && expr.value !== null ? this.ctx.resolveSnippet(expr).value : '';
1315
+ return { code: resolved ? `${this.ctx.pre}${resolved};` : '', definesInNearestScope: false };
1316
+ }
1317
+ /**
1318
+ * Attempts a member access lookup to mark a variable as modified.
1319
+ * @example
1320
+ * // given `let a; a = 1;`
1321
+ * tryMarkModified('a') // `a` is marked in the function scope
1322
+ *
1323
+ * // given `const obj; obj.prop = 1;`
1324
+ * tryMarkModified('obj.prop') // `obj` is marked in the function scope
1325
+ *
1326
+ * // given `this.buffer.$;`
1327
+ * tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it
1328
+ */
1329
+ tryMarkModified(expr) {
1330
+ if (!expr) {
1331
+ return;
1332
+ }
1333
+ const maybeObject = extractObject(expr);
1334
+ if (maybeObject !== undefined) {
1335
+ const snippet = this.ctx.getById(maybeObject);
1336
+ const scope = this.ctx.topFunctionScope;
1337
+ if (snippet && scope && scope.placeholderForVariable.has(snippet)) {
1338
+ scope.modifiedVariables.add(snippet);
1339
+ }
1340
+ }
1341
+ }
1342
+ }
776
1343
  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.`);
1344
+ if (mutated.origin === 'constant' ||
1345
+ mutated.origin === 'constant-immutable-def' ||
1346
+ mutated.origin === 'runtime-immutable-def') {
1347
+ if (isKnownAtComptime(mutated)) {
1348
+ 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.`);
1349
+ }
1350
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
1351
+ }
1352
+ if (mutated.origin === 'uniform') {
1353
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because uniform buffers cannot be mutated.`);
1354
+ }
1355
+ if (mutated.origin === 'readonly') {
1356
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because readonly buffers cannot be mutated.`);
1357
+ }
1358
+ if (mutated.origin === 'argument') {
1359
+ throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
1360
+ }
784
1361
  }
785
1362
  function assertExhaustive(value) {
786
- throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
1363
+ throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
787
1364
  }
788
1365
  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);
1366
+ // Hex literals
1367
+ if (/^0x[0-9a-f]+$/i.test(str)) {
1368
+ return Number.parseInt(str);
1369
+ }
1370
+ // Binary literals
1371
+ if (/^0b[01]+$/i.test(str)) {
1372
+ return Number.parseInt(str.slice(2), 2);
1373
+ }
1374
+ return Number.parseFloat(str);
792
1375
  }
793
1376
  function blockifySingleStatement(statement) {
794
- return typeof statement !== "object" || statement[0] !== NODE.block ? [NODE.block, [statement]] : statement;
1377
+ return typeof statement !== 'object' || statement[0] !== NODE.block
1378
+ ? [NODE.block, [statement]]
1379
+ : statement;
795
1380
  }
796
1381
  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;
1382
+ let object = expr;
1383
+ while (Array.isArray(object) &&
1384
+ (object[0] === NODE.memberAccess || object[0] === NODE.indexAccess)) {
1385
+ object = object[1];
1386
+ }
1387
+ if (typeof object === 'string') {
1388
+ return object;
1389
+ }
800
1390
  }
801
- const wgslGenerator = new WgslGenerator();
802
- var wgslGenerator_default = wgslGenerator;
803
-
804
- //#endregion
805
- export { WgslGenerator, wgslGenerator_default as default };