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.
- package/.claude/commands/build-test.md +10 -0
- package/.claude/commands/deploy-demo.md +12 -0
- package/.claude/commands/stage-status.md +13 -0
- package/.claude/settings.json +12 -0
- package/.github/workflows/ci.yml +1 -0
- package/CLAUDE.md +231 -0
- package/README.md +29 -28
- package/README.zh.md +28 -28
- package/demo.gif +0 -0
- package/dist/cli.js +2 -554
- package/dist/compile.js +2 -266
- package/dist/index.js +2 -159
- package/dist/lexer/index.js +9 -1
- package/dist/lowering/index.js +22 -5
- package/dist/src/__tests__/cli.test.d.ts +1 -0
- package/dist/src/__tests__/cli.test.js +104 -0
- package/dist/src/__tests__/codegen.test.d.ts +1 -0
- package/dist/src/__tests__/codegen.test.js +152 -0
- package/dist/src/__tests__/compile-all.test.d.ts +10 -0
- package/dist/src/__tests__/compile-all.test.js +108 -0
- package/dist/src/__tests__/dce.test.d.ts +1 -0
- package/dist/src/__tests__/dce.test.js +102 -0
- package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/src/__tests__/diagnostics.test.js +177 -0
- package/dist/src/__tests__/e2e.test.d.ts +6 -0
- package/dist/src/__tests__/e2e.test.js +1789 -0
- package/dist/src/__tests__/entity-types.test.d.ts +1 -0
- package/dist/src/__tests__/entity-types.test.js +203 -0
- package/dist/src/__tests__/formatter.test.d.ts +1 -0
- package/dist/src/__tests__/formatter.test.js +40 -0
- package/dist/src/__tests__/lexer.test.d.ts +1 -0
- package/dist/src/__tests__/lexer.test.js +343 -0
- package/dist/src/__tests__/lowering.test.d.ts +1 -0
- package/dist/src/__tests__/lowering.test.js +1015 -0
- package/dist/src/__tests__/macro.test.d.ts +8 -0
- package/dist/src/__tests__/macro.test.js +306 -0
- package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/src/__tests__/mc-integration.test.js +817 -0
- package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/src/__tests__/mc-syntax.test.js +124 -0
- package/dist/src/__tests__/nbt.test.d.ts +1 -0
- package/dist/src/__tests__/nbt.test.js +82 -0
- package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/src/__tests__/optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer.test.js +149 -0
- package/dist/src/__tests__/parser.test.d.ts +1 -0
- package/dist/src/__tests__/parser.test.js +807 -0
- package/dist/src/__tests__/repl.test.d.ts +1 -0
- package/dist/src/__tests__/repl.test.js +27 -0
- package/dist/src/__tests__/runtime.test.d.ts +1 -0
- package/dist/src/__tests__/runtime.test.js +289 -0
- package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
- package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
- package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-math.test.js +351 -0
- package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-vec.test.js +263 -0
- package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/structure-optimizer.test.js +33 -0
- package/dist/src/__tests__/typechecker.test.d.ts +1 -0
- package/dist/src/__tests__/typechecker.test.js +552 -0
- package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
- package/dist/src/__tests__/var-allocator.test.js +69 -0
- package/dist/src/ast/types.d.ts +515 -0
- package/dist/src/ast/types.js +9 -0
- package/dist/src/builtins/metadata.d.ts +36 -0
- package/dist/src/builtins/metadata.js +1014 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.js +443 -0
- package/dist/src/codegen/cmdblock/index.d.ts +26 -0
- package/dist/src/codegen/cmdblock/index.js +45 -0
- package/dist/src/codegen/mcfunction/index.d.ts +40 -0
- package/dist/src/codegen/mcfunction/index.js +606 -0
- package/dist/src/codegen/structure/index.d.ts +24 -0
- package/dist/src/codegen/structure/index.js +279 -0
- package/dist/src/codegen/var-allocator.d.ts +45 -0
- package/dist/src/codegen/var-allocator.js +104 -0
- package/dist/src/compile.d.ts +37 -0
- package/dist/src/compile.js +165 -0
- package/dist/src/diagnostics/index.d.ts +44 -0
- package/dist/src/diagnostics/index.js +140 -0
- package/dist/src/events/types.d.ts +35 -0
- package/dist/src/events/types.js +59 -0
- package/dist/src/formatter/index.d.ts +1 -0
- package/dist/src/formatter/index.js +26 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +45 -0
- package/dist/src/ir/builder.d.ts +33 -0
- package/dist/src/ir/builder.js +99 -0
- package/dist/src/ir/types.d.ts +132 -0
- package/dist/src/ir/types.js +15 -0
- package/dist/src/lexer/index.d.ts +37 -0
- package/dist/src/lexer/index.js +569 -0
- package/dist/src/lowering/index.d.ts +188 -0
- package/dist/src/lowering/index.js +3405 -0
- package/dist/src/mc-test/client.d.ts +128 -0
- package/dist/src/mc-test/client.js +174 -0
- package/dist/src/mc-test/runner.d.ts +28 -0
- package/dist/src/mc-test/runner.js +151 -0
- package/dist/src/mc-test/setup.d.ts +11 -0
- package/dist/src/mc-test/setup.js +98 -0
- package/dist/src/mc-validator/index.d.ts +17 -0
- package/dist/src/mc-validator/index.js +322 -0
- package/dist/src/nbt/index.d.ts +86 -0
- package/dist/src/nbt/index.js +250 -0
- package/dist/src/optimizer/commands.d.ts +38 -0
- package/dist/src/optimizer/commands.js +451 -0
- package/dist/src/optimizer/dce.d.ts +34 -0
- package/dist/src/optimizer/dce.js +639 -0
- package/dist/src/optimizer/passes.d.ts +34 -0
- package/dist/src/optimizer/passes.js +243 -0
- package/dist/src/optimizer/structure.d.ts +9 -0
- package/dist/src/optimizer/structure.js +356 -0
- package/dist/src/parser/index.d.ts +93 -0
- package/dist/src/parser/index.js +1687 -0
- package/dist/src/repl.d.ts +16 -0
- package/dist/src/repl.js +165 -0
- package/dist/src/runtime/index.d.ts +107 -0
- package/dist/src/runtime/index.js +1409 -0
- package/dist/src/typechecker/index.d.ts +61 -0
- package/dist/src/typechecker/index.js +1034 -0
- package/dist/src/types/entity-hierarchy.d.ts +29 -0
- package/dist/src/types/entity-hierarchy.js +107 -0
- package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
- package/dist/src2/__tests__/e2e/basic.test.js +140 -0
- package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
- package/dist/src2/__tests__/e2e/macros.test.js +182 -0
- package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
- package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
- package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
- package/dist/src2/__tests__/hir/desugar.test.js +234 -0
- package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/lower.test.js +559 -0
- package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/types.test.js +185 -0
- package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/verify.test.js +221 -0
- package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
- package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
- package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/verify.test.js +223 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
- package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
- package/dist/src2/emit/compile.d.ts +19 -0
- package/dist/src2/emit/compile.js +80 -0
- package/dist/src2/emit/index.d.ts +17 -0
- package/dist/src2/emit/index.js +172 -0
- package/dist/src2/hir/lower.d.ts +15 -0
- package/dist/src2/hir/lower.js +378 -0
- package/dist/src2/hir/types.d.ts +373 -0
- package/dist/src2/hir/types.js +16 -0
- package/dist/src2/lir/lower.d.ts +15 -0
- package/dist/src2/lir/lower.js +453 -0
- package/dist/src2/lir/types.d.ts +136 -0
- package/dist/src2/lir/types.js +11 -0
- package/dist/src2/lir/verify.d.ts +14 -0
- package/dist/src2/lir/verify.js +113 -0
- package/dist/src2/mir/lower.d.ts +9 -0
- package/dist/src2/mir/lower.js +1030 -0
- package/dist/src2/mir/macro.d.ts +22 -0
- package/dist/src2/mir/macro.js +168 -0
- package/dist/src2/mir/types.d.ts +183 -0
- package/dist/src2/mir/types.js +11 -0
- package/dist/src2/mir/verify.d.ts +16 -0
- package/dist/src2/mir/verify.js +216 -0
- package/dist/src2/optimizer/block_merge.d.ts +12 -0
- package/dist/src2/optimizer/block_merge.js +84 -0
- package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
- package/dist/src2/optimizer/branch_simplify.js +28 -0
- package/dist/src2/optimizer/constant_fold.d.ts +10 -0
- package/dist/src2/optimizer/constant_fold.js +85 -0
- package/dist/src2/optimizer/copy_prop.d.ts +9 -0
- package/dist/src2/optimizer/copy_prop.js +113 -0
- package/dist/src2/optimizer/dce.d.ts +8 -0
- package/dist/src2/optimizer/dce.js +155 -0
- package/dist/src2/optimizer/pipeline.d.ts +10 -0
- package/dist/src2/optimizer/pipeline.js +42 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/compiler-pipeline-redesign.md +2243 -0
- package/docs/optimization-ideas.md +1076 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/readme-demo.mcrs +44 -66
- package/jest.config.js +1 -1
- package/package.json +6 -5
- package/scripts/postbuild.js +15 -0
- package/src/__tests__/cli.test.ts +8 -220
- package/src/__tests__/dce.test.ts +11 -56
- package/src/__tests__/diagnostics.test.ts +59 -38
- package/src/__tests__/mc-integration.test.ts +1 -2
- package/src/ast/types.ts +6 -1
- package/src/cli.ts +29 -156
- package/src/compile.ts +6 -162
- package/src/index.ts +14 -178
- package/src/lexer/index.ts +9 -1
- package/src/mc-test/runner.ts +4 -3
- package/src/parser/index.ts +1 -1
- package/src/repl.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src2/__tests__/e2e/basic.test.ts +154 -0
- package/src2/__tests__/e2e/macros.test.ts +199 -0
- package/src2/__tests__/e2e/migrate.test.ts +3008 -0
- package/src2/__tests__/hir/desugar.test.ts +263 -0
- package/src2/__tests__/lir/lower.test.ts +619 -0
- package/src2/__tests__/lir/types.test.ts +207 -0
- package/src2/__tests__/lir/verify.test.ts +249 -0
- package/src2/__tests__/mir/arithmetic.test.ts +156 -0
- package/src2/__tests__/mir/control-flow.test.ts +242 -0
- package/src2/__tests__/mir/verify.test.ts +254 -0
- package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
- package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
- package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
- package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
- package/src2/__tests__/optimizer/dce.test.ts +83 -0
- package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
- package/src2/emit/compile.ts +99 -0
- package/src2/emit/index.ts +222 -0
- package/src2/hir/lower.ts +428 -0
- package/src2/hir/types.ts +216 -0
- package/src2/lir/lower.ts +556 -0
- package/src2/lir/types.ts +109 -0
- package/src2/lir/verify.ts +129 -0
- package/src2/mir/lower.ts +1160 -0
- package/src2/mir/macro.ts +167 -0
- package/src2/mir/types.ts +106 -0
- package/src2/mir/verify.ts +218 -0
- package/src2/optimizer/block_merge.ts +93 -0
- package/src2/optimizer/branch_simplify.ts +27 -0
- package/src2/optimizer/constant_fold.ts +88 -0
- package/src2/optimizer/copy_prop.ts +106 -0
- package/src2/optimizer/dce.ts +133 -0
- package/src2/optimizer/pipeline.ts +44 -0
- package/tsconfig.json +2 -2
- package/src/__tests__/codegen.test.ts +0 -161
- package/src/__tests__/e2e.test.ts +0 -2039
- package/src/__tests__/entity-types.test.ts +0 -236
- package/src/__tests__/lowering.test.ts +0 -1185
- package/src/__tests__/macro.test.ts +0 -343
- package/src/__tests__/nbt.test.ts +0 -58
- package/src/__tests__/optimizer-advanced.test.ts +0 -144
- package/src/__tests__/optimizer.test.ts +0 -162
- package/src/__tests__/runtime.test.ts +0 -305
- package/src/__tests__/stdlib-advanced.test.ts +0 -379
- package/src/__tests__/stdlib-bigint.test.ts +0 -427
- package/src/__tests__/stdlib-math.test.ts +0 -374
- package/src/__tests__/stdlib-vec.test.ts +0 -259
- package/src/__tests__/structure-optimizer.test.ts +0 -38
- package/src/__tests__/var-allocator.test.ts +0 -75
- package/src/codegen/cmdblock/index.ts +0 -63
- package/src/codegen/mcfunction/index.ts +0 -662
- package/src/codegen/structure/index.ts +0 -346
- package/src/codegen/var-allocator.ts +0 -104
- package/src/ir/builder.ts +0 -116
- package/src/ir/types.ts +0 -134
- package/src/lowering/index.ts +0 -3860
- package/src/optimizer/commands.ts +0 -534
- package/src/optimizer/dce.ts +0 -679
- package/src/optimizer/passes.ts +0 -250
- package/src/optimizer/structure.ts +0 -450
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Macro function detection — pre-scans HIR to find parameters used in
|
|
3
|
+
* builtin call positions (coordinates, entity types, etc.)
|
|
4
|
+
*
|
|
5
|
+
* A function becomes a "macro function" when one of its params appears
|
|
6
|
+
* in a position that requires literal substitution in the MC command
|
|
7
|
+
* (e.g. summon coords, particle coords, setblock coords, local/relative
|
|
8
|
+
* coords like ^px or ~height).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { HIRModule, HIRExpr, HIRStmt, HIRBlock } from '../hir/types'
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Known builtins that emit MC commands with inline arguments
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/** Builtins whose arguments appear literally in MC commands */
|
|
18
|
+
export const BUILTIN_SET = new Set([
|
|
19
|
+
'say', 'tell', 'tellraw', 'title', 'actionbar', 'subtitle', 'title_times',
|
|
20
|
+
'announce', 'give', 'kill', 'effect', 'effect_clear',
|
|
21
|
+
'summon', 'particle', 'playsound', 'clear', 'weather',
|
|
22
|
+
'time_set', 'time_add', 'gamerule', 'tag_add', 'tag_remove',
|
|
23
|
+
'kick', 'setblock', 'fill', 'clone', 'difficulty', 'xp_add', 'xp_set',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Public API
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export interface MacroFunctionInfo {
|
|
31
|
+
macroParams: Set<string>
|
|
32
|
+
/** param name → param type name (for NBT scale inference) */
|
|
33
|
+
paramTypes: Map<string, string>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pre-scan HIR functions to detect which params need macro treatment.
|
|
38
|
+
* Returns a map: function name → MacroFunctionInfo.
|
|
39
|
+
*/
|
|
40
|
+
export function detectMacroFunctions(hir: HIRModule): Map<string, MacroFunctionInfo> {
|
|
41
|
+
const result = new Map<string, MacroFunctionInfo>()
|
|
42
|
+
|
|
43
|
+
for (const fn of hir.functions) {
|
|
44
|
+
const paramNames = new Set(fn.params.map(p => p.name))
|
|
45
|
+
const macroParams = new Set<string>()
|
|
46
|
+
scanBlock(fn.body, paramNames, macroParams)
|
|
47
|
+
if (macroParams.size > 0) {
|
|
48
|
+
const paramTypes = new Map<string, string>()
|
|
49
|
+
for (const p of fn.params) {
|
|
50
|
+
const typeName = p.type?.kind === 'named' ? (p.type as any).name : 'int'
|
|
51
|
+
paramTypes.set(p.name, typeName)
|
|
52
|
+
}
|
|
53
|
+
result.set(fn.name, { macroParams, paramTypes })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const ib of hir.implBlocks) {
|
|
58
|
+
for (const m of ib.methods) {
|
|
59
|
+
const paramNames = new Set(m.params.map(p => p.name))
|
|
60
|
+
const macroParams = new Set<string>()
|
|
61
|
+
scanBlock(m.body, paramNames, macroParams)
|
|
62
|
+
if (macroParams.size > 0) {
|
|
63
|
+
const paramTypes = new Map<string, string>()
|
|
64
|
+
for (const p of m.params) {
|
|
65
|
+
const typeName = p.type?.kind === 'named' ? (p.type as any).name : 'int'
|
|
66
|
+
paramTypes.set(p.name, typeName)
|
|
67
|
+
}
|
|
68
|
+
result.set(`${ib.typeName}::${m.name}`, { macroParams, paramTypes })
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// HIR scanning
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
function scanBlock(stmts: HIRBlock, paramNames: Set<string>, macroParams: Set<string>): void {
|
|
81
|
+
for (const stmt of stmts) scanStmt(stmt, paramNames, macroParams)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function scanStmt(stmt: HIRStmt, paramNames: Set<string>, macroParams: Set<string>): void {
|
|
85
|
+
switch (stmt.kind) {
|
|
86
|
+
case 'expr': scanExpr(stmt.expr, paramNames, macroParams); break
|
|
87
|
+
case 'let': scanExpr(stmt.init, paramNames, macroParams); break
|
|
88
|
+
case 'return': if (stmt.value) scanExpr(stmt.value, paramNames, macroParams); break
|
|
89
|
+
case 'if':
|
|
90
|
+
scanExpr(stmt.cond, paramNames, macroParams)
|
|
91
|
+
scanBlock(stmt.then, paramNames, macroParams)
|
|
92
|
+
if (stmt.else_) scanBlock(stmt.else_, paramNames, macroParams)
|
|
93
|
+
break
|
|
94
|
+
case 'while':
|
|
95
|
+
scanExpr(stmt.cond, paramNames, macroParams)
|
|
96
|
+
scanBlock(stmt.body, paramNames, macroParams)
|
|
97
|
+
if (stmt.step) scanBlock(stmt.step, paramNames, macroParams)
|
|
98
|
+
break
|
|
99
|
+
case 'foreach': scanBlock(stmt.body, paramNames, macroParams); break
|
|
100
|
+
case 'match':
|
|
101
|
+
scanExpr(stmt.expr, paramNames, macroParams)
|
|
102
|
+
for (const arm of stmt.arms) scanBlock(arm.body, paramNames, macroParams)
|
|
103
|
+
break
|
|
104
|
+
case 'execute': scanBlock(stmt.body, paramNames, macroParams); break
|
|
105
|
+
case 'raw': break
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function scanExpr(expr: HIRExpr, paramNames: Set<string>, macroParams: Set<string>): void {
|
|
110
|
+
if (expr.kind === 'call' && BUILTIN_SET.has(expr.fn)) {
|
|
111
|
+
// Check if any argument is a param identifier or a coord with a param variable
|
|
112
|
+
for (const arg of expr.args) {
|
|
113
|
+
checkMacroArg(arg, paramNames, macroParams)
|
|
114
|
+
}
|
|
115
|
+
// Recurse into args for nested expressions
|
|
116
|
+
for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Recurse into sub-expressions
|
|
121
|
+
switch (expr.kind) {
|
|
122
|
+
case 'call':
|
|
123
|
+
for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
|
|
124
|
+
break
|
|
125
|
+
case 'invoke':
|
|
126
|
+
scanExpr(expr.callee, paramNames, macroParams)
|
|
127
|
+
for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
|
|
128
|
+
break
|
|
129
|
+
case 'binary':
|
|
130
|
+
scanExpr(expr.left, paramNames, macroParams)
|
|
131
|
+
scanExpr(expr.right, paramNames, macroParams)
|
|
132
|
+
break
|
|
133
|
+
case 'unary':
|
|
134
|
+
scanExpr(expr.operand, paramNames, macroParams)
|
|
135
|
+
break
|
|
136
|
+
case 'assign':
|
|
137
|
+
scanExpr(expr.value, paramNames, macroParams)
|
|
138
|
+
break
|
|
139
|
+
case 'member_assign':
|
|
140
|
+
scanExpr(expr.obj, paramNames, macroParams)
|
|
141
|
+
scanExpr(expr.value, paramNames, macroParams)
|
|
142
|
+
break
|
|
143
|
+
case 'member':
|
|
144
|
+
scanExpr(expr.obj, paramNames, macroParams)
|
|
145
|
+
break
|
|
146
|
+
case 'index':
|
|
147
|
+
scanExpr(expr.obj, paramNames, macroParams)
|
|
148
|
+
scanExpr(expr.index, paramNames, macroParams)
|
|
149
|
+
break
|
|
150
|
+
case 'static_call':
|
|
151
|
+
for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Check if a single argument expression references a function parameter in a macro position */
|
|
157
|
+
function checkMacroArg(expr: HIRExpr, paramNames: Set<string>, macroParams: Set<string>): void {
|
|
158
|
+
if (expr.kind === 'ident' && paramNames.has(expr.name)) {
|
|
159
|
+
macroParams.add(expr.name)
|
|
160
|
+
} else if (expr.kind === 'local_coord' || expr.kind === 'rel_coord') {
|
|
161
|
+
// ^varname or ~varname — extract the variable part
|
|
162
|
+
const rest = expr.value.slice(1)
|
|
163
|
+
if (rest && /^[a-zA-Z_]\w*$/.test(rest) && paramNames.has(rest)) {
|
|
164
|
+
macroParams.add(rest)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIR (Mid-level IR) Types — Stage 3 of the RedScript compiler pipeline.
|
|
3
|
+
*
|
|
4
|
+
* MIR is a 3-address, explicit-CFG representation with versioned temporaries.
|
|
5
|
+
* Every instruction produces at most one result into a fresh temporary.
|
|
6
|
+
*
|
|
7
|
+
* Spec: docs/compiler-pipeline-redesign.md § "MIR Instruction Set"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// A temporary variable — unique within a function, named t0, t1, t2...
|
|
11
|
+
export type Temp = string
|
|
12
|
+
|
|
13
|
+
// An operand: either a temp or an inline constant
|
|
14
|
+
export type Operand =
|
|
15
|
+
| { kind: 'temp'; name: Temp }
|
|
16
|
+
| { kind: 'const'; value: number }
|
|
17
|
+
|
|
18
|
+
// A basic block identifier
|
|
19
|
+
export type BlockId = string
|
|
20
|
+
|
|
21
|
+
// Comparison operators (for cmp instruction)
|
|
22
|
+
export type CmpOp = 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge'
|
|
23
|
+
|
|
24
|
+
// NBT value types (for nbt_write)
|
|
25
|
+
export type NBTType = 'int' | 'double' | 'float' | 'long' | 'short' | 'byte'
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Execute subcommands (used in call_context)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
export type ExecuteSubcmd =
|
|
32
|
+
| { kind: 'as'; selector: string }
|
|
33
|
+
| { kind: 'at'; selector: string }
|
|
34
|
+
| { kind: 'at_self' }
|
|
35
|
+
| { kind: 'positioned'; x: string; y: string; z: string }
|
|
36
|
+
| { kind: 'rotated'; yaw: string; pitch: string }
|
|
37
|
+
| { kind: 'in'; dimension: string }
|
|
38
|
+
| { kind: 'anchored'; anchor: 'eyes' | 'feet' }
|
|
39
|
+
| { kind: 'if_score'; a: string; op: CmpOp; b: string }
|
|
40
|
+
| { kind: 'unless_score'; a: string; op: CmpOp; b: string }
|
|
41
|
+
| { kind: 'if_matches'; score: string; range: string }
|
|
42
|
+
| { kind: 'unless_matches'; score: string; range: string }
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// MIR Instructions
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export type MIRInstr =
|
|
49
|
+
// ── Constants & copies ──────────────────────────────────────────────────
|
|
50
|
+
| { kind: 'const'; dst: Temp; value: number }
|
|
51
|
+
| { kind: 'copy'; dst: Temp; src: Operand }
|
|
52
|
+
|
|
53
|
+
// ── Integer arithmetic ──────────────────────────────────────────────────
|
|
54
|
+
| { kind: 'add'; dst: Temp; a: Operand; b: Operand }
|
|
55
|
+
| { kind: 'sub'; dst: Temp; a: Operand; b: Operand }
|
|
56
|
+
| { kind: 'mul'; dst: Temp; a: Operand; b: Operand }
|
|
57
|
+
| { kind: 'div'; dst: Temp; a: Operand; b: Operand }
|
|
58
|
+
| { kind: 'mod'; dst: Temp; a: Operand; b: Operand }
|
|
59
|
+
| { kind: 'neg'; dst: Temp; src: Operand }
|
|
60
|
+
|
|
61
|
+
// ── Comparison (result is 0 or 1) ────────────────────────────────────────
|
|
62
|
+
| { kind: 'cmp'; dst: Temp; op: CmpOp; a: Operand; b: Operand }
|
|
63
|
+
|
|
64
|
+
// ── Boolean logic ────────────────────────────────────────────────────────
|
|
65
|
+
| { kind: 'and'; dst: Temp; a: Operand; b: Operand }
|
|
66
|
+
| { kind: 'or'; dst: Temp; a: Operand; b: Operand }
|
|
67
|
+
| { kind: 'not'; dst: Temp; src: Operand }
|
|
68
|
+
|
|
69
|
+
// ── NBT storage ──────────────────────────────────────────────────────────
|
|
70
|
+
| { kind: 'nbt_read'; dst: Temp; ns: string; path: string; scale: number }
|
|
71
|
+
| { kind: 'nbt_write'; ns: string; path: string; type: NBTType; scale: number; src: Operand }
|
|
72
|
+
|
|
73
|
+
// ── Function calls ────────────────────────────────────────────────────────
|
|
74
|
+
| { kind: 'call'; dst: Temp | null; fn: string; args: Operand[] }
|
|
75
|
+
| { kind: 'call_macro'; dst: Temp | null; fn: string; args: { name: string; value: Operand; type: NBTType; scale: number }[] }
|
|
76
|
+
| { kind: 'call_context'; fn: string; subcommands: ExecuteSubcmd[] }
|
|
77
|
+
|
|
78
|
+
// ── Terminators (exactly one per basic block, must be last) ──────────────
|
|
79
|
+
| { kind: 'jump'; target: BlockId }
|
|
80
|
+
| { kind: 'branch'; cond: Operand; then: BlockId; else: BlockId }
|
|
81
|
+
| { kind: 'return'; value: Operand | null }
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Basic block and function structure
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
export interface MIRBlock {
|
|
88
|
+
id: BlockId
|
|
89
|
+
instrs: MIRInstr[] // non-terminator instructions
|
|
90
|
+
term: MIRInstr // must be jump | branch | return
|
|
91
|
+
preds: BlockId[] // predecessor block ids (for dataflow)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface MIRFunction {
|
|
95
|
+
name: string
|
|
96
|
+
params: { name: Temp; isMacroParam: boolean }[]
|
|
97
|
+
blocks: MIRBlock[]
|
|
98
|
+
entry: BlockId // entry block id (always 'entry')
|
|
99
|
+
isMacro: boolean // true if any param is a macro param
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface MIRModule {
|
|
103
|
+
functions: MIRFunction[]
|
|
104
|
+
namespace: string
|
|
105
|
+
objective: string // scoreboard objective (default: __<namespace>)
|
|
106
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIR Verifier — validates structural invariants of MIR modules.
|
|
3
|
+
*
|
|
4
|
+
* Checks:
|
|
5
|
+
* 1. Every block ends with exactly one terminator (jump | branch | return)
|
|
6
|
+
* 2. Every temp used is defined before use (in the block or as a param)
|
|
7
|
+
* 3. No unreachable blocks (all blocks reachable from entry)
|
|
8
|
+
* 4. Branch/jump targets must exist in the function
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { MIRModule, MIRFunction, MIRBlock, MIRInstr, Operand, Temp } from './types'
|
|
12
|
+
|
|
13
|
+
export interface VerifyError {
|
|
14
|
+
fn: string
|
|
15
|
+
block?: string
|
|
16
|
+
message: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function verifyMIR(module: MIRModule): VerifyError[] {
|
|
20
|
+
const errors: VerifyError[] = []
|
|
21
|
+
for (const fn of module.functions) {
|
|
22
|
+
errors.push(...verifyFunction(fn))
|
|
23
|
+
}
|
|
24
|
+
return errors
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function verifyFunction(fn: MIRFunction): VerifyError[] {
|
|
28
|
+
const errors: VerifyError[] = []
|
|
29
|
+
|
|
30
|
+
const blockIds = new Set(fn.blocks.map(b => b.id))
|
|
31
|
+
|
|
32
|
+
// 1. Check terminators
|
|
33
|
+
for (const block of fn.blocks) {
|
|
34
|
+
if (!isTerminator(block.term)) {
|
|
35
|
+
errors.push({
|
|
36
|
+
fn: fn.name,
|
|
37
|
+
block: block.id,
|
|
38
|
+
message: `block '${block.id}' does not end with a terminator (found '${block.term.kind}')`,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check that no non-terminator instruction is a terminator
|
|
43
|
+
for (const instr of block.instrs) {
|
|
44
|
+
if (isTerminator(instr)) {
|
|
45
|
+
errors.push({
|
|
46
|
+
fn: fn.name,
|
|
47
|
+
block: block.id,
|
|
48
|
+
message: `block '${block.id}' has terminator '${instr.kind}' in non-terminal position`,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. Check that branch/jump targets exist
|
|
55
|
+
for (const block of fn.blocks) {
|
|
56
|
+
const targets = getTermTargets(block.term)
|
|
57
|
+
for (const target of targets) {
|
|
58
|
+
if (!blockIds.has(target)) {
|
|
59
|
+
errors.push({
|
|
60
|
+
fn: fn.name,
|
|
61
|
+
block: block.id,
|
|
62
|
+
message: `block '${block.id}' references non-existent target '${target}'`,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. Check reachability from entry
|
|
69
|
+
const reachable = new Set<string>()
|
|
70
|
+
const entryBlock = fn.blocks.find(b => b.id === fn.entry)
|
|
71
|
+
if (!entryBlock) {
|
|
72
|
+
errors.push({
|
|
73
|
+
fn: fn.name,
|
|
74
|
+
message: `entry block '${fn.entry}' not found`,
|
|
75
|
+
})
|
|
76
|
+
} else {
|
|
77
|
+
// BFS from entry
|
|
78
|
+
const queue: string[] = [fn.entry]
|
|
79
|
+
while (queue.length > 0) {
|
|
80
|
+
const id = queue.shift()!
|
|
81
|
+
if (reachable.has(id)) continue
|
|
82
|
+
reachable.add(id)
|
|
83
|
+
|
|
84
|
+
const block = fn.blocks.find(b => b.id === id)
|
|
85
|
+
if (block) {
|
|
86
|
+
for (const target of getTermTargets(block.term)) {
|
|
87
|
+
if (!reachable.has(target)) {
|
|
88
|
+
queue.push(target)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const block of fn.blocks) {
|
|
95
|
+
if (!reachable.has(block.id)) {
|
|
96
|
+
errors.push({
|
|
97
|
+
fn: fn.name,
|
|
98
|
+
block: block.id,
|
|
99
|
+
message: `block '${block.id}' is unreachable from entry`,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4. Check use-before-def for temporaries
|
|
106
|
+
// Collect all defined temps: params + all dst fields in instructions
|
|
107
|
+
const allDefs = new Set<Temp>()
|
|
108
|
+
for (const p of fn.params) allDefs.add(p.name)
|
|
109
|
+
for (const block of fn.blocks) {
|
|
110
|
+
for (const instr of block.instrs) {
|
|
111
|
+
const dst = getDst(instr)
|
|
112
|
+
if (dst) allDefs.add(dst)
|
|
113
|
+
}
|
|
114
|
+
const termDst = getDst(block.term)
|
|
115
|
+
if (termDst) allDefs.add(termDst)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check that every temp used in an operand is in allDefs
|
|
119
|
+
for (const block of fn.blocks) {
|
|
120
|
+
for (const instr of block.instrs) {
|
|
121
|
+
for (const used of getUsedTemps(instr)) {
|
|
122
|
+
if (!allDefs.has(used)) {
|
|
123
|
+
errors.push({
|
|
124
|
+
fn: fn.name,
|
|
125
|
+
block: block.id,
|
|
126
|
+
message: `temp '${used}' used but never defined`,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const used of getUsedTemps(block.term)) {
|
|
132
|
+
if (!allDefs.has(used)) {
|
|
133
|
+
errors.push({
|
|
134
|
+
fn: fn.name,
|
|
135
|
+
block: block.id,
|
|
136
|
+
message: `temp '${used}' used in terminator but never defined`,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return errors
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isTerminator(instr: MIRInstr): boolean {
|
|
146
|
+
return instr.kind === 'jump' || instr.kind === 'branch' || instr.kind === 'return'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getTermTargets(term: MIRInstr): string[] {
|
|
150
|
+
switch (term.kind) {
|
|
151
|
+
case 'jump': return [term.target]
|
|
152
|
+
case 'branch': return [term.then, term.else]
|
|
153
|
+
case 'return': return []
|
|
154
|
+
default: return []
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getDst(instr: MIRInstr): Temp | null {
|
|
159
|
+
switch (instr.kind) {
|
|
160
|
+
case 'const':
|
|
161
|
+
case 'copy':
|
|
162
|
+
case 'add': case 'sub': case 'mul': case 'div': case 'mod':
|
|
163
|
+
case 'neg':
|
|
164
|
+
case 'cmp':
|
|
165
|
+
case 'and': case 'or': case 'not':
|
|
166
|
+
case 'nbt_read':
|
|
167
|
+
return instr.dst
|
|
168
|
+
case 'call':
|
|
169
|
+
case 'call_macro':
|
|
170
|
+
return instr.dst
|
|
171
|
+
default:
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getOperandTemps(op: Operand): Temp[] {
|
|
177
|
+
return op.kind === 'temp' ? [op.name] : []
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getUsedTemps(instr: MIRInstr): Temp[] {
|
|
181
|
+
const temps: Temp[] = []
|
|
182
|
+
switch (instr.kind) {
|
|
183
|
+
case 'const':
|
|
184
|
+
break
|
|
185
|
+
case 'copy':
|
|
186
|
+
case 'neg':
|
|
187
|
+
case 'not':
|
|
188
|
+
temps.push(...getOperandTemps(instr.src))
|
|
189
|
+
break
|
|
190
|
+
case 'add': case 'sub': case 'mul': case 'div': case 'mod':
|
|
191
|
+
case 'cmp':
|
|
192
|
+
case 'and': case 'or':
|
|
193
|
+
temps.push(...getOperandTemps(instr.a), ...getOperandTemps(instr.b))
|
|
194
|
+
break
|
|
195
|
+
case 'nbt_read':
|
|
196
|
+
break
|
|
197
|
+
case 'nbt_write':
|
|
198
|
+
temps.push(...getOperandTemps(instr.src))
|
|
199
|
+
break
|
|
200
|
+
case 'call':
|
|
201
|
+
for (const arg of instr.args) temps.push(...getOperandTemps(arg))
|
|
202
|
+
break
|
|
203
|
+
case 'call_macro':
|
|
204
|
+
for (const arg of instr.args) temps.push(...getOperandTemps(arg.value))
|
|
205
|
+
break
|
|
206
|
+
case 'call_context':
|
|
207
|
+
break
|
|
208
|
+
case 'jump':
|
|
209
|
+
break
|
|
210
|
+
case 'branch':
|
|
211
|
+
temps.push(...getOperandTemps(instr.cond))
|
|
212
|
+
break
|
|
213
|
+
case 'return':
|
|
214
|
+
if (instr.value) temps.push(...getOperandTemps(instr.value))
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
return temps
|
|
218
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Merging — MIR optimization pass.
|
|
3
|
+
*
|
|
4
|
+
* Merges a block B into its sole predecessor A when:
|
|
5
|
+
* - A ends with an unconditional jump to B
|
|
6
|
+
* - B has exactly one predecessor (A)
|
|
7
|
+
* - B is not the entry block
|
|
8
|
+
*
|
|
9
|
+
* The merged block keeps A's id and combines A's instrs + B's instrs + B's terminator.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { MIRFunction, MIRBlock, BlockId } from '../mir/types'
|
|
13
|
+
|
|
14
|
+
export function blockMerge(fn: MIRFunction): MIRFunction {
|
|
15
|
+
let blocks = fn.blocks
|
|
16
|
+
|
|
17
|
+
// Iterate until no more merges possible
|
|
18
|
+
let changed = true
|
|
19
|
+
while (changed) {
|
|
20
|
+
changed = false
|
|
21
|
+
const blockMap = new Map(blocks.map(b => [b.id, b]))
|
|
22
|
+
const predCounts = computePredCounts(blocks)
|
|
23
|
+
|
|
24
|
+
const newBlocks: MIRBlock[] = []
|
|
25
|
+
const removed = new Set<BlockId>()
|
|
26
|
+
|
|
27
|
+
for (const block of blocks) {
|
|
28
|
+
if (removed.has(block.id)) continue
|
|
29
|
+
|
|
30
|
+
// Check: does this block jump unconditionally to a single-pred successor?
|
|
31
|
+
if (block.term.kind === 'jump') {
|
|
32
|
+
const targetId = block.term.target
|
|
33
|
+
const target = blockMap.get(targetId)
|
|
34
|
+
if (target && targetId !== fn.entry && predCounts.get(targetId) === 1) {
|
|
35
|
+
// Merge target into this block
|
|
36
|
+
const merged: MIRBlock = {
|
|
37
|
+
id: block.id,
|
|
38
|
+
instrs: [...block.instrs, ...target.instrs],
|
|
39
|
+
term: target.term,
|
|
40
|
+
preds: block.preds,
|
|
41
|
+
}
|
|
42
|
+
newBlocks.push(merged)
|
|
43
|
+
removed.add(targetId)
|
|
44
|
+
changed = true
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
newBlocks.push(block)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
blocks = newBlocks
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recompute preds after merging
|
|
56
|
+
blocks = recomputePreds(blocks)
|
|
57
|
+
|
|
58
|
+
return { ...fn, blocks }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function computePredCounts(blocks: MIRBlock[]): Map<BlockId, number> {
|
|
62
|
+
const counts = new Map<BlockId, number>()
|
|
63
|
+
for (const b of blocks) counts.set(b.id, 0)
|
|
64
|
+
|
|
65
|
+
for (const block of blocks) {
|
|
66
|
+
for (const target of getTermTargets(block.term)) {
|
|
67
|
+
counts.set(target, (counts.get(target) ?? 0) + 1)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return counts
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function recomputePreds(blocks: MIRBlock[]): MIRBlock[] {
|
|
74
|
+
const predMap = new Map<BlockId, BlockId[]>()
|
|
75
|
+
for (const b of blocks) predMap.set(b.id, [])
|
|
76
|
+
|
|
77
|
+
for (const block of blocks) {
|
|
78
|
+
for (const target of getTermTargets(block.term)) {
|
|
79
|
+
const preds = predMap.get(target)
|
|
80
|
+
if (preds) preds.push(block.id)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return blocks.map(b => ({ ...b, preds: predMap.get(b.id) ?? [] }))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getTermTargets(term: MIRBlock['term']): BlockId[] {
|
|
88
|
+
switch (term.kind) {
|
|
89
|
+
case 'jump': return [term.target]
|
|
90
|
+
case 'branch': return [term.then, term.else]
|
|
91
|
+
default: return []
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch Simplification — MIR optimization pass.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `branch(const, then, else)` with an unconditional `jump`:
|
|
5
|
+
* - branch(nonzero, then, else) → jump(then)
|
|
6
|
+
* - branch(0, then, else) → jump(else)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MIRFunction, MIRBlock } from '../mir/types'
|
|
10
|
+
|
|
11
|
+
export function branchSimplify(fn: MIRFunction): MIRFunction {
|
|
12
|
+
return {
|
|
13
|
+
...fn,
|
|
14
|
+
blocks: fn.blocks.map(simplifyBlock),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function simplifyBlock(block: MIRBlock): MIRBlock {
|
|
19
|
+
if (block.term.kind !== 'branch') return block
|
|
20
|
+
if (block.term.cond.kind !== 'const') return block
|
|
21
|
+
|
|
22
|
+
const target = block.term.cond.value !== 0 ? block.term.then : block.term.else
|
|
23
|
+
return {
|
|
24
|
+
...block,
|
|
25
|
+
term: { kind: 'jump', target },
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constant Folding — MIR optimization pass.
|
|
3
|
+
*
|
|
4
|
+
* Folds instructions where all operands are constants:
|
|
5
|
+
* - Arithmetic: add(3, 4) → 7, neg(5) → -5
|
|
6
|
+
* - Comparison: cmp(lt, 3, 4) → 1
|
|
7
|
+
* - Boolean: and(1, 0) → 0, not(0) → 1
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { MIRFunction, MIRBlock, MIRInstr, Operand } from '../mir/types'
|
|
11
|
+
|
|
12
|
+
export function constantFold(fn: MIRFunction): MIRFunction {
|
|
13
|
+
return {
|
|
14
|
+
...fn,
|
|
15
|
+
blocks: fn.blocks.map(foldBlock),
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function foldBlock(block: MIRBlock): MIRBlock {
|
|
20
|
+
const instrs: MIRInstr[] = []
|
|
21
|
+
for (const instr of block.instrs) {
|
|
22
|
+
const folded = tryFold(instr)
|
|
23
|
+
instrs.push(folded ?? instr)
|
|
24
|
+
}
|
|
25
|
+
return { ...block, instrs }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isConst(op: Operand): op is { kind: 'const'; value: number } {
|
|
29
|
+
return op.kind === 'const'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function tryFold(instr: MIRInstr): MIRInstr | null {
|
|
33
|
+
switch (instr.kind) {
|
|
34
|
+
case 'add':
|
|
35
|
+
if (isConst(instr.a) && isConst(instr.b))
|
|
36
|
+
return { kind: 'const', dst: instr.dst, value: instr.a.value + instr.b.value }
|
|
37
|
+
break
|
|
38
|
+
case 'sub':
|
|
39
|
+
if (isConst(instr.a) && isConst(instr.b))
|
|
40
|
+
return { kind: 'const', dst: instr.dst, value: instr.a.value - instr.b.value }
|
|
41
|
+
break
|
|
42
|
+
case 'mul':
|
|
43
|
+
if (isConst(instr.a) && isConst(instr.b))
|
|
44
|
+
return { kind: 'const', dst: instr.dst, value: instr.a.value * instr.b.value }
|
|
45
|
+
break
|
|
46
|
+
case 'div':
|
|
47
|
+
if (isConst(instr.a) && isConst(instr.b) && instr.b.value !== 0)
|
|
48
|
+
return { kind: 'const', dst: instr.dst, value: Math.trunc(instr.a.value / instr.b.value) }
|
|
49
|
+
break
|
|
50
|
+
case 'mod':
|
|
51
|
+
if (isConst(instr.a) && isConst(instr.b) && instr.b.value !== 0)
|
|
52
|
+
return { kind: 'const', dst: instr.dst, value: instr.a.value % instr.b.value }
|
|
53
|
+
break
|
|
54
|
+
case 'neg':
|
|
55
|
+
if (isConst(instr.src))
|
|
56
|
+
return { kind: 'const', dst: instr.dst, value: -instr.src.value }
|
|
57
|
+
break
|
|
58
|
+
case 'not':
|
|
59
|
+
if (isConst(instr.src))
|
|
60
|
+
return { kind: 'const', dst: instr.dst, value: instr.src.value === 0 ? 1 : 0 }
|
|
61
|
+
break
|
|
62
|
+
case 'and':
|
|
63
|
+
if (isConst(instr.a) && isConst(instr.b))
|
|
64
|
+
return { kind: 'const', dst: instr.dst, value: (instr.a.value !== 0 && instr.b.value !== 0) ? 1 : 0 }
|
|
65
|
+
break
|
|
66
|
+
case 'or':
|
|
67
|
+
if (isConst(instr.a) && isConst(instr.b))
|
|
68
|
+
return { kind: 'const', dst: instr.dst, value: (instr.a.value !== 0 || instr.b.value !== 0) ? 1 : 0 }
|
|
69
|
+
break
|
|
70
|
+
case 'cmp':
|
|
71
|
+
if (isConst(instr.a) && isConst(instr.b))
|
|
72
|
+
return { kind: 'const', dst: instr.dst, value: evalCmp(instr.op, instr.a.value, instr.b.value) }
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function evalCmp(op: string, a: number, b: number): number {
|
|
79
|
+
switch (op) {
|
|
80
|
+
case 'eq': return a === b ? 1 : 0
|
|
81
|
+
case 'ne': return a !== b ? 1 : 0
|
|
82
|
+
case 'lt': return a < b ? 1 : 0
|
|
83
|
+
case 'le': return a <= b ? 1 : 0
|
|
84
|
+
case 'gt': return a > b ? 1 : 0
|
|
85
|
+
case 'ge': return a >= b ? 1 : 0
|
|
86
|
+
default: return 0
|
|
87
|
+
}
|
|
88
|
+
}
|