redscript-mc 1.2.30 → 2.0.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 (269) hide show
  1. package/.claude/commands/build-test.md +10 -0
  2. package/.claude/commands/deploy-demo.md +12 -0
  3. package/.claude/commands/stage-status.md +13 -0
  4. package/.claude/settings.json +12 -0
  5. package/.github/workflows/ci.yml +1 -0
  6. package/CLAUDE.md +231 -0
  7. package/demo.gif +0 -0
  8. package/dist/cli.js +2 -554
  9. package/dist/compile.js +2 -266
  10. package/dist/index.js +2 -159
  11. package/dist/lowering/index.js +5 -3
  12. package/dist/src/__tests__/cli.test.d.ts +1 -0
  13. package/dist/src/__tests__/cli.test.js +104 -0
  14. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  15. package/dist/src/__tests__/codegen.test.js +152 -0
  16. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  17. package/dist/src/__tests__/compile-all.test.js +108 -0
  18. package/dist/src/__tests__/dce.test.d.ts +1 -0
  19. package/dist/src/__tests__/dce.test.js +102 -0
  20. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  21. package/dist/src/__tests__/diagnostics.test.js +177 -0
  22. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  23. package/dist/src/__tests__/e2e.test.js +1789 -0
  24. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  25. package/dist/src/__tests__/entity-types.test.js +203 -0
  26. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  27. package/dist/src/__tests__/formatter.test.js +40 -0
  28. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  29. package/dist/src/__tests__/lexer.test.js +343 -0
  30. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  31. package/dist/src/__tests__/lowering.test.js +1015 -0
  32. package/dist/src/__tests__/macro.test.d.ts +8 -0
  33. package/dist/src/__tests__/macro.test.js +306 -0
  34. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  35. package/dist/src/__tests__/mc-integration.test.js +817 -0
  36. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  37. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  38. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  39. package/dist/src/__tests__/nbt.test.js +82 -0
  40. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  41. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  42. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  43. package/dist/src/__tests__/optimizer.test.js +149 -0
  44. package/dist/src/__tests__/parser.test.d.ts +1 -0
  45. package/dist/src/__tests__/parser.test.js +807 -0
  46. package/dist/src/__tests__/repl.test.d.ts +1 -0
  47. package/dist/src/__tests__/repl.test.js +27 -0
  48. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  49. package/dist/src/__tests__/runtime.test.js +289 -0
  50. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  51. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  52. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  53. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  54. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  55. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  56. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  57. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  58. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  59. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  60. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  61. package/dist/src/__tests__/typechecker.test.js +552 -0
  62. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  63. package/dist/src/__tests__/var-allocator.test.js +69 -0
  64. package/dist/src/ast/types.d.ts +515 -0
  65. package/dist/src/ast/types.js +9 -0
  66. package/dist/src/builtins/metadata.d.ts +36 -0
  67. package/dist/src/builtins/metadata.js +1014 -0
  68. package/dist/src/cli.d.ts +11 -0
  69. package/dist/src/cli.js +443 -0
  70. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  71. package/dist/src/codegen/cmdblock/index.js +45 -0
  72. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  73. package/dist/src/codegen/mcfunction/index.js +606 -0
  74. package/dist/src/codegen/structure/index.d.ts +24 -0
  75. package/dist/src/codegen/structure/index.js +279 -0
  76. package/dist/src/codegen/var-allocator.d.ts +45 -0
  77. package/dist/src/codegen/var-allocator.js +104 -0
  78. package/dist/src/compile.d.ts +37 -0
  79. package/dist/src/compile.js +165 -0
  80. package/dist/src/diagnostics/index.d.ts +44 -0
  81. package/dist/src/diagnostics/index.js +140 -0
  82. package/dist/src/events/types.d.ts +35 -0
  83. package/dist/src/events/types.js +59 -0
  84. package/dist/src/formatter/index.d.ts +1 -0
  85. package/dist/src/formatter/index.js +26 -0
  86. package/dist/src/index.d.ts +22 -0
  87. package/dist/src/index.js +45 -0
  88. package/dist/src/ir/builder.d.ts +33 -0
  89. package/dist/src/ir/builder.js +99 -0
  90. package/dist/src/ir/types.d.ts +132 -0
  91. package/dist/src/ir/types.js +15 -0
  92. package/dist/src/lexer/index.d.ts +37 -0
  93. package/dist/src/lexer/index.js +569 -0
  94. package/dist/src/lowering/index.d.ts +188 -0
  95. package/dist/src/lowering/index.js +3405 -0
  96. package/dist/src/mc-test/client.d.ts +128 -0
  97. package/dist/src/mc-test/client.js +174 -0
  98. package/dist/src/mc-test/runner.d.ts +28 -0
  99. package/dist/src/mc-test/runner.js +151 -0
  100. package/dist/src/mc-test/setup.d.ts +11 -0
  101. package/dist/src/mc-test/setup.js +98 -0
  102. package/dist/src/mc-validator/index.d.ts +17 -0
  103. package/dist/src/mc-validator/index.js +322 -0
  104. package/dist/src/nbt/index.d.ts +86 -0
  105. package/dist/src/nbt/index.js +250 -0
  106. package/dist/src/optimizer/commands.d.ts +38 -0
  107. package/dist/src/optimizer/commands.js +451 -0
  108. package/dist/src/optimizer/dce.d.ts +34 -0
  109. package/dist/src/optimizer/dce.js +639 -0
  110. package/dist/src/optimizer/passes.d.ts +34 -0
  111. package/dist/src/optimizer/passes.js +243 -0
  112. package/dist/src/optimizer/structure.d.ts +9 -0
  113. package/dist/src/optimizer/structure.js +356 -0
  114. package/dist/src/parser/index.d.ts +93 -0
  115. package/dist/src/parser/index.js +1687 -0
  116. package/dist/src/repl.d.ts +16 -0
  117. package/dist/src/repl.js +165 -0
  118. package/dist/src/runtime/index.d.ts +107 -0
  119. package/dist/src/runtime/index.js +1409 -0
  120. package/dist/src/typechecker/index.d.ts +61 -0
  121. package/dist/src/typechecker/index.js +1034 -0
  122. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  123. package/dist/src/types/entity-hierarchy.js +107 -0
  124. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  125. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  126. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  127. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  128. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  129. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  130. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  131. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  132. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  133. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  134. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  135. package/dist/src2/__tests__/lir/types.test.js +185 -0
  136. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  137. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  138. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  139. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  140. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  141. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  142. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  143. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  144. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  145. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  146. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  147. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  148. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  149. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  150. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  151. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  152. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  153. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  154. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  155. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  156. package/dist/src2/emit/compile.d.ts +19 -0
  157. package/dist/src2/emit/compile.js +80 -0
  158. package/dist/src2/emit/index.d.ts +17 -0
  159. package/dist/src2/emit/index.js +172 -0
  160. package/dist/src2/hir/lower.d.ts +15 -0
  161. package/dist/src2/hir/lower.js +378 -0
  162. package/dist/src2/hir/types.d.ts +373 -0
  163. package/dist/src2/hir/types.js +16 -0
  164. package/dist/src2/lir/lower.d.ts +15 -0
  165. package/dist/src2/lir/lower.js +453 -0
  166. package/dist/src2/lir/types.d.ts +136 -0
  167. package/dist/src2/lir/types.js +11 -0
  168. package/dist/src2/lir/verify.d.ts +14 -0
  169. package/dist/src2/lir/verify.js +113 -0
  170. package/dist/src2/mir/lower.d.ts +9 -0
  171. package/dist/src2/mir/lower.js +1030 -0
  172. package/dist/src2/mir/macro.d.ts +22 -0
  173. package/dist/src2/mir/macro.js +168 -0
  174. package/dist/src2/mir/types.d.ts +183 -0
  175. package/dist/src2/mir/types.js +11 -0
  176. package/dist/src2/mir/verify.d.ts +16 -0
  177. package/dist/src2/mir/verify.js +216 -0
  178. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  179. package/dist/src2/optimizer/block_merge.js +84 -0
  180. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  181. package/dist/src2/optimizer/branch_simplify.js +28 -0
  182. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  183. package/dist/src2/optimizer/constant_fold.js +85 -0
  184. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  185. package/dist/src2/optimizer/copy_prop.js +113 -0
  186. package/dist/src2/optimizer/dce.d.ts +8 -0
  187. package/dist/src2/optimizer/dce.js +155 -0
  188. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  189. package/dist/src2/optimizer/pipeline.js +42 -0
  190. package/dist/tsconfig.tsbuildinfo +1 -0
  191. package/docs/compiler-pipeline-redesign.md +2243 -0
  192. package/docs/optimization-ideas.md +1076 -0
  193. package/editors/vscode/package-lock.json +3 -3
  194. package/editors/vscode/package.json +1 -1
  195. package/jest.config.js +1 -1
  196. package/package.json +6 -5
  197. package/scripts/postbuild.js +15 -0
  198. package/src/__tests__/cli.test.ts +8 -220
  199. package/src/__tests__/dce.test.ts +11 -56
  200. package/src/__tests__/diagnostics.test.ts +59 -38
  201. package/src/__tests__/mc-integration.test.ts +1 -2
  202. package/src/ast/types.ts +6 -1
  203. package/src/cli.ts +29 -156
  204. package/src/compile.ts +6 -162
  205. package/src/index.ts +14 -178
  206. package/src/mc-test/runner.ts +4 -3
  207. package/src/parser/index.ts +1 -1
  208. package/src/repl.ts +1 -1
  209. package/src/runtime/index.ts +1 -1
  210. package/src2/__tests__/e2e/basic.test.ts +154 -0
  211. package/src2/__tests__/e2e/macros.test.ts +199 -0
  212. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  213. package/src2/__tests__/hir/desugar.test.ts +263 -0
  214. package/src2/__tests__/lir/lower.test.ts +619 -0
  215. package/src2/__tests__/lir/types.test.ts +207 -0
  216. package/src2/__tests__/lir/verify.test.ts +249 -0
  217. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  218. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  219. package/src2/__tests__/mir/verify.test.ts +254 -0
  220. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  221. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  222. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  223. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  224. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  225. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  226. package/src2/emit/compile.ts +99 -0
  227. package/src2/emit/index.ts +222 -0
  228. package/src2/hir/lower.ts +428 -0
  229. package/src2/hir/types.ts +216 -0
  230. package/src2/lir/lower.ts +556 -0
  231. package/src2/lir/types.ts +109 -0
  232. package/src2/lir/verify.ts +129 -0
  233. package/src2/mir/lower.ts +1160 -0
  234. package/src2/mir/macro.ts +167 -0
  235. package/src2/mir/types.ts +106 -0
  236. package/src2/mir/verify.ts +218 -0
  237. package/src2/optimizer/block_merge.ts +93 -0
  238. package/src2/optimizer/branch_simplify.ts +27 -0
  239. package/src2/optimizer/constant_fold.ts +88 -0
  240. package/src2/optimizer/copy_prop.ts +106 -0
  241. package/src2/optimizer/dce.ts +133 -0
  242. package/src2/optimizer/pipeline.ts +44 -0
  243. package/tsconfig.json +2 -2
  244. package/src/__tests__/codegen.test.ts +0 -161
  245. package/src/__tests__/e2e.test.ts +0 -2039
  246. package/src/__tests__/entity-types.test.ts +0 -236
  247. package/src/__tests__/lowering.test.ts +0 -1185
  248. package/src/__tests__/macro.test.ts +0 -343
  249. package/src/__tests__/nbt.test.ts +0 -58
  250. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  251. package/src/__tests__/optimizer.test.ts +0 -162
  252. package/src/__tests__/runtime.test.ts +0 -305
  253. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  254. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  255. package/src/__tests__/stdlib-math.test.ts +0 -374
  256. package/src/__tests__/stdlib-vec.test.ts +0 -259
  257. package/src/__tests__/structure-optimizer.test.ts +0 -38
  258. package/src/__tests__/var-allocator.test.ts +0 -75
  259. package/src/codegen/cmdblock/index.ts +0 -63
  260. package/src/codegen/mcfunction/index.ts +0 -662
  261. package/src/codegen/structure/index.ts +0 -346
  262. package/src/codegen/var-allocator.ts +0 -104
  263. package/src/ir/builder.ts +0 -116
  264. package/src/ir/types.ts +0 -134
  265. package/src/lowering/index.ts +0 -3876
  266. package/src/optimizer/commands.ts +0 -534
  267. package/src/optimizer/dce.ts +0 -679
  268. package/src/optimizer/passes.ts +0 -250
  269. package/src/optimizer/structure.ts +0 -450
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ /**
3
+ * Optimization passes over IR.
4
+ *
5
+ * Each pass: IRFunction → IRFunction (pure transformation)
6
+ *
7
+ * Pipeline order:
8
+ * 1. constantFolding — evaluate constant expressions at compile time
9
+ * 2. copyPropagation — eliminate redundant copies
10
+ * 3. deadCodeElimination — remove unused assignments
11
+ * 4. commandMerging — MC-specific: merge chained execute conditions
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.defaultPipeline = void 0;
15
+ exports.constantFolding = constantFolding;
16
+ exports.constantFoldingWithStats = constantFoldingWithStats;
17
+ exports.copyPropagation = copyPropagation;
18
+ exports.deadCodeElimination = deadCodeElimination;
19
+ exports.deadCodeEliminationWithStats = deadCodeEliminationWithStats;
20
+ exports.optimize = optimize;
21
+ exports.optimizeWithStats = optimizeWithStats;
22
+ const commands_1 = require("./commands");
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+ function isConst(op) {
27
+ return op.kind === 'const';
28
+ }
29
+ function evalBinop(lhs, bop, rhs) {
30
+ switch (bop) {
31
+ case '+': return lhs + rhs;
32
+ case '-': return lhs - rhs;
33
+ case '*': return lhs * rhs;
34
+ case '/': return rhs === 0 ? null : Math.trunc(lhs / rhs); // MC uses truncated int division
35
+ case '%': return rhs === 0 ? null : lhs % rhs;
36
+ default: return null;
37
+ }
38
+ }
39
+ function evalCmp(lhs, cop, rhs) {
40
+ switch (cop) {
41
+ case '==': return lhs === rhs ? 1 : 0;
42
+ case '!=': return lhs !== rhs ? 1 : 0;
43
+ case '<': return lhs < rhs ? 1 : 0;
44
+ case '<=': return lhs <= rhs ? 1 : 0;
45
+ case '>': return lhs > rhs ? 1 : 0;
46
+ case '>=': return lhs >= rhs ? 1 : 0;
47
+ default: return 0;
48
+ }
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Pass 1: Constant Folding
52
+ // Evaluates expressions with all-constant operands at compile time.
53
+ // ---------------------------------------------------------------------------
54
+ function constantFolding(fn) {
55
+ return constantFoldingWithStats(fn).fn;
56
+ }
57
+ function constantFoldingWithStats(fn) {
58
+ let folded = 0;
59
+ const newBlocks = fn.blocks.map(block => {
60
+ const newInstrs = [];
61
+ for (const instr of block.instrs) {
62
+ if (instr.op === 'binop' && isConst(instr.lhs) && isConst(instr.rhs)) {
63
+ const result = evalBinop(instr.lhs.value, instr.bop, instr.rhs.value);
64
+ if (result !== null) {
65
+ folded++;
66
+ newInstrs.push({ op: 'assign', dst: instr.dst, src: { kind: 'const', value: result } });
67
+ continue;
68
+ }
69
+ }
70
+ if (instr.op === 'cmp' && isConst(instr.lhs) && isConst(instr.rhs)) {
71
+ const result = evalCmp(instr.lhs.value, instr.cop, instr.rhs.value);
72
+ folded++;
73
+ newInstrs.push({ op: 'assign', dst: instr.dst, src: { kind: 'const', value: result } });
74
+ continue;
75
+ }
76
+ newInstrs.push(instr);
77
+ }
78
+ return { ...block, instrs: newInstrs };
79
+ });
80
+ return { fn: { ...fn, blocks: newBlocks }, stats: { constantFolds: folded } };
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // Pass 2: Copy Propagation
84
+ // Replaces uses of variables that are just copies with their source.
85
+ // e.g. t0 = x; y = t0 + 1 → y = x + 1
86
+ // ---------------------------------------------------------------------------
87
+ function copyPropagation(fn) {
88
+ // Build copy map within each block (single-block analysis for simplicity)
89
+ const newBlocks = fn.blocks.map(block => {
90
+ const copies = new Map(); // var → its source if it's a copy
91
+ function resolve(op) {
92
+ if (op.kind !== 'var')
93
+ return op;
94
+ return copies.get(op.name) ?? op;
95
+ }
96
+ /**
97
+ * Invalidate all copies that became stale because `written` was modified.
98
+ * When $y is overwritten, any mapping copies[$tmp] = $y is now stale:
99
+ * reading $tmp would return the OLD $y value via the copy, but $y now holds
100
+ * a different value. Remove both the direct entry (copies[$y]) and every
101
+ * reverse entry that points at $y.
102
+ */
103
+ function invalidate(written) {
104
+ copies.delete(written);
105
+ for (const [k, v] of copies) {
106
+ if (v.kind === 'var' && v.name === written)
107
+ copies.delete(k);
108
+ }
109
+ }
110
+ const newInstrs = [];
111
+ for (const instr of block.instrs) {
112
+ switch (instr.op) {
113
+ case 'assign': {
114
+ const src = resolve(instr.src);
115
+ invalidate(instr.dst);
116
+ // Only propagate scalars (var or const), not storage
117
+ if (src.kind === 'var' || src.kind === 'const') {
118
+ copies.set(instr.dst, src);
119
+ }
120
+ newInstrs.push({ ...instr, src });
121
+ break;
122
+ }
123
+ case 'binop':
124
+ invalidate(instr.dst);
125
+ newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) });
126
+ break;
127
+ case 'cmp':
128
+ invalidate(instr.dst);
129
+ newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) });
130
+ break;
131
+ case 'call':
132
+ if (instr.dst)
133
+ invalidate(instr.dst);
134
+ newInstrs.push({ ...instr, args: instr.args.map(resolve) });
135
+ break;
136
+ default:
137
+ newInstrs.push(instr);
138
+ }
139
+ }
140
+ return { ...block, instrs: newInstrs };
141
+ });
142
+ return { ...fn, blocks: newBlocks };
143
+ }
144
+ // ---------------------------------------------------------------------------
145
+ // Pass 3: Dead Code Elimination
146
+ // Removes assignments to variables that are never read afterward.
147
+ // ---------------------------------------------------------------------------
148
+ function deadCodeElimination(fn) {
149
+ return deadCodeEliminationWithStats(fn).fn;
150
+ }
151
+ function deadCodeEliminationWithStats(fn) {
152
+ // Collect all reads across all blocks
153
+ const readVars = new Set();
154
+ function markRead(op) {
155
+ if (op.kind === 'var')
156
+ readVars.add(op.name);
157
+ }
158
+ function markRawReads(cmd) {
159
+ for (const match of cmd.matchAll(/\$[A-Za-z0-9_]+/g)) {
160
+ readVars.add(match[0]);
161
+ }
162
+ }
163
+ for (const block of fn.blocks) {
164
+ for (const instr of block.instrs) {
165
+ if (instr.op === 'binop') {
166
+ markRead(instr.lhs);
167
+ markRead(instr.rhs);
168
+ }
169
+ if (instr.op === 'cmp') {
170
+ markRead(instr.lhs);
171
+ markRead(instr.rhs);
172
+ }
173
+ if (instr.op === 'call') {
174
+ instr.args.forEach(markRead);
175
+ }
176
+ if (instr.op === 'assign') {
177
+ markRead(instr.src);
178
+ }
179
+ if (instr.op === 'raw') {
180
+ markRawReads(instr.cmd);
181
+ }
182
+ }
183
+ // Terminator reads
184
+ const t = block.term;
185
+ if (t.op === 'jump_if' || t.op === 'jump_unless')
186
+ readVars.add(t.cond);
187
+ if (t.op === 'return' && t.value)
188
+ markRead(t.value);
189
+ if (t.op === 'tick_yield') { /* no reads */ }
190
+ }
191
+ // Also keep params and globals
192
+ fn.params.forEach(p => readVars.add(p));
193
+ let removed = 0;
194
+ const newBlocks = fn.blocks.map(block => ({
195
+ ...block,
196
+ instrs: block.instrs.filter(instr => {
197
+ // Only assignments/binops/cmps with an unused dst are candidates for removal
198
+ if (instr.op === 'assign' || instr.op === 'binop' || instr.op === 'cmp') {
199
+ // Always keep assignments to global variables (they may be read by other functions)
200
+ // Temps are $t0, $t1, ...; params are $p0, $p1, ...; locals are $_0, $_1, ...
201
+ // Everything else is a potential global
202
+ const isTemp = /^\$t\d+$/.test(instr.dst) || /^\$p\d+$/.test(instr.dst) || /^\$_\d+$/.test(instr.dst);
203
+ const keep = !isTemp || readVars.has(instr.dst);
204
+ if (!keep)
205
+ removed++;
206
+ return keep;
207
+ }
208
+ // calls may have side effects — keep them always
209
+ return true;
210
+ }),
211
+ }));
212
+ return { fn: { ...fn, blocks: newBlocks }, stats: { deadCodeRemoved: removed } };
213
+ }
214
+ exports.defaultPipeline = [
215
+ { name: 'constant-folding', run: constantFolding },
216
+ { name: 'copy-propagation', run: copyPropagation },
217
+ { name: 'dead-code-elimination', run: deadCodeElimination },
218
+ // commandMerging is applied during codegen (MC-specific)
219
+ ];
220
+ function optimize(fn, passes = exports.defaultPipeline) {
221
+ return optimizeWithStats(fn, passes).fn;
222
+ }
223
+ function optimizeWithStats(fn, passes = exports.defaultPipeline) {
224
+ let current = fn;
225
+ const stats = (0, commands_1.createEmptyOptimizationStats)();
226
+ for (const pass of passes) {
227
+ if (pass.name === 'constant-folding') {
228
+ const result = constantFoldingWithStats(current);
229
+ current = result.fn;
230
+ (0, commands_1.mergeOptimizationStats)(stats, result.stats);
231
+ continue;
232
+ }
233
+ if (pass.name === 'dead-code-elimination') {
234
+ const result = deadCodeEliminationWithStats(current);
235
+ current = result.fn;
236
+ (0, commands_1.mergeOptimizationStats)(stats, result.stats);
237
+ continue;
238
+ }
239
+ current = pass.run(current);
240
+ }
241
+ return { fn: current, stats };
242
+ }
243
+ //# sourceMappingURL=passes.js.map
@@ -0,0 +1,9 @@
1
+ import type { IRCommand, IRFunction } from '../ir/types';
2
+ import { type OptimizationStats } from './commands';
3
+ export declare function setStructureObjective(obj: string): void;
4
+ export declare function optimizeFunctionForStructure(fn: IRFunction, functions: Map<string, IRFunction>, namespace: string): IRCommand[];
5
+ export declare function optimizeForStructure(functions: IRFunction[], namespace?: string): IRFunction[];
6
+ export declare function optimizeForStructureWithStats(functions: IRFunction[], namespace?: string): {
7
+ functions: IRFunction[];
8
+ stats: OptimizationStats;
9
+ };
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setStructureObjective = setStructureObjective;
4
+ exports.optimizeFunctionForStructure = optimizeFunctionForStructure;
5
+ exports.optimizeForStructure = optimizeForStructure;
6
+ exports.optimizeForStructureWithStats = optimizeForStructureWithStats;
7
+ const commands_1 = require("./commands");
8
+ let OBJ = 'rs';
9
+ function setStructureObjective(obj) {
10
+ OBJ = obj;
11
+ (0, commands_1.setOptimizerObjective)(obj);
12
+ }
13
+ const INLINE_THRESHOLD = 8;
14
+ const BOP_OP = {
15
+ '+': '+=',
16
+ '-': '-=',
17
+ '*': '*=',
18
+ '/': '/=',
19
+ '%': '%=',
20
+ };
21
+ function varRef(name) {
22
+ return name.startsWith('$') ? name : `$${name}`;
23
+ }
24
+ function operandToScore(op) {
25
+ if (op.kind === 'var')
26
+ return `${varRef(op.name)} ${OBJ}`;
27
+ if (op.kind === 'const')
28
+ return `$const_${op.value} ${OBJ}`;
29
+ if (op.kind === 'param')
30
+ return `$p${op.index} ${OBJ}`;
31
+ throw new Error(`Cannot convert storage operand to score: ${op.path}`);
32
+ }
33
+ function emitInstr(instr, namespace) {
34
+ const commands = [];
35
+ switch (instr.op) {
36
+ case 'assign':
37
+ if (instr.src.kind === 'const') {
38
+ commands.push({ cmd: `scoreboard players set ${varRef(instr.dst)} ${OBJ} ${instr.src.value}` });
39
+ }
40
+ else if (instr.src.kind === 'var') {
41
+ commands.push({
42
+ cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = ${varRef(instr.src.name)} ${OBJ}`,
43
+ });
44
+ }
45
+ else if (instr.src.kind === 'param') {
46
+ commands.push({
47
+ cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = $p${instr.src.index} ${OBJ}`,
48
+ });
49
+ }
50
+ else {
51
+ commands.push({
52
+ cmd: `execute store result score ${varRef(instr.dst)} ${OBJ} run data get storage ${instr.src.path}`,
53
+ });
54
+ }
55
+ break;
56
+ case 'binop':
57
+ commands.push(...emitInstr({ op: 'assign', dst: instr.dst, src: instr.lhs }, namespace));
58
+ commands.push({
59
+ cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} ${BOP_OP[instr.bop]} ${operandToScore(instr.rhs)}`,
60
+ });
61
+ break;
62
+ case 'cmp': {
63
+ const dst = varRef(instr.dst);
64
+ const lhs = operandToScore(instr.lhs);
65
+ const rhs = operandToScore(instr.rhs);
66
+ commands.push({ cmd: `scoreboard players set ${dst} ${OBJ} 0` });
67
+ const op = instr.cop === '==' ? 'if score' :
68
+ instr.cop === '!=' ? 'unless score' :
69
+ instr.cop === '<' ? 'if score' :
70
+ instr.cop === '<=' ? 'if score' :
71
+ instr.cop === '>' ? 'if score' :
72
+ 'if score';
73
+ const cmp = instr.cop === '==' || instr.cop === '!=' ? '=' :
74
+ instr.cop;
75
+ commands.push({
76
+ cmd: `execute ${op} ${lhs} ${cmp} ${rhs} run scoreboard players set ${dst} ${OBJ} 1`,
77
+ });
78
+ break;
79
+ }
80
+ case 'call':
81
+ for (let i = 0; i < instr.args.length; i++) {
82
+ commands.push(...emitInstr({ op: 'assign', dst: `$p${i}`, src: instr.args[i] }, namespace));
83
+ }
84
+ commands.push({ cmd: `function ${namespace}:${instr.fn}` });
85
+ if (instr.dst) {
86
+ commands.push({
87
+ cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = $ret ${OBJ}`,
88
+ });
89
+ }
90
+ break;
91
+ case 'raw':
92
+ commands.push({ cmd: instr.cmd });
93
+ break;
94
+ }
95
+ return commands;
96
+ }
97
+ function emitReturn(term) {
98
+ const commands = [];
99
+ if (term.value) {
100
+ commands.push(...emitInstr({ op: 'assign', dst: '$ret', src: term.value }, ''));
101
+ }
102
+ if (term.value?.kind === 'const') {
103
+ commands.push({ cmd: `return ${term.value.value}` });
104
+ }
105
+ else if (term.value?.kind === 'var') {
106
+ commands.push({ cmd: `return run scoreboard players get ${varRef(term.value.name)} ${OBJ}` });
107
+ }
108
+ return commands;
109
+ }
110
+ function markConditional(commands) {
111
+ return commands.map(command => ({
112
+ ...command,
113
+ conditional: true,
114
+ }));
115
+ }
116
+ function cloneVisited(visited) {
117
+ return new Set(visited);
118
+ }
119
+ function isRecursiveCommand(command, currentFn, namespace) {
120
+ return command.includes(`function ${namespace}:${currentFn}`);
121
+ }
122
+ function getInlineableBlock(block, currentFn, namespace) {
123
+ if (!block)
124
+ return null;
125
+ if (block.term.op === 'jump_if' || block.term.op === 'jump_unless' || block.term.op === 'tick_yield') {
126
+ return null;
127
+ }
128
+ const commands = block.instrs.flatMap(instr => emitInstr(instr, namespace));
129
+ if (commands.some(command => isRecursiveCommand(command.cmd, currentFn, namespace))) {
130
+ return null;
131
+ }
132
+ if (block.term.op === 'return') {
133
+ commands.push(...emitReturn(block.term));
134
+ }
135
+ if (commands.length > INLINE_THRESHOLD) {
136
+ return null;
137
+ }
138
+ return {
139
+ commands,
140
+ continuation: block.term.op === 'jump' ? block.term.target : undefined,
141
+ };
142
+ }
143
+ function flattenBlock(fn, label, namespace, visited) {
144
+ const blockMap = new Map(fn.blocks.map(block => [block.label, block]));
145
+ const block = blockMap.get(label);
146
+ if (!block) {
147
+ return [];
148
+ }
149
+ if (visited.has(label)) {
150
+ return [{ cmd: `function ${namespace}:${fn.name}/${label}`, label }];
151
+ }
152
+ visited.add(label);
153
+ const commands = [];
154
+ if (label === fn.blocks[0]?.label) {
155
+ for (let i = 0; i < fn.params.length; i++) {
156
+ commands.push({
157
+ cmd: `scoreboard players operation ${varRef(fn.params[i])} ${OBJ} = $p${i} ${OBJ}`,
158
+ });
159
+ }
160
+ }
161
+ commands.push(...block.instrs.flatMap(instr => emitInstr(instr, namespace)));
162
+ const term = block.term;
163
+ switch (term.op) {
164
+ case 'jump':
165
+ commands.push(...flattenBlock(fn, term.target, namespace, visited));
166
+ return commands;
167
+ case 'jump_if':
168
+ case 'jump_unless': {
169
+ const trueLabel = term.op === 'jump_if' ? term.then : term.else_;
170
+ const falseLabel = term.op === 'jump_if' ? term.else_ : term.then;
171
+ const trueRange = term.op === 'jump_if' ? '1..' : '..0';
172
+ const falseRange = term.op === 'jump_if' ? '..0' : '1..';
173
+ const trueBlock = getInlineableBlock(blockMap.get(trueLabel), fn.name, namespace);
174
+ const falseBlock = getInlineableBlock(blockMap.get(falseLabel), fn.name, namespace);
175
+ if (trueBlock && falseBlock) {
176
+ if (trueBlock.commands.length > 0) {
177
+ commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${trueRange}`, label: trueLabel });
178
+ commands.push(...markConditional(trueBlock.commands));
179
+ }
180
+ if (falseBlock.commands.length > 0) {
181
+ commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${falseRange}`, label: falseLabel });
182
+ commands.push(...markConditional(falseBlock.commands));
183
+ }
184
+ const continuation = trueBlock.continuation && trueBlock.continuation === falseBlock.continuation
185
+ ? trueBlock.continuation
186
+ : undefined;
187
+ if (continuation) {
188
+ commands.push(...flattenBlock(fn, continuation, namespace, cloneVisited(visited)));
189
+ }
190
+ return commands;
191
+ }
192
+ commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${trueRange} run function ${namespace}:${fn.name}/${trueLabel}` });
193
+ commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${falseRange} run function ${namespace}:${fn.name}/${falseLabel}` });
194
+ return commands;
195
+ }
196
+ case 'return':
197
+ commands.push(...emitReturn(term));
198
+ return commands;
199
+ case 'tick_yield':
200
+ commands.push({ cmd: `schedule function ${namespace}:${fn.name}/${term.continuation} 1t replace` });
201
+ return commands;
202
+ }
203
+ }
204
+ function findVars(command) {
205
+ return Array.from(command.matchAll(/\$[A-Za-z0-9_]+/g), match => match[0]);
206
+ }
207
+ function parsePureWrite(command) {
208
+ let match = command.match(/^scoreboard players set (\$[A-Za-z0-9_]+) rs -?\d+$/);
209
+ if (match) {
210
+ return { dst: match[1], reads: [] };
211
+ }
212
+ match = command.match(/^scoreboard players operation (\$[A-Za-z0-9_]+) rs = (\$[A-Za-z0-9_]+) rs$/);
213
+ if (match) {
214
+ return { dst: match[1], reads: [match[2]] };
215
+ }
216
+ match = command.match(/^execute .* run scoreboard players set (\$[A-Za-z0-9_]+) rs -?\d+$/);
217
+ if (match) {
218
+ return {
219
+ dst: match[1],
220
+ reads: findVars(command).filter(name => name !== match[1]),
221
+ };
222
+ }
223
+ return null;
224
+ }
225
+ function deadStoreEliminate(commands) {
226
+ const live = new Set();
227
+ const kept = [];
228
+ for (let i = commands.length - 1; i >= 0; i--) {
229
+ const command = commands[i];
230
+ const pureWrite = parsePureWrite(command.cmd);
231
+ if (pureWrite) {
232
+ pureWrite.reads.forEach(name => live.add(name));
233
+ if (!live.has(pureWrite.dst)) {
234
+ continue;
235
+ }
236
+ live.delete(pureWrite.dst);
237
+ kept.push(command);
238
+ continue;
239
+ }
240
+ findVars(command.cmd).forEach(name => live.add(name));
241
+ kept.push(command);
242
+ }
243
+ return kept.reverse();
244
+ }
245
+ function isInlineableFunction(fn, currentFn, namespace) {
246
+ if (!fn?.commands || fn.name === currentFn || fn.commands.length > INLINE_THRESHOLD) {
247
+ return false;
248
+ }
249
+ return !fn.commands.some(command => isRecursiveCommand(command.cmd, currentFn, namespace) ||
250
+ isRecursiveCommand(command.cmd, fn.name, namespace));
251
+ }
252
+ function inlineConditionalCalls(commands, functions, currentFn, namespace) {
253
+ const optimized = [];
254
+ for (const command of commands) {
255
+ const match = command.cmd.match(/^(execute .+) run function ([^:]+):(.+)$/);
256
+ if (!match || match[2] !== namespace) {
257
+ optimized.push(command);
258
+ continue;
259
+ }
260
+ const target = functions.get(match[3]);
261
+ if (!isInlineableFunction(target, currentFn, namespace)) {
262
+ optimized.push(command);
263
+ continue;
264
+ }
265
+ optimized.push({ cmd: match[1], label: command.label });
266
+ optimized.push(...markConditional(target.commands));
267
+ }
268
+ return optimized;
269
+ }
270
+ function invertExecuteCondition(command) {
271
+ if (command.startsWith('execute if ')) {
272
+ return command.replace(/^execute if /, 'execute unless ');
273
+ }
274
+ if (command.startsWith('execute unless ')) {
275
+ return command.replace(/^execute unless /, 'execute if ');
276
+ }
277
+ return null;
278
+ }
279
+ function eliminateBranchVariables(commands, functions, currentFn, namespace) {
280
+ const optimized = [];
281
+ for (let i = 0; i < commands.length; i++) {
282
+ const init = commands[i];
283
+ const set = commands[i + 1];
284
+ const thenCmd = commands[i + 2];
285
+ const elseCmd = commands[i + 3];
286
+ const initMatch = init?.cmd.match(/^scoreboard players set (\$[A-Za-z0-9_]+) rs 0$/);
287
+ const setMatch = set?.cmd.match(/^((?:execute if|execute unless) .+) run scoreboard players set (\$[A-Za-z0-9_]+) rs 1$/);
288
+ const thenMatch = thenCmd?.cmd.match(/^execute if score (\$[A-Za-z0-9_]+) rs matches 1\.\. run function [^:]+:(.+)$/);
289
+ const elseMatch = elseCmd?.cmd.match(/^execute if score (\$[A-Za-z0-9_]+) rs matches ..0 run function [^:]+:(.+)$/) ??
290
+ elseCmd?.cmd.match(/^execute unless score (\$[A-Za-z0-9_]+) rs matches 1\.\. run function [^:]+:(.+)$/);
291
+ if (!initMatch || !setMatch || !thenMatch || !elseMatch) {
292
+ optimized.push(init);
293
+ continue;
294
+ }
295
+ const branchVar = initMatch[1];
296
+ if (setMatch[2] !== branchVar || thenMatch[1] !== branchVar || elseMatch[1] !== branchVar) {
297
+ optimized.push(init);
298
+ continue;
299
+ }
300
+ const thenFn = functions.get(thenMatch[2]);
301
+ const elseFn = functions.get(elseMatch[2]);
302
+ if (!isInlineableFunction(thenFn, currentFn, namespace) || !isInlineableFunction(elseFn, currentFn, namespace)) {
303
+ optimized.push(init);
304
+ continue;
305
+ }
306
+ const thenCondition = setMatch[1];
307
+ const elseCondition = invertExecuteCondition(thenCondition);
308
+ if (!elseCondition) {
309
+ optimized.push(init);
310
+ continue;
311
+ }
312
+ optimized.push({ cmd: thenCondition });
313
+ optimized.push(...markConditional(thenFn.commands));
314
+ if (elseFn.commands.length > 0) {
315
+ optimized.push({ cmd: elseCondition });
316
+ optimized.push(...markConditional(elseFn.commands));
317
+ }
318
+ i += 3;
319
+ }
320
+ return optimized;
321
+ }
322
+ function optimizeFunctionForStructure(fn, functions, namespace) {
323
+ if (fn.blocks.length === 0) {
324
+ return [];
325
+ }
326
+ const linear = flattenBlock(fn, fn.blocks[0].label, namespace, new Set());
327
+ const branchEliminated = eliminateBranchVariables(linear, functions, fn.name, namespace);
328
+ const inlined = inlineConditionalCalls(branchEliminated, functions, fn.name, namespace);
329
+ return deadStoreEliminate(inlined);
330
+ }
331
+ function optimizeForStructure(functions, namespace = 'redscript') {
332
+ return optimizeForStructureWithStats(functions, namespace).functions;
333
+ }
334
+ function optimizeForStructureWithStats(functions, namespace = 'redscript') {
335
+ const staged = new Map(functions.map(fn => [fn.name, { ...fn }]));
336
+ for (const fn of staged.values()) {
337
+ fn.commands = flattenBlock(fn, fn.blocks[0]?.label ?? 'entry', namespace, new Set());
338
+ }
339
+ for (const fn of staged.values()) {
340
+ fn.commands = optimizeFunctionForStructure(fn, staged, namespace);
341
+ }
342
+ const optimizedCommands = (0, commands_1.optimizeCommandFunctions)(Array.from(staged.values()).map(fn => ({
343
+ name: fn.name,
344
+ commands: fn.commands ?? [],
345
+ })));
346
+ const stats = (0, commands_1.createEmptyOptimizationStats)();
347
+ (0, commands_1.mergeOptimizationStats)(stats, optimizedCommands.stats);
348
+ return {
349
+ functions: Array.from(staged.values()).map(fn => ({
350
+ ...fn,
351
+ commands: optimizedCommands.functions.find(candidate => candidate.name === fn.name)?.commands ?? fn.commands,
352
+ })),
353
+ stats,
354
+ };
355
+ }
356
+ //# sourceMappingURL=structure.js.map