@woosh/meep-engine 3.16.0 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  3. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  4. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  7. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  10. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  11. package/editor/view/node-graph/NodeGraphView.js +84 -8
  12. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  15. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  16. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  17. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  20. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  22. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  25. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  27. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  28. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  29. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  30. package/editor/view/particles/effect/particle-editor.css +123 -35
  31. package/package.json +1 -1
  32. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  34. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  35. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  37. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  38. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  39. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  40. package/src/core/graph/csr/CSRGraph.js +14 -6
  41. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  44. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  47. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  50. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  53. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  55. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  56. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  57. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  59. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  62. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  65. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  68. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  70. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  71. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  74. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  76. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  77. package/src/engine/animation/AnimationUtils.js +246 -246
  78. package/src/engine/animation/Animations.js +74 -74
  79. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  80. package/src/engine/ecs/EventType.d.ts +0 -1
  81. package/src/engine/ecs/EventType.js +1 -26
  82. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  85. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  86. package/src/engine/ecs/parent/EntityNode.js +4 -9
  87. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  89. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  93. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  95. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  96. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  97. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  98. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  99. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  100. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  101. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  102. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  103. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  104. package/src/engine/graphics3/LightSystem.js +4 -8
  105. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  106. package/src/engine/graphics3/MeshSystem.js +9 -19
  107. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  109. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  111. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  112. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  113. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  114. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  115. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  116. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  117. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  118. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  119. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  120. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  121. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  122. package/src/shade/playground/particle_editor/README.md +43 -2
  123. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  124. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  125. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  126. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  127. package/src/shade/renderer/Renderer.js +13 -1
  128. package/src/shade/renderer/particles/DESIGN.md +104 -19
  129. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  130. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  131. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  132. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  133. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  134. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  135. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  136. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  137. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  138. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  139. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  140. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  141. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  142. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  143. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  144. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  145. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  146. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  147. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  148. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  149. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  151. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  153. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  154. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  155. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  157. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  158. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  159. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  160. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  161. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  162. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  163. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  164. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  165. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  166. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  168. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  169. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  170. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  171. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  172. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  174. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  175. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  176. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  177. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  178. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  179. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  180. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  181. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  182. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  183. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  185. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  186. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  187. package/src/shade/renderer/particles/graph_particles.js +105 -262
  188. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  189. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  190. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  191. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  192. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  193. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  194. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  196. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  197. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  198. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  199. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  200. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  202. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  203. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  205. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  206. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  207. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  208. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  209. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  210. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  211. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  212. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  213. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  214. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  215. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  216. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  217. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  218. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  219. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  220. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  221. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  222. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  223. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  224. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  225. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  226. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  227. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  228. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  229. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  230. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  235. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  236. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  237. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  238. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  239. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  240. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  241. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  242. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  243. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  244. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  245. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  246. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  247. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  248. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  249. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  250. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  251. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  252. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  253. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  254. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  255. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  256. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  257. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  258. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  259. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  261. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  262. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  263. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  264. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  265. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  266. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  267. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  268. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  270. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  271. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  272. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  273. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  274. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  275. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  276. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  277. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  279. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  280. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  281. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  282. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  283. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  285. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  286. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  287. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  288. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  289. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  291. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +26 -0
  292. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +1 -1
  293. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  294. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  295. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  296. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  297. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  298. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  299. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  300. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  302. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  303. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  305. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  306. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  307. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  308. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  309. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  310. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  311. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  312. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  314. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  315. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  317. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  318. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  320. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  321. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  323. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  324. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  325. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  326. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  327. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  328. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  329. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  330. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  331. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  332. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  333. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  334. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  335. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  336. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  337. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  338. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  339. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  340. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  341. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  342. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  343. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  344. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  345. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  346. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  347. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  348. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  349. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  350. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  351. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  352. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  353. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  354. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  355. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  356. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  357. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  374. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  375. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  376. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  377. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  378. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  379. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  380. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  381. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  382. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  383. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  384. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  385. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  386. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  387. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  388. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  389. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  390. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  391. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  392. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  393. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  394. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  395. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  396. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  397. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  398. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  399. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  400. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  401. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  402. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  403. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  404. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  405. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  406. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  407. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  408. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  409. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  410. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -1,647 +1,764 @@
1
- import { CSRGraph } from "../../../../core/graph/csr/CSRGraph.js";
2
- import { PARTICLE_VM_REGISTER_SLOTS } from "../ParticleConstants.js";
3
- import {
4
- VM_DEST,
5
- VM_ISA,
6
- VM_OP,
7
- VM_OPERAND,
8
- vm_decode_op,
9
- vm_decode_width,
10
- vm_dest_width,
11
- vm_operation_width,
12
- vm_visit_register_operands,
13
- } from "../isa/ParticleVMISA.js";
14
- import { vm_semantics_of, vm_trim_swizzle, vm_trimmed_swizzle_span } from "./particle_vm_semantics.js";
15
-
16
- /**
17
- * The analysis every optimizer pass reads: instructions decoded into the operand runs they touch,
18
- * the def-use graph over those runs, and per-lane liveness.
19
- *
20
- * Everything here is derived, never authoritative — the instruction words are. A pass rewrites
21
- * words and the next one re-derives, which is why there is no incremental update path and no
22
- * mutation of an analysis in place: the programs are at most
23
- * {@link ../ParticleConstants.js PARTICLE_VM_MAX_INSTRUCTIONS} instructions over
24
- * {@link PARTICLE_VM_REGISTER_SLOTS} slots, and re-deriving all of it is cheaper than keeping it
25
- * honest.
26
- *
27
- * ## Lanes, not registers
28
- *
29
- * A register is one `f32` slot and an operand of width `w` is a RUN of `w` of them, so nothing here
30
- * is per-register: a value's producer is per (instruction, lane), and so is its liveness. The rules
31
- * for how far an operand reaches are irregular — reductions write one lane, `SCALE` and `CURVE`
32
- * have a scalar source, a `SWIZZLE` reads as far as its pattern names, `STORE_ATTR` and `KILL` keep
33
- * a *source* in the destination slot — and they are not restated here:
34
- * {@link vm_visit_register_operands} is the one place that knows them.
35
- */
36
-
37
- /**
38
- * @typedef {object} ParticleOperandRun
39
- * @property {number} operand which operand this is: `-1` for the source that `STORE_ATTR` and
40
- * `KILL` keep in the destination slot, otherwise the source slot `0..2`.
41
- * @property {number} base first register slot
42
- * @property {number} span consecutive slots the operand reaches
43
- */
44
-
45
- /**
46
- * @typedef {object} ParticleDecodedInstruction
47
- * @property {number} index position in the stream
48
- * @property {number[]} words the five instruction words
49
- * @property {number} op
50
- * @property {number} width the encoded width field
51
- * @property {number} operation_width lanes the operation spans, after the ISA's fixed-width rules
52
- * @property {object} meta the {@link VM_ISA} entry
53
- * @property {import("./particle_vm_semantics.js").ParticleOpSemantics} semantics
54
- * @property {ParticleOperandRun|null} write destination run, or null when the opcode writes nothing
55
- * @property {ParticleOperandRun[]} reads every register run the instruction reads
56
- */
57
-
58
- /**
59
- * Decode one instruction into the runs of register slots it reads and writes.
60
- *
61
- * @param {ArrayLike<number>} words the five words of a single instruction
62
- * @param {number} [index]
63
- * @returns {ParticleDecodedInstruction}
64
- */
65
- export function decode_particle_instruction(words, index = 0) {
66
- const word0 = words[0];
67
- const op = vm_decode_op(word0);
68
- const width = vm_decode_width(word0);
69
- const meta = VM_ISA.get(op);
70
-
71
- if (meta === undefined) {
72
- throw new Error(`particle optimizer: unknown opcode ${op} at instruction ${index}`);
73
- }
74
-
75
- // The visitor yields the destination slot first (when the opcode uses one) and then the REG
76
- // source slots in order, which is what lets the runs be matched back up to operand positions
77
- // without restating how far any of them reaches.
78
- /** @type {Array<{base: number, span: number}>} */
79
- const visited = [];
80
-
81
- vm_visit_register_operands(words, 0, (base, span) => visited.push({ base, span }));
82
-
83
- /** @type {number[]} source slots that carry a register, in the order the visitor yields them */
84
- const register_slots = [];
85
-
86
- for (let slot = 0; slot < 3; slot++) {
87
- if (meta.operands[slot] === VM_OPERAND.REG) {
88
- register_slots.push(slot);
89
- }
90
- }
91
-
92
- let cursor = 0;
93
- let write = null;
94
- /** @type {ParticleOperandRun[]} */
95
- const reads = [];
96
-
97
- if (meta.dst) {
98
- const run = visited[cursor++];
99
-
100
- if (meta.dest === VM_DEST.NONE) {
101
- // STORE_ATTR and KILL: the destination slot holds a source, and the run is what it reads.
102
- reads.push({ operand: -1, base: run.base, span: run.span });
103
- } else {
104
- write = { operand: -1, base: run.base, span: run.span };
105
- }
106
- }
107
-
108
- for (const slot of register_slots) {
109
- const run = visited[cursor++];
110
-
111
- reads.push({ operand: slot, base: run.base, span: run.span });
112
- }
113
-
114
- return {
115
- index,
116
- words: [words[0], words[1], words[2], words[3], words[4]],
117
- op,
118
- width,
119
- operation_width: vm_operation_width(op, width),
120
- meta,
121
- semantics: vm_semantics_of(op),
122
- write,
123
- reads,
124
- };
125
- }
126
-
127
- /**
128
- * @param {ArrayLike<ArrayLike<number>>} instructions five-word tuples
129
- * @returns {ParticleDecodedInstruction[]}
130
- */
131
- export function decode_particle_instructions(instructions) {
132
- const out = new Array(instructions.length);
133
-
134
- for (let i = 0; i < instructions.length; i++) {
135
- out[i] = decode_particle_instruction(instructions[i], i);
136
- }
137
-
138
- return out;
139
- }
140
-
141
- /**
142
- * Whether an instruction's operands satisfy the rule {@link ../isa/ParticleProgram.js} enforces:
143
- * two runs of one instruction name the same slots or disjoint ones, never a partial overlap.
144
- *
145
- * Every executor reads slot `c` of a source to write slot `c` of the destination, so an exact alias
146
- * is fine and a shifted one would read slots the same instruction has already written. A pass that
147
- * moves a base or changes a width can turn a legal pair into an illegal one, so each of them checks
148
- * its candidate here before keeping it — cheaper than discovering it in `validate()` downstream, and
149
- * it keeps the rule stated once.
150
- *
151
- * @param {ArrayLike<number>} words the five words of a single instruction
152
- * @returns {boolean}
153
- */
154
- export function particle_instruction_is_well_formed(words) {
155
- const decoded = decode_particle_instruction(words);
156
- /** @type {ParticleOperandRun[]} */
157
- const runs = decoded.write === null ? decoded.reads : [decoded.write, ...decoded.reads];
158
-
159
- for (const run of runs) {
160
- if (run.base < 0 || run.base + run.span > PARTICLE_VM_REGISTER_SLOTS) {
161
- return false;
162
- }
163
- }
164
-
165
- for (let a = 0; a < runs.length; a++) {
166
- for (let b = a + 1; b < runs.length; b++) {
167
- const x = runs[a];
168
- const y = runs[b];
169
-
170
- if (x.base < y.base + y.span && y.base < x.base + x.span && x.base !== y.base) {
171
- return false;
172
- }
173
- }
174
- }
175
-
176
- return true;
177
- }
178
-
179
- /**
180
- * Def-use over the instruction stream: an edge `i -> j` says instruction `j` reads a slot
181
- * instruction `i` last wrote.
182
- *
183
- * Straight-line code with no control flow, so "last wrote" is exact rather than a may-reach
184
- * approximation: one forward scan of a slot-indexed table finds every producer. The result is a
185
- * {@link CSRGraph} because that is what the rest of the engine walks — `edge_addresses` /
186
- * `adjacency` directly, without a per-vertex closure — and because dead-code elimination is then a
187
- * reachability question over it rather than a bespoke container.
188
- *
189
- * A read of a slot nothing has written yet has no producer and contributes no edge: the register
190
- * file starts zeroed on both executors, so such a read is a constant, not a dependency.
191
- *
192
- * @param {ParticleDecodedInstruction[]} decoded
193
- * @param {number} [register_slots]
194
- * @returns {CSRGraph} vertices are instructions; vertex `i`'s adjacency lists its consumers
195
- */
196
- export function build_particle_def_use_graph(decoded, register_slots = PARTICLE_VM_REGISTER_SLOTS) {
197
- const count = decoded.length;
198
- const degree = new Uint32Array(count + 1);
199
- /** @type {number[][]} consumer lists, gathered before the CSR is sized */
200
- const edges = [];
201
-
202
- const producer = new Int32Array(register_slots).fill(-1);
203
-
204
- for (let i = 0; i < count; i++) {
205
- const instruction = decoded[i];
206
- /** @type {Set<number>} one edge per producing instruction, however many lanes it feeds */
207
- const sources = new Set();
208
-
209
- for (const read of instruction.reads) {
210
- for (let lane = 0; lane < read.span; lane++) {
211
- const slot = read.base + lane;
212
- const from = slot < register_slots ? producer[slot] : -1;
213
-
214
- if (from >= 0) {
215
- sources.add(from);
216
- }
217
- }
218
- }
219
-
220
- for (const from of sources) {
221
- edges.push([from, i]);
222
- degree[from]++;
223
- }
224
-
225
- if (instruction.write !== null) {
226
- for (let lane = 0; lane < instruction.write.span; lane++) {
227
- const slot = instruction.write.base + lane;
228
-
229
- if (slot < register_slots) {
230
- producer[slot] = i;
231
- }
232
- }
233
- }
234
- }
235
-
236
- const graph = new CSRGraph(count, edges.length);
237
- const addresses = graph.edge_addresses;
238
-
239
- for (let i = 0; i < count; i++) {
240
- addresses[i + 1] = addresses[i] + degree[i];
241
- }
242
-
243
- const cursor = addresses.slice(0, count);
244
-
245
- for (const [from, to] of edges) {
246
- graph.adjacency[cursor[from]++] = to;
247
- }
248
-
249
- graph.edge_weights.fill(1);
250
-
251
- return graph;
252
- }
253
-
254
- /**
255
- * @typedef {object} ParticleLaneAnalysis
256
- * @property {Uint8Array} keep per instruction: whether it has to survive — it runs, and either it
257
- * has an effect beyond its destination or something reads a lane it writes
258
- * @property {Int32Array} width per instruction: the width field it could carry, once the lanes
259
- * nothing reads are dropped. Equal to the encoded one wherever narrowing is not allowed.
260
- * @property {Int32Array} pattern per instruction: the swizzle pattern at that width; the encoded
261
- * one for every other opcode.
262
- * @property {Uint8Array} live_lanes per instruction: bitmask of destination lanes read later
263
- * @property {number} halt_index index of the first `HALT`, or `-1`
264
- */
265
-
266
- /**
267
- * Backward per-lane liveness: which lanes of each instruction's destination anything downstream
268
- * actually reads, and therefore how narrow the instruction could be and whether it is needed at all.
269
- *
270
- * Registers do not outlive a section — both executors start a particle with a zeroed file — so the
271
- * walk starts with nothing live and everything a program means has already been written to the
272
- * record, the kill flag or the RNG by the time it ends.
273
- *
274
- * Narrowing is decided inside the same backward pass rather than after it, because an instruction's
275
- * reads follow its width: narrowing a broadcast to two lanes shortens the run it reads, which is
276
- * what makes the value above it dead. Deciding both at once reaches that fixed point in one sweep.
277
- *
278
- * @param {ParticleDecodedInstruction[]} decoded
279
- * @param {number} [register_slots]
280
- * @returns {ParticleLaneAnalysis}
281
- */
282
- export function analyze_particle_lanes(decoded, register_slots = PARTICLE_VM_REGISTER_SLOTS) {
283
- const count = decoded.length;
284
- const keep = new Uint8Array(count);
285
- const width = new Int32Array(count);
286
- const pattern = new Int32Array(count);
287
- const live_lanes = new Uint8Array(count);
288
-
289
- let halt_index = -1;
290
-
291
- for (let i = 0; i < count; i++) {
292
- width[i] = decoded[i].width;
293
- pattern[i] = decoded[i].words[3];
294
-
295
- if (halt_index < 0 && decoded[i].op === VM_OP.HALT) {
296
- halt_index = i;
297
- }
298
- }
299
-
300
- // A HALT returns, so nothing past the first one runs — and nothing past it can make a value live.
301
- const last = halt_index >= 0 ? halt_index : count - 1;
302
- const live = new Uint8Array(register_slots);
303
-
304
- for (let i = last; i >= 0; i--) {
305
- const instruction = decoded[i];
306
- const semantics = instruction.semantics;
307
-
308
- let mask = 0;
309
- let highest_live = -1;
310
-
311
- if (instruction.write !== null) {
312
- for (let lane = 0; lane < instruction.write.span; lane++) {
313
- if (live[instruction.write.base + lane]) {
314
- mask |= 1 << lane;
315
- highest_live = lane;
316
- }
317
- }
318
- }
319
-
320
- live_lanes[i] = mask;
321
-
322
- if (!semantics.side_effects && highest_live < 0) {
323
- continue;
324
- }
325
-
326
- keep[i] = 1;
327
-
328
- // Only the lanes below the highest live one have to be computed. Lanes are a contiguous run
329
- // from zero, so a live lane 2 holds the width at 3 however dead lanes 0 and 1 are.
330
- if (
331
- semantics.narrowable
332
- && highest_live >= 0
333
- && instruction.write !== null
334
- && instruction.write.span === instruction.operation_width
335
- ) {
336
- width[i] = Math.max(highest_live + 1, instruction.meta.min_width);
337
- }
338
-
339
- const operation_width = vm_operation_width(instruction.op, width[i]);
340
-
341
- if (instruction.op === VM_OP.SWIZZLE) {
342
- pattern[i] = vm_trim_swizzle(instruction.words[3], operation_width);
343
- }
344
-
345
- if (instruction.write !== null) {
346
- const span = vm_dest_width(instruction.op, width[i]);
347
-
348
- for (let lane = 0; lane < span; lane++) {
349
- live[instruction.write.base + lane] = 0;
350
- }
351
- }
352
-
353
- for (const read of instruction.reads) {
354
- const span = read_span_at(instruction, read, operation_width, pattern[i]);
355
-
356
- for (let lane = 0; lane < span; lane++) {
357
- live[read.base + lane] = 1;
358
- }
359
- }
360
- }
361
-
362
- return { keep, width, pattern, live_lanes, halt_index };
363
- }
364
-
365
- /**
366
- * How far a read reaches once the instruction is narrowed to `operation_width`.
367
- *
368
- * @param {ParticleDecodedInstruction} instruction
369
- * @param {ParticleOperandRun} read
370
- * @param {number} operation_width
371
- * @param {number} pattern the (possibly trimmed) swizzle pattern
372
- * @returns {number}
373
- */
374
- export function read_span_at(instruction, read, operation_width, pattern) {
375
- if (instruction.op === VM_OP.SWIZZLE && read.operand === 0) {
376
- return vm_trimmed_swizzle_span(pattern, operation_width);
377
- }
378
-
379
- if (read.operand >= 0 && instruction.meta.scalar_operands.includes(read.operand)) {
380
- return 1;
381
- }
382
-
383
- return operation_width;
384
- }
385
-
386
- /**
387
- * @param {number} base_a
388
- * @param {number} span_a
389
- * @param {number} base_b
390
- * @param {number} span_b
391
- * @returns {boolean} whether the two runs share a slot
392
- */
393
- export function particle_runs_overlap(base_a, span_a, base_b, span_b) {
394
- return base_a < base_b + span_b && base_b < base_a + span_a;
395
- }
396
-
397
- /**
398
- * Whether anything in `[from, to)` reads or writes any slot of a run.
399
- *
400
- * What a pass moving an instruction has to ask: a value that nothing in the gap looks at or
401
- * disturbs can be produced anywhere in that gap.
402
- *
403
- * @param {ParticleDecodedInstruction[]} decoded
404
- * @param {number} from inclusive
405
- * @param {number} to exclusive
406
- * @param {number} base
407
- * @param {number} span
408
- * @returns {boolean}
409
- */
410
- export function particle_run_is_touched_between(decoded, from, to, base, span) {
411
- for (let i = from; i < to; i++) {
412
- const instruction = decoded[i];
413
-
414
- if (instruction.write !== null
415
- && particle_runs_overlap(instruction.write.base, instruction.write.span, base, span)) {
416
- return true;
417
- }
418
-
419
- for (const read of instruction.reads) {
420
- if (particle_runs_overlap(read.base, read.span, base, span)) {
421
- return true;
422
- }
423
- }
424
- }
425
-
426
- return false;
427
- }
428
-
429
- /**
430
- * Whether every slot of a run is overwritten — or the section ends — before anything reads it again.
431
- *
432
- * A run that is dead from `index` onwards belongs to nobody, so the instruction that filled it may
433
- * be retargeted, folded into its consumer, or dropped.
434
- *
435
- * @param {ParticleDecodedInstruction[]} decoded
436
- * @param {number} index the instruction after which to look
437
- * @param {number} base
438
- * @param {number} span
439
- * @returns {boolean}
440
- */
441
- export function particle_run_is_dead_after(decoded, index, base, span) {
442
- const pending = new Set();
443
-
444
- for (let lane = 0; lane < span; lane++) {
445
- pending.add(base + lane);
446
- }
447
-
448
- for (let i = index + 1; i < decoded.length && pending.size > 0; i++) {
449
- const instruction = decoded[i];
450
-
451
- // Nothing past a HALT runs, so nothing past it can want the value.
452
- if (instruction.op === VM_OP.HALT) {
453
- return true;
454
- }
455
-
456
- for (const read of instruction.reads) {
457
- for (let lane = 0; lane < read.span; lane++) {
458
- if (pending.has(read.base + lane)) {
459
- return false;
460
- }
461
- }
462
- }
463
-
464
- if (instruction.write !== null) {
465
- for (let lane = 0; lane < instruction.write.span; lane++) {
466
- pending.delete(instruction.write.base + lane);
467
- }
468
- }
469
- }
470
-
471
- return true;
472
- }
473
-
474
- /**
475
- * Which value each register slot holds, as a number, and where that value was first computed.
476
- *
477
- * Two passes ask questions that turn out to be the same question underneath. "Is this register still
478
- * the copy it was?" and "has this expression already been evaluated?" both reduce to comparing value
479
- * numbers, and both need a copy to carry its source's number across rather than minting a new one —
480
- * otherwise a value moved between registers stops being recognisable as itself.
481
- *
482
- * Numbers are minted per write and inherited by `MOV` and `SWIZZLE`, the only two opcodes that move
483
- * a value without changing it. Because a number is never reused, a slot overwritten since simply
484
- * fails to match, and there is no invalidation step to get wrong.
485
- */
486
- export class ParticleValueNumbering {
487
- /**
488
- * @param {number} [slots]
489
- */
490
- constructor(slots = PARTICLE_VM_REGISTER_SLOTS) {
491
- /** @type {number} */
492
- this.slots = slots;
493
- /** @type {Int32Array} the value in each slot */
494
- this.value_id = new Int32Array(slots);
495
- /** @type {Int32Array} the slot each value was first computed in */
496
- this.home = new Int32Array(slots);
497
- /** @type {number} */
498
- this.next_id = slots;
499
-
500
- // The file starts zeroed on both executors, but every slot is given a DISTINCT number: they
501
- // hold equal values, not the same value, and nothing is gained by conflating them.
502
- for (let slot = 0; slot < slots; slot++) {
503
- this.value_id[slot] = slot;
504
- this.home[slot] = slot;
505
- }
506
- }
507
-
508
- /**
509
- * @param {number} base
510
- * @param {number} span
511
- * @returns {number[]|null} the numbers in that run, or `null` if it leaves the file
512
- */
513
- ids_of(base, span) {
514
- if (base < 0 || base + span > this.slots) {
515
- return null;
516
- }
517
-
518
- const ids = new Array(span);
519
-
520
- for (let lane = 0; lane < span; lane++) {
521
- ids[lane] = this.value_id[base + lane];
522
- }
523
-
524
- return ids;
525
- }
526
-
527
- /**
528
- * @param {number} base
529
- * @param {number[]} ids
530
- * @returns {boolean} whether that run still holds exactly those values
531
- */
532
- holds(base, ids) {
533
- if (base < 0 || base + ids.length > this.slots) {
534
- return false;
535
- }
536
-
537
- for (let lane = 0; lane < ids.length; lane++) {
538
- if (this.value_id[base + lane] !== ids[lane]) {
539
- return false;
540
- }
541
- }
542
-
543
- return true;
544
- }
545
-
546
- /**
547
- * Where the values in a run were originally computed, when that is itself a run and still holds
548
- * them.
549
- *
550
- * Two lanes of a broadcast were both computed in the same slot, which is not a run — so a
551
- * broadcast as a whole has no origin to be read past, however each of its lanes does.
552
- *
553
- * @param {number} base
554
- * @param {number} span
555
- * @returns {number} the origin's base, or `-1`
556
- */
557
- origin_of(base, span) {
558
- if (base < 0 || base + span > this.slots) {
559
- return -1;
560
- }
561
-
562
- const origin = this.home[base];
563
-
564
- if (origin + span > this.slots) {
565
- return -1;
566
- }
567
-
568
- for (let lane = 0; lane < span; lane++) {
569
- if (this.home[base + lane] !== origin + lane || this.value_id[origin + lane] !== this.value_id[base + lane]) {
570
- return -1;
571
- }
572
- }
573
-
574
- return origin;
575
- }
576
-
577
- /**
578
- * Record what an instruction leaves in its destination.
579
- *
580
- * Takes the words as they will be EMITTED rather than as they were read, because a pass may have
581
- * moved a source or rewritten the opcode by the time it gets here — the state has to describe
582
- * the program that will run.
583
- *
584
- * @param {ArrayLike<number>} words the five words of a single instruction
585
- */
586
- publish(words) {
587
- const instruction = decode_particle_instruction(words);
588
-
589
- if (instruction.write === null) {
590
- return;
591
- }
592
-
593
- const base = instruction.write.base;
594
- const span = instruction.write.span;
595
- const source = words[2];
596
-
597
- /** Gathered before anything is written: the ISA lets a destination alias a source exactly. */
598
- const lanes = new Array(span);
599
-
600
- if (instruction.semantics.broadcasts) {
601
- // One value in every lane, so lane `i` is a copy of lane 0 and traces back to it — which
602
- // is what lets a consumer read the scalar instead of the widened form.
603
- const id = this.next_id++;
604
-
605
- lanes.fill({ id, home: base });
606
- } else {
607
- for (let lane = 0; lane < span; lane++) {
608
- const from = carried_lane(instruction.op, words, lane, source);
609
-
610
- lanes[lane] = from >= 0 && from < this.slots
611
- ? { id: this.value_id[from], home: this.home[from] }
612
- : { id: this.next_id++, home: base + lane };
613
- }
614
- }
615
-
616
- for (let lane = 0; lane < span; lane++) {
617
- const slot = base + lane;
618
-
619
- if (slot < this.slots) {
620
- this.value_id[slot] = lanes[lane].id;
621
- this.home[slot] = lanes[lane].home;
622
- }
623
- }
624
- }
625
- }
626
-
627
- /**
628
- * The slot an output lane's value is carried across from, for the two opcodes that move a value
629
- * without changing it. `-1` for everything else, which mints a new value.
630
- *
631
- * @param {number} op
632
- * @param {ArrayLike<number>} words
633
- * @param {number} lane
634
- * @param {number} source
635
- * @returns {number}
636
- */
637
- function carried_lane(op, words, lane, source) {
638
- if (op === VM_OP.MOV) {
639
- return source + lane;
640
- }
641
-
642
- if (op === VM_OP.SWIZZLE) {
643
- return source + ((words[3] >> (2 * lane)) & 3);
644
- }
645
-
646
- return -1;
647
- }
1
+ import { CSRGraph } from "../../../../core/graph/csr/CSRGraph.js";
2
+ import { PARTICLE_VM_REGISTER_SLOTS } from "../ParticleConstants.js";
3
+ import {
4
+ VM_ISA,
5
+ VM_KIND,
6
+ VM_MAX_WIDTH,
7
+ VM_OP,
8
+ VM_SWIZZLE_IDENTITY,
9
+ swizzle_selector,
10
+ vm_decode_dst,
11
+ vm_decode_kinds,
12
+ vm_decode_op,
13
+ vm_decode_width,
14
+ vm_dest_width,
15
+ vm_kinds_problem,
16
+ vm_operand_base,
17
+ vm_operand_pattern,
18
+ vm_operand_slot,
19
+ vm_operation_width,
20
+ vm_register,
21
+ vm_visit_operands,
22
+ } from "../isa/ParticleVMISA.js";
23
+ import { vm_semantics_of } from "./particle_vm_semantics.js";
24
+
25
+ /**
26
+ * The analysis every optimizer pass reads: instructions decoded into the operands they resolve and
27
+ * the run they write, the def-use graph over those, and per-lane liveness.
28
+ *
29
+ * Everything here is derived, never authoritative — the instruction words are. A pass rewrites
30
+ * words and the next one re-derives, which is why there is no incremental update path and no
31
+ * mutation of an analysis in place: the programs are at most
32
+ * {@link ../ParticleConstants.js PARTICLE_VM_MAX_INSTRUCTIONS} instructions over
33
+ * {@link PARTICLE_VM_REGISTER_SLOTS} slots, and re-deriving all of it is cheaper than keeping it
34
+ * honest.
35
+ *
36
+ * ## Lanes, not registers
37
+ *
38
+ * A register is one `f32` slot and a value of width `w` is a run of `w` of them, so nothing here is
39
+ * per-register: a value's producer is per (instruction, lane), and so is its liveness. Which slots a
40
+ * source reads is the operand's own business — its base and its swizzle, read at as many lanes as the
41
+ * opcode takes of it — and {@link particle_read_slots} is the one place that turns an operand into
42
+ * slots. The ISA's {@link vm_visit_operands} is the one place that says how many lanes that is.
43
+ *
44
+ * ## Kinds
45
+ *
46
+ * A source is a register run or a literal, by the kind bits its instruction carries. Only the first
47
+ * is a run of the file, so `reads` holds only those — every question about liveness, aliasing and
48
+ * dependency is a question about the file, and a literal whose bits happen to look like a slot index
49
+ * must never answer it. The passes that care what a source *is* rather than where it lives — folding,
50
+ * sharing, the copy rewrites — read `sources`, which lists every operand with its kind.
51
+ */
52
+
53
+ /**
54
+ * @typedef {object} ParticleOperandRun
55
+ * @property {number} operand which source slot this is, `0..2`; `-1` for the destination
56
+ * @property {number} kind a `VM_KIND`; `REG` for every entry of `reads` and for the write
57
+ * @property {number} word the operand word as encoded
58
+ * @property {number} base first register slot, or `-1` for a literal
59
+ * @property {number} pattern the swizzle the operand reads through, as `pack_swizzle` packs one; the
60
+ * identity for a write
61
+ * @property {number} lanes how many lanes the instruction reads of it — the operation's width, or one
62
+ * for a slot the ISA marks scalar; for the write, how many it writes
63
+ * @property {number} span consecutive slots the operand reaches from its base
64
+ */
65
+
66
+ /**
67
+ * @typedef {object} ParticleDecodedInstruction
68
+ * @property {number} index position in the stream
69
+ * @property {number[]} words the four instruction words
70
+ * @property {number} op
71
+ * @property {number} width the encoded width field
72
+ * @property {number} dst the encoded destination register
73
+ * @property {number} kinds the three source kinds as one field, as word 0 carries them
74
+ * @property {number} operation_width lanes the operation spans, after the ISA's fixed-width rules
75
+ * @property {object} meta the {@link VM_ISA} entry
76
+ * @property {import("./particle_vm_semantics.js").ParticleOpSemantics} semantics
77
+ * @property {ParticleOperandRun|null} write destination run, or null when the opcode writes nothing
78
+ * @property {ParticleOperandRun[]} reads every register operand the instruction reads — the `REG` sources
79
+ * @property {ParticleOperandRun[]} sources every source operand, whatever its kind, in slot order
80
+ */
81
+
82
+ /**
83
+ * Decode one instruction into the operands it resolves and the run it writes.
84
+ *
85
+ * @param {ArrayLike<number>} words the four words of a single instruction
86
+ * @param {number} [index]
87
+ * @returns {ParticleDecodedInstruction}
88
+ */
89
+ export function decode_particle_instruction(words, index = 0) {
90
+ const word0 = words[0];
91
+ const op = vm_decode_op(word0);
92
+ const width = vm_decode_width(word0);
93
+ const meta = VM_ISA.get(op);
94
+
95
+ if (meta === undefined) {
96
+ throw new Error(`particle optimizer: unknown opcode ${op} at instruction ${index}`);
97
+ }
98
+
99
+ const dst = vm_decode_dst(word0);
100
+ const dest = vm_dest_width(op, width);
101
+
102
+ /** @type {ParticleOperandRun|null} */
103
+ const write = dest === 0
104
+ ? null
105
+ : { operand: -1, kind: VM_KIND.REG, word: dst, base: dst, pattern: VM_SWIZZLE_IDENTITY, lanes: dest, span: dest };
106
+
107
+ /** @type {ParticleOperandRun[]} */
108
+ const sources = [];
109
+
110
+ vm_visit_operands(words, 0, ({ slot, kind, word, lanes, span }) => {
111
+ sources.push({
112
+ operand: slot,
113
+ kind,
114
+ word,
115
+ base: kind === VM_KIND.REG ? vm_operand_base(word) : -1,
116
+ pattern: kind === VM_KIND.REG ? vm_operand_pattern(word) : VM_SWIZZLE_IDENTITY,
117
+ lanes,
118
+ span,
119
+ });
120
+ });
121
+
122
+ return {
123
+ index,
124
+ words: [words[0], words[1], words[2], words[3]],
125
+ op,
126
+ width,
127
+ dst,
128
+ kinds: vm_decode_kinds(word0),
129
+ operation_width: vm_operation_width(op, width),
130
+ meta,
131
+ semantics: vm_semantics_of(op),
132
+ write,
133
+ reads: sources.filter(source => source.kind === VM_KIND.REG),
134
+ sources,
135
+ };
136
+ }
137
+
138
+ /**
139
+ * @param {ArrayLike<ArrayLike<number>>} instructions four-word tuples
140
+ * @returns {ParticleDecodedInstruction[]}
141
+ */
142
+ export function decode_particle_instructions(instructions) {
143
+ const out = new Array(instructions.length);
144
+
145
+ for (let i = 0; i < instructions.length; i++) {
146
+ out[i] = decode_particle_instruction(instructions[i], i);
147
+ }
148
+
149
+ return out;
150
+ }
151
+
152
+ /**
153
+ * The slots a register operand reads, one per lane read — in lane order, and a slot appears once per
154
+ * lane that reads it, so a broadcast lists the same slot several times.
155
+ *
156
+ * @param {ParticleOperandRun} read a `REG` run
157
+ * @param {number} [lanes] how many lanes to read; the operand's own count when left out
158
+ * @returns {number[]}
159
+ */
160
+ export function particle_read_slots(read, lanes = read.lanes) {
161
+ const slots = new Array(lanes);
162
+
163
+ for (let lane = 0; lane < lanes; lane++) {
164
+ slots[lane] = read.base + swizzle_selector(read.pattern, lane);
165
+ }
166
+
167
+ return slots;
168
+ }
169
+
170
+ /**
171
+ * Whether an instruction fits the register file: its kinds agree with its opcode, and every run it
172
+ * touches ends inside the file.
173
+ *
174
+ * There is no aliasing rule to check. Every executor resolves every source before it writes the
175
+ * destination, so a source may overlap the destination in any way and mean what it held before the
176
+ * instruction — which is also what lets a pass move a base or fold a swizzle without asking how the
177
+ * result sits against the other operands.
178
+ *
179
+ * @param {ArrayLike<number>} words the four words of a single instruction
180
+ * @returns {boolean}
181
+ */
182
+ export function particle_instruction_is_well_formed(words) {
183
+ if (vm_kinds_problem(words, 0) !== null) {
184
+ return false;
185
+ }
186
+
187
+ const decoded = decode_particle_instruction(words);
188
+ const runs = decoded.write === null ? decoded.reads : [decoded.write, ...decoded.reads];
189
+
190
+ for (const run of runs) {
191
+ if (run.base < 0 || run.base + run.span > PARTICLE_VM_REGISTER_SLOTS) {
192
+ return false;
193
+ }
194
+ }
195
+
196
+ return true;
197
+ }
198
+
199
+ /**
200
+ * Def-use over the instruction stream: an edge `i -> j` says instruction `j` reads a slot
201
+ * instruction `i` last wrote.
202
+ *
203
+ * Straight-line code with no control flow, so "last wrote" is exact rather than a may-reach
204
+ * approximation: one forward scan of a slot-indexed table finds every producer. The result is a
205
+ * {@link CSRGraph} because that is what the rest of the engine walks — `edge_addresses` /
206
+ * `adjacency` directly, without a per-vertex closure — and because dead-code elimination is then a
207
+ * reachability question over it rather than a bespoke container.
208
+ *
209
+ * A read of a slot nothing has written yet has no producer and contributes no edge: the register
210
+ * file starts zeroed on both executors, so such a read is a constant, not a dependency.
211
+ *
212
+ * @param {ParticleDecodedInstruction[]} decoded
213
+ * @param {number} [register_slots]
214
+ * @returns {CSRGraph} vertices are instructions; vertex `i`'s adjacency lists its consumers
215
+ */
216
+ export function build_particle_def_use_graph(decoded, register_slots = PARTICLE_VM_REGISTER_SLOTS) {
217
+ const count = decoded.length;
218
+ const degree = new Uint32Array(count + 1);
219
+ /** @type {number[][]} consumer lists, gathered before the CSR is sized */
220
+ const edges = [];
221
+
222
+ const producer = new Int32Array(register_slots).fill(-1);
223
+
224
+ for (let i = 0; i < count; i++) {
225
+ const instruction = decoded[i];
226
+ /** @type {Set<number>} one edge per producing instruction, however many lanes it feeds */
227
+ const sources = new Set();
228
+
229
+ for (const read of instruction.reads) {
230
+ for (const slot of particle_read_slots(read)) {
231
+ const from = slot < register_slots ? producer[slot] : -1;
232
+
233
+ if (from >= 0) {
234
+ sources.add(from);
235
+ }
236
+ }
237
+ }
238
+
239
+ for (const from of sources) {
240
+ edges.push([from, i]);
241
+ degree[from]++;
242
+ }
243
+
244
+ if (instruction.write !== null) {
245
+ for (let lane = 0; lane < instruction.write.span; lane++) {
246
+ const slot = instruction.write.base + lane;
247
+
248
+ if (slot < register_slots) {
249
+ producer[slot] = i;
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ const graph = new CSRGraph(count, edges.length);
256
+ const addresses = graph.edge_addresses;
257
+
258
+ for (let i = 0; i < count; i++) {
259
+ addresses[i + 1] = addresses[i] + degree[i];
260
+ }
261
+
262
+ const cursor = addresses.slice(0, count);
263
+
264
+ for (const [from, to] of edges) {
265
+ graph.adjacency[cursor[from]++] = to;
266
+ }
267
+
268
+ graph.edge_weights.fill(1);
269
+
270
+ return graph;
271
+ }
272
+
273
+ /**
274
+ * @typedef {object} ParticleLaneAnalysis
275
+ * @property {Uint8Array} keep per instruction: whether it has to survive — it runs, and either it
276
+ * has an effect beyond its destination or something reads a lane it writes
277
+ * @property {Int32Array} width per instruction: the width field it could carry, once the lanes
278
+ * nothing reads are dropped. Equal to the encoded one wherever narrowing is not allowed.
279
+ * @property {Uint8Array} live_lanes per instruction: bitmask of destination lanes read later
280
+ * @property {number} halt_index index of the first `HALT`, or `-1`
281
+ */
282
+
283
+ /**
284
+ * Backward per-lane liveness: which lanes of each instruction's destination anything downstream
285
+ * actually reads, and therefore how narrow the instruction could be and whether it is needed at all.
286
+ *
287
+ * Registers do not outlive a section — both executors start a particle with a zeroed file — so the
288
+ * walk starts with nothing live and everything a program means has already been written to the
289
+ * record, the kill flag or the RNG by the time it ends.
290
+ *
291
+ * Narrowing is decided inside the same backward pass rather than after it, because an instruction's
292
+ * reads follow its width: a narrowed copy reads fewer lanes of its source, which is what makes the
293
+ * value above it dead. Deciding both at once reaches that fixed point in one sweep.
294
+ *
295
+ * @param {ParticleDecodedInstruction[]} decoded
296
+ * @param {number} [register_slots]
297
+ * @returns {ParticleLaneAnalysis}
298
+ */
299
+ export function analyze_particle_lanes(decoded, register_slots = PARTICLE_VM_REGISTER_SLOTS) {
300
+ const count = decoded.length;
301
+ const keep = new Uint8Array(count);
302
+ const width = new Int32Array(count);
303
+ const live_lanes = new Uint8Array(count);
304
+
305
+ let halt_index = -1;
306
+
307
+ for (let i = 0; i < count; i++) {
308
+ width[i] = decoded[i].width;
309
+
310
+ if (halt_index < 0 && decoded[i].op === VM_OP.HALT) {
311
+ halt_index = i;
312
+ }
313
+ }
314
+
315
+ // A HALT returns, so nothing past the first one runs — and nothing past it can make a value live.
316
+ const last = halt_index >= 0 ? halt_index : count - 1;
317
+ const live = new Uint8Array(register_slots);
318
+
319
+ for (let i = last; i >= 0; i--) {
320
+ const instruction = decoded[i];
321
+ const semantics = instruction.semantics;
322
+
323
+ let mask = 0;
324
+ let highest_live = -1;
325
+
326
+ if (instruction.write !== null) {
327
+ for (let lane = 0; lane < instruction.write.span; lane++) {
328
+ if (live[instruction.write.base + lane]) {
329
+ mask |= 1 << lane;
330
+ highest_live = lane;
331
+ }
332
+ }
333
+ }
334
+
335
+ live_lanes[i] = mask;
336
+
337
+ if (!semantics.side_effects && highest_live < 0) {
338
+ continue;
339
+ }
340
+
341
+ keep[i] = 1;
342
+
343
+ // Only the lanes below the highest live one have to be computed. Lanes are a contiguous run
344
+ // from zero, so a live lane 2 holds the width at 3 however dead lanes 0 and 1 are.
345
+ if (
346
+ semantics.narrowable
347
+ && highest_live >= 0
348
+ && instruction.write !== null
349
+ && instruction.write.span === instruction.operation_width
350
+ ) {
351
+ width[i] = Math.max(highest_live + 1, instruction.meta.min_width);
352
+ }
353
+
354
+ const operation_width = vm_operation_width(instruction.op, width[i]);
355
+
356
+ if (instruction.write !== null) {
357
+ const span = vm_dest_width(instruction.op, width[i]);
358
+
359
+ for (let lane = 0; lane < span; lane++) {
360
+ live[instruction.write.base + lane] = 0;
361
+ }
362
+ }
363
+
364
+ for (const read of instruction.reads) {
365
+ for (const slot of particle_read_slots(read, particle_lanes_read_at(instruction, read, operation_width))) {
366
+ if (slot < register_slots) {
367
+ live[slot] = 1;
368
+ }
369
+ }
370
+ }
371
+ }
372
+
373
+ return { keep, width, live_lanes, halt_index };
374
+ }
375
+
376
+ /**
377
+ * The most slots alive at once anywhere in the section — what a perfect allocator would need, and
378
+ * the one measure of an instruction order the scheduling pass is for.
379
+ *
380
+ * A slot is alive between a write and a later read of it; a read at the instruction that overwrites
381
+ * the slot counts for the value above, since every executor reads its sources before it writes.
382
+ * `HALT` is not special here: what follows one is dead code, and counting it can only overstate.
383
+ *
384
+ * @param {ParticleDecodedInstruction[]} decoded
385
+ * @param {number} [register_slots]
386
+ * @returns {number}
387
+ */
388
+ export function particle_peak_live_slots(decoded, register_slots = PARTICLE_VM_REGISTER_SLOTS) {
389
+ const live = new Uint8Array(register_slots);
390
+
391
+ let alive = 0;
392
+ let peak = 0;
393
+
394
+ for (let i = decoded.length - 1; i >= 0; i--) {
395
+ const instruction = decoded[i];
396
+
397
+ if (instruction.write !== null) {
398
+ for (let lane = 0; lane < instruction.write.span; lane++) {
399
+ const slot = instruction.write.base + lane;
400
+
401
+ if (slot < register_slots && live[slot]) {
402
+ live[slot] = 0;
403
+ alive--;
404
+ }
405
+ }
406
+ }
407
+
408
+ for (const read of instruction.reads) {
409
+ for (const slot of particle_read_slots(read)) {
410
+ if (slot >= 0 && slot < register_slots && !live[slot]) {
411
+ live[slot] = 1;
412
+ alive++;
413
+ }
414
+ }
415
+ }
416
+
417
+ peak = Math.max(peak, alive);
418
+ }
419
+
420
+ return peak;
421
+ }
422
+
423
+ /**
424
+ * How many lanes of a source an instruction reads once it is narrowed to `operation_width`: one for a
425
+ * slot the ISA marks scalar, the width for everything else.
426
+ *
427
+ * @param {ParticleDecodedInstruction} instruction
428
+ * @param {ParticleOperandRun} read
429
+ * @param {number} operation_width
430
+ * @returns {number}
431
+ */
432
+ function particle_lanes_read_at(instruction, read, operation_width) {
433
+ return instruction.meta.scalar_operands.includes(read.operand) ? 1 : operation_width;
434
+ }
435
+
436
+ /**
437
+ * @param {number} base_a
438
+ * @param {number} span_a
439
+ * @param {number} base_b
440
+ * @param {number} span_b
441
+ * @returns {boolean} whether the two runs share a slot
442
+ */
443
+ export function particle_runs_overlap(base_a, span_a, base_b, span_b) {
444
+ return base_a < base_b + span_b && base_b < base_a + span_a;
445
+ }
446
+
447
+ /**
448
+ * Whether anything in `[from, to)` reads or writes any slot of a run.
449
+ *
450
+ * What a pass moving an instruction has to ask: a value that nothing in the gap looks at or
451
+ * disturbs can be produced anywhere in that gap. A read is taken at its whole span, which is at
452
+ * least what it touches.
453
+ *
454
+ * @param {ParticleDecodedInstruction[]} decoded
455
+ * @param {number} from inclusive
456
+ * @param {number} to exclusive
457
+ * @param {number} base
458
+ * @param {number} span
459
+ * @returns {boolean}
460
+ */
461
+ export function particle_run_is_touched_between(decoded, from, to, base, span) {
462
+ for (let i = from; i < to; i++) {
463
+ const instruction = decoded[i];
464
+
465
+ if (instruction.write !== null
466
+ && particle_runs_overlap(instruction.write.base, instruction.write.span, base, span)) {
467
+ return true;
468
+ }
469
+
470
+ for (const read of instruction.reads) {
471
+ if (particle_runs_overlap(read.base, read.span, base, span)) {
472
+ return true;
473
+ }
474
+ }
475
+ }
476
+
477
+ return false;
478
+ }
479
+
480
+ /**
481
+ * Whether anything in `[from, to)` writes any slot of a run — the weaker question, for a pass that
482
+ * needs a value to still be where it was but does not mind who has looked at it in between.
483
+ *
484
+ * @param {ParticleDecodedInstruction[]} decoded
485
+ * @param {number} from inclusive
486
+ * @param {number} to exclusive
487
+ * @param {number} base
488
+ * @param {number} span
489
+ * @returns {boolean}
490
+ */
491
+ export function particle_run_is_written_between(decoded, from, to, base, span) {
492
+ for (let i = from; i < to; i++) {
493
+ const instruction = decoded[i];
494
+
495
+ if (instruction.write !== null
496
+ && particle_runs_overlap(instruction.write.base, instruction.write.span, base, span)) {
497
+ return true;
498
+ }
499
+ }
500
+
501
+ return false;
502
+ }
503
+
504
+ /**
505
+ * Whether every slot of a run is overwritten — or the section ends — before anything reads it again.
506
+ *
507
+ * A run that is dead from `index` onwards belongs to nobody, so the instruction that filled it may
508
+ * be retargeted, folded into its consumer, or dropped.
509
+ *
510
+ * @param {ParticleDecodedInstruction[]} decoded
511
+ * @param {number} index the instruction after which to look
512
+ * @param {number} base
513
+ * @param {number} span
514
+ * @returns {boolean}
515
+ */
516
+ export function particle_run_is_dead_after(decoded, index, base, span) {
517
+ const pending = new Set();
518
+
519
+ for (let lane = 0; lane < span; lane++) {
520
+ pending.add(base + lane);
521
+ }
522
+
523
+ for (let i = index + 1; i < decoded.length && pending.size > 0; i++) {
524
+ const instruction = decoded[i];
525
+
526
+ // Nothing past a HALT runs, so nothing past it can want the value.
527
+ if (instruction.op === VM_OP.HALT) {
528
+ return true;
529
+ }
530
+
531
+ for (const read of instruction.reads) {
532
+ for (const slot of particle_read_slots(read)) {
533
+ if (pending.has(slot)) {
534
+ return false;
535
+ }
536
+ }
537
+ }
538
+
539
+ if (instruction.write !== null) {
540
+ for (let lane = 0; lane < instruction.write.span; lane++) {
541
+ pending.delete(instruction.write.base + lane);
542
+ }
543
+ }
544
+ }
545
+
546
+ return true;
547
+ }
548
+
549
+ /**
550
+ * Which value each register slot holds, as a number, and where that value was first computed.
551
+ *
552
+ * Two passes ask questions that turn out to be the same question underneath. "Is this register still
553
+ * the copy it was?" and "has this expression already been evaluated?" both reduce to comparing value
554
+ * numbers, and both need a copy to carry its source's number across rather than minting a new one —
555
+ * otherwise a value moved between registers stops being recognisable as itself.
556
+ *
557
+ * Numbers are minted per write and inherited by `MOV`, the one opcode that moves a value without
558
+ * changing it — through whatever swizzle its source carries, so a broadcast's lanes all hold one
559
+ * number and a permutation's hold the source's in the permuted order. Because a number is never
560
+ * reused, a slot overwritten since simply fails to match, and there is no invalidation step to get
561
+ * wrong.
562
+ */
563
+ export class ParticleValueNumbering {
564
+ /**
565
+ * @param {number} [slots]
566
+ */
567
+ constructor(slots = PARTICLE_VM_REGISTER_SLOTS) {
568
+ /** @type {number} */
569
+ this.slots = slots;
570
+ /** @type {Int32Array} the value in each slot */
571
+ this.value_id = new Int32Array(slots);
572
+ /** @type {Int32Array} the slot each value was first computed in */
573
+ this.home = new Int32Array(slots);
574
+ /** @type {Int32Array} which publish computed the value in each slot; `-1` for the reset's zero */
575
+ this.producer = new Int32Array(slots).fill(-1);
576
+ /** @type {number} */
577
+ this.next_id = slots;
578
+ /** @type {number} publishes so far, which is what names a producer */
579
+ this.published = 0;
580
+
581
+ // The file starts zeroed on both executors, but every slot is given a DISTINCT number: they
582
+ // hold equal values, not the same value, and nothing is gained by conflating them.
583
+ for (let slot = 0; slot < slots; slot++) {
584
+ this.value_id[slot] = slot;
585
+ this.home[slot] = slot;
586
+ }
587
+ }
588
+
589
+ /**
590
+ * @param {number} base
591
+ * @param {number} span
592
+ * @returns {number[]|null} the numbers in that run, or `null` if it leaves the file
593
+ */
594
+ ids_of(base, span) {
595
+ if (base < 0 || base + span > this.slots) {
596
+ return null;
597
+ }
598
+
599
+ const ids = new Array(span);
600
+
601
+ for (let lane = 0; lane < span; lane++) {
602
+ ids[lane] = this.value_id[base + lane];
603
+ }
604
+
605
+ return ids;
606
+ }
607
+
608
+ /**
609
+ * The numbers a register operand reads, one per lane, through its swizzle.
610
+ *
611
+ * @param {ParticleOperandRun} read a `REG` run
612
+ * @returns {number[]|null} `null` if a lane reaches outside the file
613
+ */
614
+ ids_read(read) {
615
+ const slots = particle_read_slots(read);
616
+
617
+ if (slots.some(slot => slot < 0 || slot >= this.slots)) {
618
+ return null;
619
+ }
620
+
621
+ return slots.map(slot => this.value_id[slot]);
622
+ }
623
+
624
+ /**
625
+ * @param {number} base
626
+ * @param {number[]} ids
627
+ * @returns {boolean} whether that run still holds exactly those values
628
+ */
629
+ holds(base, ids) {
630
+ if (base < 0 || base + ids.length > this.slots) {
631
+ return false;
632
+ }
633
+
634
+ for (let lane = 0; lane < ids.length; lane++) {
635
+ if (this.value_id[base + lane] !== ids[lane]) {
636
+ return false;
637
+ }
638
+ }
639
+
640
+ return true;
641
+ }
642
+
643
+ /**
644
+ * The register operand that reads the same values from where they were first computed.
645
+ *
646
+ * Lane by lane, the value a read sees has a home slot; if every home still holds its value and
647
+ * the homes fit in the four slots a swizzle can reach from one base, the read can be redirected
648
+ * there. That is how a copy is read through, a broadcast folds into the consumer's swizzle, and a
649
+ * value moved between registers stays recognisable as itself.
650
+ *
651
+ * Every lane has to come from ONE instruction. Two producers' results side by side could be named
652
+ * by one swizzled operand too, but folding them there would leave both producers where they are;
653
+ * forwarding one of them into the copy's slot deletes the copy outright, and that is the rewrite
654
+ * the copy-propagation pass wants to have the chance to make.
655
+ *
656
+ * @param {ParticleOperandRun} read a `REG` run
657
+ * @returns {number} a register operand word, or `-1` when the values are not all at home, come from
658
+ * more than one producer, or their homes do not fit one operand
659
+ */
660
+ origin_of_read(read) {
661
+ const slots = particle_read_slots(read);
662
+ const homes = new Array(slots.length);
663
+
664
+ let low = Infinity;
665
+ let high = -Infinity;
666
+ let producer = null;
667
+
668
+ for (let lane = 0; lane < slots.length; lane++) {
669
+ const slot = slots[lane];
670
+
671
+ if (slot < 0 || slot >= this.slots) {
672
+ return -1;
673
+ }
674
+
675
+ const home = this.home[slot];
676
+
677
+ if (this.value_id[home] !== this.value_id[slot]) {
678
+ return -1;
679
+ }
680
+
681
+ if (producer !== null && this.producer[slot] !== producer) {
682
+ return -1;
683
+ }
684
+
685
+ producer = this.producer[slot];
686
+ homes[lane] = home;
687
+ low = Math.min(low, home);
688
+ high = Math.max(high, home);
689
+ }
690
+
691
+ if (high - low >= VM_MAX_WIDTH) {
692
+ return -1;
693
+ }
694
+
695
+ let pattern = 0;
696
+
697
+ for (let lane = 0; lane < VM_MAX_WIDTH; lane++) {
698
+ // Lanes past the read are the identity: they are not read, and the canonical spelling
699
+ // wants them so.
700
+ const selector = lane < homes.length ? homes[lane] - low : lane;
701
+
702
+ pattern |= (selector & 3) << (2 * lane);
703
+ }
704
+
705
+ return vm_register(low, pattern);
706
+ }
707
+
708
+ /**
709
+ * Record what an instruction leaves in its destination.
710
+ *
711
+ * Takes the words as they will be EMITTED rather than as they were read, because a pass may have
712
+ * moved a source or rewritten the opcode by the time it gets here — the state has to describe
713
+ * the program that will run.
714
+ *
715
+ * @param {ArrayLike<number>} words the four words of a single instruction
716
+ */
717
+ publish(words) {
718
+ const instruction = decode_particle_instruction(words);
719
+
720
+ if (instruction.write === null) {
721
+ return;
722
+ }
723
+
724
+ const base = instruction.write.base;
725
+ const span = instruction.write.span;
726
+ const publish = this.published++;
727
+
728
+ /** Gathered before anything is written: a source may alias the destination. */
729
+ const lanes = new Array(span);
730
+
731
+ if (instruction.semantics.broadcasts) {
732
+ // One value in every lane, so lane `i` is a copy of lane 0 and traces back to it — which
733
+ // is what lets a consumer read the scalar instead of the widened form.
734
+ const id = this.next_id++;
735
+
736
+ lanes.fill({ id, home: base, producer: publish });
737
+ } else {
738
+ // A copy carries values only from a REGISTER source. A literal is a value the file never
739
+ // held, so a MOV from one mints — and its word is not the slot it would otherwise be
740
+ // read as.
741
+ const source = instruction.op === VM_OP.MOV && instruction.sources[0].kind === VM_KIND.REG
742
+ ? instruction.sources[0]
743
+ : null;
744
+
745
+ for (let lane = 0; lane < span; lane++) {
746
+ const from = source === null ? -1 : vm_operand_slot(source.word, lane);
747
+
748
+ lanes[lane] = from >= 0 && from < this.slots
749
+ ? { id: this.value_id[from], home: this.home[from], producer: this.producer[from] }
750
+ : { id: this.next_id++, home: base + lane, producer: publish };
751
+ }
752
+ }
753
+
754
+ for (let lane = 0; lane < span; lane++) {
755
+ const slot = base + lane;
756
+
757
+ if (slot < this.slots) {
758
+ this.value_id[slot] = lanes[lane].id;
759
+ this.home[slot] = lanes[lane].home;
760
+ this.producer[slot] = lanes[lane].producer;
761
+ }
762
+ }
763
+ }
764
+ }