redscript-mc 1.2.29 → 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 (274) 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/README.md +29 -28
  8. package/README.zh.md +28 -28
  9. package/demo.gif +0 -0
  10. package/dist/cli.js +2 -554
  11. package/dist/compile.js +2 -266
  12. package/dist/index.js +2 -159
  13. package/dist/lexer/index.js +9 -1
  14. package/dist/lowering/index.js +22 -5
  15. package/dist/src/__tests__/cli.test.d.ts +1 -0
  16. package/dist/src/__tests__/cli.test.js +104 -0
  17. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  18. package/dist/src/__tests__/codegen.test.js +152 -0
  19. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  20. package/dist/src/__tests__/compile-all.test.js +108 -0
  21. package/dist/src/__tests__/dce.test.d.ts +1 -0
  22. package/dist/src/__tests__/dce.test.js +102 -0
  23. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  24. package/dist/src/__tests__/diagnostics.test.js +177 -0
  25. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  26. package/dist/src/__tests__/e2e.test.js +1789 -0
  27. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  28. package/dist/src/__tests__/entity-types.test.js +203 -0
  29. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  30. package/dist/src/__tests__/formatter.test.js +40 -0
  31. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  32. package/dist/src/__tests__/lexer.test.js +343 -0
  33. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  34. package/dist/src/__tests__/lowering.test.js +1015 -0
  35. package/dist/src/__tests__/macro.test.d.ts +8 -0
  36. package/dist/src/__tests__/macro.test.js +306 -0
  37. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  38. package/dist/src/__tests__/mc-integration.test.js +817 -0
  39. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  40. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  41. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  42. package/dist/src/__tests__/nbt.test.js +82 -0
  43. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  44. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  45. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  46. package/dist/src/__tests__/optimizer.test.js +149 -0
  47. package/dist/src/__tests__/parser.test.d.ts +1 -0
  48. package/dist/src/__tests__/parser.test.js +807 -0
  49. package/dist/src/__tests__/repl.test.d.ts +1 -0
  50. package/dist/src/__tests__/repl.test.js +27 -0
  51. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  52. package/dist/src/__tests__/runtime.test.js +289 -0
  53. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  54. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  55. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  56. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  57. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  58. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  59. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  60. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  61. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  62. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  63. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  64. package/dist/src/__tests__/typechecker.test.js +552 -0
  65. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  66. package/dist/src/__tests__/var-allocator.test.js +69 -0
  67. package/dist/src/ast/types.d.ts +515 -0
  68. package/dist/src/ast/types.js +9 -0
  69. package/dist/src/builtins/metadata.d.ts +36 -0
  70. package/dist/src/builtins/metadata.js +1014 -0
  71. package/dist/src/cli.d.ts +11 -0
  72. package/dist/src/cli.js +443 -0
  73. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  74. package/dist/src/codegen/cmdblock/index.js +45 -0
  75. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  76. package/dist/src/codegen/mcfunction/index.js +606 -0
  77. package/dist/src/codegen/structure/index.d.ts +24 -0
  78. package/dist/src/codegen/structure/index.js +279 -0
  79. package/dist/src/codegen/var-allocator.d.ts +45 -0
  80. package/dist/src/codegen/var-allocator.js +104 -0
  81. package/dist/src/compile.d.ts +37 -0
  82. package/dist/src/compile.js +165 -0
  83. package/dist/src/diagnostics/index.d.ts +44 -0
  84. package/dist/src/diagnostics/index.js +140 -0
  85. package/dist/src/events/types.d.ts +35 -0
  86. package/dist/src/events/types.js +59 -0
  87. package/dist/src/formatter/index.d.ts +1 -0
  88. package/dist/src/formatter/index.js +26 -0
  89. package/dist/src/index.d.ts +22 -0
  90. package/dist/src/index.js +45 -0
  91. package/dist/src/ir/builder.d.ts +33 -0
  92. package/dist/src/ir/builder.js +99 -0
  93. package/dist/src/ir/types.d.ts +132 -0
  94. package/dist/src/ir/types.js +15 -0
  95. package/dist/src/lexer/index.d.ts +37 -0
  96. package/dist/src/lexer/index.js +569 -0
  97. package/dist/src/lowering/index.d.ts +188 -0
  98. package/dist/src/lowering/index.js +3405 -0
  99. package/dist/src/mc-test/client.d.ts +128 -0
  100. package/dist/src/mc-test/client.js +174 -0
  101. package/dist/src/mc-test/runner.d.ts +28 -0
  102. package/dist/src/mc-test/runner.js +151 -0
  103. package/dist/src/mc-test/setup.d.ts +11 -0
  104. package/dist/src/mc-test/setup.js +98 -0
  105. package/dist/src/mc-validator/index.d.ts +17 -0
  106. package/dist/src/mc-validator/index.js +322 -0
  107. package/dist/src/nbt/index.d.ts +86 -0
  108. package/dist/src/nbt/index.js +250 -0
  109. package/dist/src/optimizer/commands.d.ts +38 -0
  110. package/dist/src/optimizer/commands.js +451 -0
  111. package/dist/src/optimizer/dce.d.ts +34 -0
  112. package/dist/src/optimizer/dce.js +639 -0
  113. package/dist/src/optimizer/passes.d.ts +34 -0
  114. package/dist/src/optimizer/passes.js +243 -0
  115. package/dist/src/optimizer/structure.d.ts +9 -0
  116. package/dist/src/optimizer/structure.js +356 -0
  117. package/dist/src/parser/index.d.ts +93 -0
  118. package/dist/src/parser/index.js +1687 -0
  119. package/dist/src/repl.d.ts +16 -0
  120. package/dist/src/repl.js +165 -0
  121. package/dist/src/runtime/index.d.ts +107 -0
  122. package/dist/src/runtime/index.js +1409 -0
  123. package/dist/src/typechecker/index.d.ts +61 -0
  124. package/dist/src/typechecker/index.js +1034 -0
  125. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  126. package/dist/src/types/entity-hierarchy.js +107 -0
  127. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  128. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  129. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  130. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  131. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  132. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  133. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  134. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  135. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  136. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  137. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  138. package/dist/src2/__tests__/lir/types.test.js +185 -0
  139. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  140. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  141. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  142. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  143. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  144. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  145. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  146. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  147. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  148. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  149. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  150. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  151. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  152. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  153. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  154. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  155. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  156. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  157. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  158. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  159. package/dist/src2/emit/compile.d.ts +19 -0
  160. package/dist/src2/emit/compile.js +80 -0
  161. package/dist/src2/emit/index.d.ts +17 -0
  162. package/dist/src2/emit/index.js +172 -0
  163. package/dist/src2/hir/lower.d.ts +15 -0
  164. package/dist/src2/hir/lower.js +378 -0
  165. package/dist/src2/hir/types.d.ts +373 -0
  166. package/dist/src2/hir/types.js +16 -0
  167. package/dist/src2/lir/lower.d.ts +15 -0
  168. package/dist/src2/lir/lower.js +453 -0
  169. package/dist/src2/lir/types.d.ts +136 -0
  170. package/dist/src2/lir/types.js +11 -0
  171. package/dist/src2/lir/verify.d.ts +14 -0
  172. package/dist/src2/lir/verify.js +113 -0
  173. package/dist/src2/mir/lower.d.ts +9 -0
  174. package/dist/src2/mir/lower.js +1030 -0
  175. package/dist/src2/mir/macro.d.ts +22 -0
  176. package/dist/src2/mir/macro.js +168 -0
  177. package/dist/src2/mir/types.d.ts +183 -0
  178. package/dist/src2/mir/types.js +11 -0
  179. package/dist/src2/mir/verify.d.ts +16 -0
  180. package/dist/src2/mir/verify.js +216 -0
  181. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  182. package/dist/src2/optimizer/block_merge.js +84 -0
  183. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  184. package/dist/src2/optimizer/branch_simplify.js +28 -0
  185. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  186. package/dist/src2/optimizer/constant_fold.js +85 -0
  187. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  188. package/dist/src2/optimizer/copy_prop.js +113 -0
  189. package/dist/src2/optimizer/dce.d.ts +8 -0
  190. package/dist/src2/optimizer/dce.js +155 -0
  191. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  192. package/dist/src2/optimizer/pipeline.js +42 -0
  193. package/dist/tsconfig.tsbuildinfo +1 -0
  194. package/docs/compiler-pipeline-redesign.md +2243 -0
  195. package/docs/optimization-ideas.md +1076 -0
  196. package/editors/vscode/package-lock.json +3 -3
  197. package/editors/vscode/package.json +1 -1
  198. package/examples/readme-demo.mcrs +44 -66
  199. package/jest.config.js +1 -1
  200. package/package.json +6 -5
  201. package/scripts/postbuild.js +15 -0
  202. package/src/__tests__/cli.test.ts +8 -220
  203. package/src/__tests__/dce.test.ts +11 -56
  204. package/src/__tests__/diagnostics.test.ts +59 -38
  205. package/src/__tests__/mc-integration.test.ts +1 -2
  206. package/src/ast/types.ts +6 -1
  207. package/src/cli.ts +29 -156
  208. package/src/compile.ts +6 -162
  209. package/src/index.ts +14 -178
  210. package/src/lexer/index.ts +9 -1
  211. package/src/mc-test/runner.ts +4 -3
  212. package/src/parser/index.ts +1 -1
  213. package/src/repl.ts +1 -1
  214. package/src/runtime/index.ts +1 -1
  215. package/src2/__tests__/e2e/basic.test.ts +154 -0
  216. package/src2/__tests__/e2e/macros.test.ts +199 -0
  217. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  218. package/src2/__tests__/hir/desugar.test.ts +263 -0
  219. package/src2/__tests__/lir/lower.test.ts +619 -0
  220. package/src2/__tests__/lir/types.test.ts +207 -0
  221. package/src2/__tests__/lir/verify.test.ts +249 -0
  222. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  223. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  224. package/src2/__tests__/mir/verify.test.ts +254 -0
  225. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  226. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  227. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  228. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  229. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  230. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  231. package/src2/emit/compile.ts +99 -0
  232. package/src2/emit/index.ts +222 -0
  233. package/src2/hir/lower.ts +428 -0
  234. package/src2/hir/types.ts +216 -0
  235. package/src2/lir/lower.ts +556 -0
  236. package/src2/lir/types.ts +109 -0
  237. package/src2/lir/verify.ts +129 -0
  238. package/src2/mir/lower.ts +1160 -0
  239. package/src2/mir/macro.ts +167 -0
  240. package/src2/mir/types.ts +106 -0
  241. package/src2/mir/verify.ts +218 -0
  242. package/src2/optimizer/block_merge.ts +93 -0
  243. package/src2/optimizer/branch_simplify.ts +27 -0
  244. package/src2/optimizer/constant_fold.ts +88 -0
  245. package/src2/optimizer/copy_prop.ts +106 -0
  246. package/src2/optimizer/dce.ts +133 -0
  247. package/src2/optimizer/pipeline.ts +44 -0
  248. package/tsconfig.json +2 -2
  249. package/src/__tests__/codegen.test.ts +0 -161
  250. package/src/__tests__/e2e.test.ts +0 -2039
  251. package/src/__tests__/entity-types.test.ts +0 -236
  252. package/src/__tests__/lowering.test.ts +0 -1185
  253. package/src/__tests__/macro.test.ts +0 -343
  254. package/src/__tests__/nbt.test.ts +0 -58
  255. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  256. package/src/__tests__/optimizer.test.ts +0 -162
  257. package/src/__tests__/runtime.test.ts +0 -305
  258. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  259. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  260. package/src/__tests__/stdlib-math.test.ts +0 -374
  261. package/src/__tests__/stdlib-vec.test.ts +0 -259
  262. package/src/__tests__/structure-optimizer.test.ts +0 -38
  263. package/src/__tests__/var-allocator.test.ts +0 -75
  264. package/src/codegen/cmdblock/index.ts +0 -63
  265. package/src/codegen/mcfunction/index.ts +0 -662
  266. package/src/codegen/structure/index.ts +0 -346
  267. package/src/codegen/var-allocator.ts +0 -104
  268. package/src/ir/builder.ts +0 -116
  269. package/src/ir/types.ts +0 -134
  270. package/src/lowering/index.ts +0 -3860
  271. package/src/optimizer/commands.ts +0 -534
  272. package/src/optimizer/dce.ts +0 -679
  273. package/src/optimizer/passes.ts +0 -250
  274. package/src/optimizer/structure.ts +0 -450
@@ -0,0 +1,242 @@
1
+ import { Lexer } from '../../../src/lexer'
2
+ import { Parser } from '../../../src/parser'
3
+ import { lowerToHIR } from '../../hir/lower'
4
+ import { lowerToMIR } from '../../mir/lower'
5
+ import { verifyMIR } from '../../mir/verify'
6
+ import type { MIRModule, MIRBlock, MIRInstr, MIRFunction } from '../../mir/types'
7
+
8
+ function compile(source: string): MIRModule {
9
+ const tokens = new Lexer(source).tokenize()
10
+ const ast = new Parser(tokens).parse('test')
11
+ const hir = lowerToHIR(ast)
12
+ return lowerToMIR(hir)
13
+ }
14
+
15
+ function getFn(mod: MIRModule, name?: string): MIRFunction {
16
+ if (name) return mod.functions.find(f => f.name === name)!
17
+ return mod.functions[0]
18
+ }
19
+
20
+ function getBlock(fn: MIRFunction, id: string): MIRBlock | undefined {
21
+ return fn.blocks.find(b => b.id === id)
22
+ }
23
+
24
+ describe('MIR lowering — if/else → branch CFG', () => {
25
+ test('if without else creates branch to then + merge', () => {
26
+ const mod = compile('fn f(x: int): int { if (x > 0) { return x; } return 0; }')
27
+ expect(verifyMIR(mod)).toEqual([])
28
+
29
+ const fn = getFn(mod)
30
+ // Entry block should end with a branch
31
+ const entry = getBlock(fn, fn.entry)!
32
+ expect(entry.term.kind).toBe('branch')
33
+
34
+ const branch = entry.term as Extract<MIRInstr, { kind: 'branch' }>
35
+ // Both targets should exist
36
+ expect(fn.blocks.some(b => b.id === branch.then)).toBe(true)
37
+ expect(fn.blocks.some(b => b.id === branch.else)).toBe(true)
38
+ })
39
+
40
+ test('if/else creates branch to then + else + merge', () => {
41
+ const mod = compile(`
42
+ fn f(x: int): int {
43
+ if (x > 0) { return 1; }
44
+ else { return -1; }
45
+ }
46
+ `)
47
+ expect(verifyMIR(mod)).toEqual([])
48
+
49
+ const fn = getFn(mod)
50
+ const entry = getBlock(fn, fn.entry)!
51
+ expect(entry.term.kind).toBe('branch')
52
+
53
+ const branch = entry.term as Extract<MIRInstr, { kind: 'branch' }>
54
+ const thenBlock = getBlock(fn, branch.then)!
55
+ const elseBlock = getBlock(fn, branch.else)!
56
+
57
+ // Both branches should have return terminators
58
+ expect(thenBlock.term.kind).toBe('return')
59
+ expect(elseBlock.term.kind).toBe('return')
60
+ })
61
+
62
+ test('nested if/else produces correct block structure', () => {
63
+ const mod = compile(`
64
+ fn f(x: int): int {
65
+ if (x > 0) {
66
+ if (x > 10) { return 2; }
67
+ return 1;
68
+ }
69
+ return 0;
70
+ }
71
+ `)
72
+ expect(verifyMIR(mod)).toEqual([])
73
+
74
+ const fn = getFn(mod)
75
+ // Should have multiple blocks due to nesting
76
+ expect(fn.blocks.length).toBeGreaterThanOrEqual(4)
77
+ })
78
+ })
79
+
80
+ describe('MIR lowering — while → loop CFG', () => {
81
+ test('while loop creates header + body + exit blocks', () => {
82
+ const mod = compile(`
83
+ fn f(x: int): int {
84
+ let sum: int = 0;
85
+ while (x > 0) {
86
+ sum = sum + x;
87
+ x = x - 1;
88
+ }
89
+ return sum;
90
+ }
91
+ `)
92
+ expect(verifyMIR(mod)).toEqual([])
93
+
94
+ const fn = getFn(mod)
95
+ // Find loop header block (has branch terminator based on condition)
96
+ const headerBlock = fn.blocks.find(b =>
97
+ b.id.startsWith('loop_header') && b.term.kind === 'branch'
98
+ )
99
+ expect(headerBlock).toBeDefined()
100
+
101
+ // Find loop body block
102
+ const bodyBlock = fn.blocks.find(b => b.id.startsWith('loop_body'))
103
+ expect(bodyBlock).toBeDefined()
104
+
105
+ // Find loop exit block
106
+ const exitBlock = fn.blocks.find(b => b.id.startsWith('loop_exit'))
107
+ expect(exitBlock).toBeDefined()
108
+
109
+ // Header should branch to body (then) and exit (else)
110
+ const branch = headerBlock!.term as Extract<MIRInstr, { kind: 'branch' }>
111
+ expect(branch.then).toBe(bodyBlock!.id)
112
+ expect(branch.else).toBe(exitBlock!.id)
113
+ })
114
+
115
+ test('break jumps to loop exit', () => {
116
+ const mod = compile(`
117
+ fn f(x: int): void {
118
+ while (x > 0) {
119
+ if (x == 5) { break; }
120
+ x = x - 1;
121
+ }
122
+ }
123
+ `)
124
+ expect(verifyMIR(mod)).toEqual([])
125
+
126
+ const fn = getFn(mod)
127
+ const exitBlock = fn.blocks.find(b => b.id.startsWith('loop_exit'))
128
+ expect(exitBlock).toBeDefined()
129
+
130
+ // Some block should jump directly to the exit (the break)
131
+ const breakBlock = fn.blocks.find(b =>
132
+ b.term.kind === 'jump' && (b.term as any).target === exitBlock!.id
133
+ && !b.id.startsWith('loop_header') && !b.id.startsWith('loop_exit')
134
+ && !b.id.startsWith('entry')
135
+ )
136
+ expect(breakBlock).toBeDefined()
137
+ })
138
+
139
+ test('continue jumps to loop header', () => {
140
+ const mod = compile(`
141
+ fn f(x: int): void {
142
+ while (x > 0) {
143
+ x = x - 1;
144
+ if (x == 3) { continue; }
145
+ }
146
+ }
147
+ `)
148
+ expect(verifyMIR(mod)).toEqual([])
149
+
150
+ const fn = getFn(mod)
151
+ const headerBlock = fn.blocks.find(b => b.id.startsWith('loop_header'))
152
+ expect(headerBlock).toBeDefined()
153
+
154
+ // Some block should jump to header (the continue)
155
+ const continueBlock = fn.blocks.find(b =>
156
+ b.term.kind === 'jump' && (b.term as any).target === headerBlock!.id
157
+ && !b.id.startsWith('loop_body') && !b.id.startsWith('entry')
158
+ )
159
+ expect(continueBlock).toBeDefined()
160
+ })
161
+ })
162
+
163
+ describe('MIR lowering — short-circuit operators', () => {
164
+ test('&& produces branch (short-circuit)', () => {
165
+ const mod = compile('fn f(a: bool, b: bool): bool { return a && b; }')
166
+ expect(verifyMIR(mod)).toEqual([])
167
+
168
+ const fn = getFn(mod)
169
+ // Should have and_right, and_false, and_merge blocks
170
+ expect(fn.blocks.some(b => b.id.startsWith('and_right'))).toBe(true)
171
+ expect(fn.blocks.some(b => b.id.startsWith('and_false'))).toBe(true)
172
+ expect(fn.blocks.some(b => b.id.startsWith('and_merge'))).toBe(true)
173
+ })
174
+
175
+ test('|| produces branch (short-circuit)', () => {
176
+ const mod = compile('fn f(a: bool, b: bool): bool { return a || b; }')
177
+ expect(verifyMIR(mod)).toEqual([])
178
+
179
+ const fn = getFn(mod)
180
+ expect(fn.blocks.some(b => b.id.startsWith('or_true'))).toBe(true)
181
+ expect(fn.blocks.some(b => b.id.startsWith('or_right'))).toBe(true)
182
+ expect(fn.blocks.some(b => b.id.startsWith('or_merge'))).toBe(true)
183
+ })
184
+ })
185
+
186
+ describe('MIR lowering — return', () => {
187
+ test('void return', () => {
188
+ const mod = compile('fn f(): void { return; }')
189
+ expect(verifyMIR(mod)).toEqual([])
190
+
191
+ const fn = getFn(mod)
192
+ const entry = getBlock(fn, fn.entry)!
193
+ expect(entry.term.kind).toBe('return')
194
+ expect((entry.term as any).value).toBeNull()
195
+ })
196
+
197
+ test('value return', () => {
198
+ const mod = compile('fn f(): int { return 42; }')
199
+ expect(verifyMIR(mod)).toEqual([])
200
+
201
+ const fn = getFn(mod)
202
+ const entry = getBlock(fn, fn.entry)!
203
+ expect(entry.term.kind).toBe('return')
204
+ expect((entry.term as any).value).toEqual({ kind: 'const', value: 42 })
205
+ })
206
+
207
+ test('early return creates dead block for subsequent code', () => {
208
+ const mod = compile(`
209
+ fn f(x: int): int {
210
+ return x;
211
+ let y: int = 0;
212
+ }
213
+ `)
214
+ expect(verifyMIR(mod)).toEqual([])
215
+
216
+ const fn = getFn(mod)
217
+ const entry = getBlock(fn, fn.entry)!
218
+ expect(entry.term.kind).toBe('return')
219
+ })
220
+ })
221
+
222
+ describe('MIR lowering — all blocks reachable and well-formed', () => {
223
+ test('complex function verifies clean', () => {
224
+ const mod = compile(`
225
+ fn f(n: int): int {
226
+ let sum: int = 0;
227
+ let i: int = 0;
228
+ while (i < n) {
229
+ if (i % 2 == 0) {
230
+ sum = sum + i;
231
+ } else {
232
+ sum = sum - i;
233
+ }
234
+ i = i + 1;
235
+ }
236
+ return sum;
237
+ }
238
+ `)
239
+ const errors = verifyMIR(mod)
240
+ expect(errors).toEqual([])
241
+ })
242
+ })
@@ -0,0 +1,254 @@
1
+ import { verifyMIR } from '../../mir/verify'
2
+ import type { MIRModule, MIRBlock, MIRFunction } from '../../mir/types'
3
+
4
+ function makeModule(functions: MIRFunction[]): MIRModule {
5
+ return { functions, namespace: 'test', objective: '__test' }
6
+ }
7
+
8
+ function makeBlock(id: string, instrs: any[], term: any, preds: string[] = []): MIRBlock {
9
+ return { id, instrs, term, preds }
10
+ }
11
+
12
+ describe('MIR verifier — terminator checks', () => {
13
+ test('rejects block without proper terminator', () => {
14
+ const fn: MIRFunction = {
15
+ name: 'bad',
16
+ params: [],
17
+ blocks: [
18
+ makeBlock('entry', [], { kind: 'const', dst: 't0', value: 42 }),
19
+ ],
20
+ entry: 'entry',
21
+ isMacro: false,
22
+ }
23
+
24
+ const errors = verifyMIR(makeModule([fn]))
25
+ expect(errors.length).toBeGreaterThan(0)
26
+ expect(errors[0].message).toContain('does not end with a terminator')
27
+ })
28
+
29
+ test('rejects terminator in non-terminal position', () => {
30
+ const fn: MIRFunction = {
31
+ name: 'bad',
32
+ params: [],
33
+ blocks: [
34
+ makeBlock(
35
+ 'entry',
36
+ [{ kind: 'jump', target: 'entry' }], // terminator in instrs
37
+ { kind: 'return', value: null },
38
+ ),
39
+ ],
40
+ entry: 'entry',
41
+ isMacro: false,
42
+ }
43
+
44
+ const errors = verifyMIR(makeModule([fn]))
45
+ expect(errors.length).toBeGreaterThan(0)
46
+ expect(errors[0].message).toContain('terminator')
47
+ expect(errors[0].message).toContain('non-terminal position')
48
+ })
49
+
50
+ test('accepts valid terminator (return)', () => {
51
+ const fn: MIRFunction = {
52
+ name: 'good',
53
+ params: [],
54
+ blocks: [
55
+ makeBlock('entry', [], { kind: 'return', value: null }),
56
+ ],
57
+ entry: 'entry',
58
+ isMacro: false,
59
+ }
60
+
61
+ expect(verifyMIR(makeModule([fn]))).toEqual([])
62
+ })
63
+
64
+ test('accepts valid terminator (jump)', () => {
65
+ const fn: MIRFunction = {
66
+ name: 'good',
67
+ params: [],
68
+ blocks: [
69
+ makeBlock('entry', [], { kind: 'jump', target: 'b1' }),
70
+ makeBlock('b1', [], { kind: 'return', value: null }),
71
+ ],
72
+ entry: 'entry',
73
+ isMacro: false,
74
+ }
75
+
76
+ expect(verifyMIR(makeModule([fn]))).toEqual([])
77
+ })
78
+ })
79
+
80
+ describe('MIR verifier — target existence', () => {
81
+ test('rejects jump to non-existent block', () => {
82
+ const fn: MIRFunction = {
83
+ name: 'bad',
84
+ params: [],
85
+ blocks: [
86
+ makeBlock('entry', [], { kind: 'jump', target: 'nonexistent' }),
87
+ ],
88
+ entry: 'entry',
89
+ isMacro: false,
90
+ }
91
+
92
+ const errors = verifyMIR(makeModule([fn]))
93
+ expect(errors.length).toBeGreaterThan(0)
94
+ expect(errors[0].message).toContain('non-existent target')
95
+ })
96
+
97
+ test('rejects branch to non-existent block', () => {
98
+ const fn: MIRFunction = {
99
+ name: 'bad',
100
+ params: [],
101
+ blocks: [
102
+ makeBlock('entry', [
103
+ { kind: 'const', dst: 't0', value: 1 },
104
+ ], { kind: 'branch', cond: { kind: 'temp', name: 't0' }, then: 'yes', else: 'no' }),
105
+ ],
106
+ entry: 'entry',
107
+ isMacro: false,
108
+ }
109
+
110
+ const errors = verifyMIR(makeModule([fn]))
111
+ expect(errors.length).toBe(2) // both targets missing
112
+ expect(errors[0].message).toContain('non-existent target')
113
+ })
114
+ })
115
+
116
+ describe('MIR verifier — reachability', () => {
117
+ test('rejects unreachable block', () => {
118
+ const fn: MIRFunction = {
119
+ name: 'bad',
120
+ params: [],
121
+ blocks: [
122
+ makeBlock('entry', [], { kind: 'return', value: null }),
123
+ makeBlock('orphan', [], { kind: 'return', value: null }),
124
+ ],
125
+ entry: 'entry',
126
+ isMacro: false,
127
+ }
128
+
129
+ const errors = verifyMIR(makeModule([fn]))
130
+ expect(errors.length).toBe(1)
131
+ expect(errors[0].message).toContain('unreachable')
132
+ expect(errors[0].block).toBe('orphan')
133
+ })
134
+
135
+ test('accepts all blocks reachable via jumps', () => {
136
+ const fn: MIRFunction = {
137
+ name: 'good',
138
+ params: [],
139
+ blocks: [
140
+ makeBlock('entry', [], { kind: 'jump', target: 'b1' }),
141
+ makeBlock('b1', [], { kind: 'jump', target: 'b2' }),
142
+ makeBlock('b2', [], { kind: 'return', value: null }),
143
+ ],
144
+ entry: 'entry',
145
+ isMacro: false,
146
+ }
147
+
148
+ expect(verifyMIR(makeModule([fn]))).toEqual([])
149
+ })
150
+
151
+ test('accepts blocks reachable via branch', () => {
152
+ const fn: MIRFunction = {
153
+ name: 'good',
154
+ params: [],
155
+ blocks: [
156
+ makeBlock('entry', [
157
+ { kind: 'const', dst: 't0', value: 1 },
158
+ ], { kind: 'branch', cond: { kind: 'temp', name: 't0' }, then: 'yes', else: 'no' }),
159
+ makeBlock('yes', [], { kind: 'return', value: null }),
160
+ makeBlock('no', [], { kind: 'return', value: null }),
161
+ ],
162
+ entry: 'entry',
163
+ isMacro: false,
164
+ }
165
+
166
+ expect(verifyMIR(makeModule([fn]))).toEqual([])
167
+ })
168
+ })
169
+
170
+ describe('MIR verifier — use-before-def', () => {
171
+ test('rejects use of undefined temp', () => {
172
+ const fn: MIRFunction = {
173
+ name: 'bad',
174
+ params: [],
175
+ blocks: [
176
+ makeBlock('entry', [
177
+ { kind: 'copy', dst: 't0', src: { kind: 'temp', name: 'undefined_temp' } },
178
+ ], { kind: 'return', value: null }),
179
+ ],
180
+ entry: 'entry',
181
+ isMacro: false,
182
+ }
183
+
184
+ const errors = verifyMIR(makeModule([fn]))
185
+ expect(errors.length).toBeGreaterThan(0)
186
+ expect(errors[0].message).toContain('undefined_temp')
187
+ expect(errors[0].message).toContain('never defined')
188
+ })
189
+
190
+ test('accepts temps defined as params', () => {
191
+ const fn: MIRFunction = {
192
+ name: 'good',
193
+ params: [{ name: 't0', isMacroParam: false }],
194
+ blocks: [
195
+ makeBlock('entry', [], { kind: 'return', value: { kind: 'temp', name: 't0' } }),
196
+ ],
197
+ entry: 'entry',
198
+ isMacro: false,
199
+ }
200
+
201
+ expect(verifyMIR(makeModule([fn]))).toEqual([])
202
+ })
203
+
204
+ test('accepts temps defined in instructions', () => {
205
+ const fn: MIRFunction = {
206
+ name: 'good',
207
+ params: [],
208
+ blocks: [
209
+ makeBlock('entry', [
210
+ { kind: 'const', dst: 't0', value: 42 },
211
+ { kind: 'copy', dst: 't1', src: { kind: 'temp', name: 't0' } },
212
+ ], { kind: 'return', value: { kind: 'temp', name: 't1' } }),
213
+ ],
214
+ entry: 'entry',
215
+ isMacro: false,
216
+ }
217
+
218
+ expect(verifyMIR(makeModule([fn]))).toEqual([])
219
+ })
220
+
221
+ test('const operands do not require definition', () => {
222
+ const fn: MIRFunction = {
223
+ name: 'good',
224
+ params: [],
225
+ blocks: [
226
+ makeBlock('entry', [
227
+ { kind: 'const', dst: 't0', value: 5 },
228
+ { kind: 'add', dst: 't1', a: { kind: 'temp', name: 't0' }, b: { kind: 'const', value: 3 } },
229
+ ], { kind: 'return', value: { kind: 'temp', name: 't1' } }),
230
+ ],
231
+ entry: 'entry',
232
+ isMacro: false,
233
+ }
234
+
235
+ expect(verifyMIR(makeModule([fn]))).toEqual([])
236
+ })
237
+ })
238
+
239
+ describe('MIR verifier — entry block', () => {
240
+ test('rejects missing entry block', () => {
241
+ const fn: MIRFunction = {
242
+ name: 'bad',
243
+ params: [],
244
+ blocks: [
245
+ makeBlock('not_entry', [], { kind: 'return', value: null }),
246
+ ],
247
+ entry: 'entry',
248
+ isMacro: false,
249
+ }
250
+
251
+ const errors = verifyMIR(makeModule([fn]))
252
+ expect(errors.some(e => e.message.includes('entry block'))).toBe(true)
253
+ })
254
+ })
@@ -0,0 +1,84 @@
1
+ import { blockMerge } from '../../optimizer/block_merge'
2
+ import type { MIRFunction, MIRBlock, MIRInstr, Operand } from '../../mir/types'
3
+
4
+ function mkFn(blocks: MIRBlock[], entry = 'entry'): MIRFunction {
5
+ return { name: 'test', params: [], blocks, entry, isMacro: false }
6
+ }
7
+
8
+ function mkBlock(id: string, instrs: MIRInstr[], term: MIRInstr, preds: string[] = []): MIRBlock {
9
+ return { id, instrs, term, preds }
10
+ }
11
+
12
+ const c = (v: number): Operand => ({ kind: 'const', value: v })
13
+ const t = (n: string): Operand => ({ kind: 'temp', name: n })
14
+
15
+ describe('block merging', () => {
16
+ test('merges single-pred successor into predecessor', () => {
17
+ const fn = mkFn([
18
+ mkBlock('entry', [
19
+ { kind: 'const', dst: 't0', value: 1 },
20
+ ], { kind: 'jump', target: 'b1' }),
21
+ mkBlock('b1', [
22
+ { kind: 'const', dst: 't1', value: 2 },
23
+ ], { kind: 'return', value: t('t1') }, ['entry']),
24
+ ])
25
+ const result = blockMerge(fn)
26
+ expect(result.blocks).toHaveLength(1)
27
+ expect(result.blocks[0].id).toBe('entry')
28
+ expect(result.blocks[0].instrs).toHaveLength(2)
29
+ expect(result.blocks[0].term).toEqual({ kind: 'return', value: t('t1') })
30
+ })
31
+
32
+ test('does not merge when successor has multiple preds', () => {
33
+ const fn = mkFn([
34
+ mkBlock('entry', [], { kind: 'branch', cond: t('c'), then: 'b1', else: 'b2' }),
35
+ mkBlock('b1', [], { kind: 'jump', target: 'merge' }, ['entry']),
36
+ mkBlock('b2', [], { kind: 'jump', target: 'merge' }, ['entry']),
37
+ mkBlock('merge', [], { kind: 'return', value: null }, ['b1', 'b2']),
38
+ ])
39
+ const result = blockMerge(fn)
40
+ // merge has 2 preds → no merging of merge block
41
+ expect(result.blocks.length).toBeGreaterThanOrEqual(3)
42
+ expect(result.blocks.some(b => b.id === 'merge')).toBe(true)
43
+ })
44
+
45
+ test('does not merge entry block into predecessor', () => {
46
+ // Entry block should never be merged away
47
+ const fn = mkFn([
48
+ mkBlock('entry', [], { kind: 'jump', target: 'b1' }),
49
+ mkBlock('b1', [], { kind: 'return', value: null }, ['entry']),
50
+ ])
51
+ const result = blockMerge(fn)
52
+ // b1 has single pred → merged into entry
53
+ expect(result.blocks).toHaveLength(1)
54
+ expect(result.blocks[0].id).toBe('entry')
55
+ })
56
+
57
+ test('chains merges: A→B→C all single-pred', () => {
58
+ const fn = mkFn([
59
+ mkBlock('entry', [
60
+ { kind: 'const', dst: 't0', value: 1 },
61
+ ], { kind: 'jump', target: 'b1' }),
62
+ mkBlock('b1', [
63
+ { kind: 'const', dst: 't1', value: 2 },
64
+ ], { kind: 'jump', target: 'b2' }, ['entry']),
65
+ mkBlock('b2', [
66
+ { kind: 'const', dst: 't2', value: 3 },
67
+ ], { kind: 'return', value: t('t2') }, ['b1']),
68
+ ])
69
+ const result = blockMerge(fn)
70
+ expect(result.blocks).toHaveLength(1)
71
+ expect(result.blocks[0].instrs).toHaveLength(3)
72
+ })
73
+
74
+ test('recomputes preds after merge', () => {
75
+ const fn = mkFn([
76
+ mkBlock('entry', [], { kind: 'jump', target: 'b1' }),
77
+ mkBlock('b1', [], { kind: 'jump', target: 'b2' }, ['entry']),
78
+ mkBlock('b2', [], { kind: 'return', value: null }, ['b1']),
79
+ ])
80
+ const result = blockMerge(fn)
81
+ expect(result.blocks).toHaveLength(1)
82
+ expect(result.blocks[0].preds).toEqual([])
83
+ })
84
+ })
@@ -0,0 +1,64 @@
1
+ import { branchSimplify } from '../../optimizer/branch_simplify'
2
+ import type { MIRFunction, MIRBlock, MIRInstr, Operand } from '../../mir/types'
3
+
4
+ function mkFn(blocks: MIRBlock[]): MIRFunction {
5
+ return { name: 'test', params: [], blocks, entry: 'entry', isMacro: false }
6
+ }
7
+
8
+ function mkBlock(id: string, instrs: MIRInstr[], term: MIRInstr, preds: string[] = []): MIRBlock {
9
+ return { id, instrs, term, preds }
10
+ }
11
+
12
+ const c = (v: number): Operand => ({ kind: 'const', value: v })
13
+ const t = (n: string): Operand => ({ kind: 'temp', name: n })
14
+
15
+ describe('branch simplification', () => {
16
+ test('branch(1, then, else) → jump(then)', () => {
17
+ const fn = mkFn([
18
+ mkBlock('entry', [], { kind: 'branch', cond: c(1), then: 'b1', else: 'b2' }),
19
+ mkBlock('b1', [], { kind: 'return', value: null }, ['entry']),
20
+ mkBlock('b2', [], { kind: 'return', value: null }, ['entry']),
21
+ ])
22
+ const result = branchSimplify(fn)
23
+ expect(result.blocks[0].term).toEqual({ kind: 'jump', target: 'b1' })
24
+ })
25
+
26
+ test('branch(0, then, else) → jump(else)', () => {
27
+ const fn = mkFn([
28
+ mkBlock('entry', [], { kind: 'branch', cond: c(0), then: 'b1', else: 'b2' }),
29
+ mkBlock('b1', [], { kind: 'return', value: null }, ['entry']),
30
+ mkBlock('b2', [], { kind: 'return', value: null }, ['entry']),
31
+ ])
32
+ const result = branchSimplify(fn)
33
+ expect(result.blocks[0].term).toEqual({ kind: 'jump', target: 'b2' })
34
+ })
35
+
36
+ test('nonzero const (42) → jump(then)', () => {
37
+ const fn = mkFn([
38
+ mkBlock('entry', [], { kind: 'branch', cond: c(42), then: 'b1', else: 'b2' }),
39
+ mkBlock('b1', [], { kind: 'return', value: null }),
40
+ mkBlock('b2', [], { kind: 'return', value: null }),
41
+ ])
42
+ const result = branchSimplify(fn)
43
+ expect(result.blocks[0].term).toEqual({ kind: 'jump', target: 'b1' })
44
+ })
45
+
46
+ test('does not simplify branch with temp cond', () => {
47
+ const fn = mkFn([
48
+ mkBlock('entry', [], { kind: 'branch', cond: t('flag'), then: 'b1', else: 'b2' }),
49
+ mkBlock('b1', [], { kind: 'return', value: null }),
50
+ mkBlock('b2', [], { kind: 'return', value: null }),
51
+ ])
52
+ const result = branchSimplify(fn)
53
+ expect(result.blocks[0].term.kind).toBe('branch')
54
+ })
55
+
56
+ test('does not touch jump terminators', () => {
57
+ const fn = mkFn([
58
+ mkBlock('entry', [], { kind: 'jump', target: 'b1' }),
59
+ mkBlock('b1', [], { kind: 'return', value: null }),
60
+ ])
61
+ const result = branchSimplify(fn)
62
+ expect(result.blocks[0].term).toEqual({ kind: 'jump', target: 'b1' })
63
+ })
64
+ })