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,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy Propagation — MIR optimization pass.
|
|
3
|
+
*
|
|
4
|
+
* Within each block, tracks `copy dst, src` and `const dst, value` instructions
|
|
5
|
+
* and replaces subsequent uses of `dst` with the known operand.
|
|
6
|
+
* Invalidates mappings when a temp is redefined (written to).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MIRFunction, MIRBlock, MIRInstr, Operand, Temp } from '../mir/types'
|
|
10
|
+
|
|
11
|
+
export function copyProp(fn: MIRFunction): MIRFunction {
|
|
12
|
+
return {
|
|
13
|
+
...fn,
|
|
14
|
+
blocks: fn.blocks.map(propBlock),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function propBlock(block: MIRBlock): MIRBlock {
|
|
19
|
+
// Map from temp → the operand it was copied from
|
|
20
|
+
const copies = new Map<Temp, Operand>()
|
|
21
|
+
|
|
22
|
+
const instrs: MIRInstr[] = []
|
|
23
|
+
for (const instr of block.instrs) {
|
|
24
|
+
// Rewrite uses first
|
|
25
|
+
const rewritten = rewriteUses(instr, copies)
|
|
26
|
+
|
|
27
|
+
// Invalidate any mapping whose source was just redefined
|
|
28
|
+
const dst = getDst(rewritten)
|
|
29
|
+
if (dst) {
|
|
30
|
+
// Remove any mapping that points TO this dst (as a temp source)
|
|
31
|
+
for (const [k, v] of copies) {
|
|
32
|
+
if (v.kind === 'temp' && v.name === dst) {
|
|
33
|
+
copies.delete(k)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Remove dst's own previous mapping (will be re-added below if applicable)
|
|
37
|
+
copies.delete(dst)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Track new propagatable definitions
|
|
41
|
+
if (rewritten.kind === 'const') {
|
|
42
|
+
// const dst, value → record dst → const operand
|
|
43
|
+
copies.set(rewritten.dst, { kind: 'const', value: rewritten.value })
|
|
44
|
+
} else if (rewritten.kind === 'copy') {
|
|
45
|
+
// copy dst, src → record dst → src (temp or const)
|
|
46
|
+
copies.set(rewritten.dst, rewritten.src)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
instrs.push(rewritten)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Also rewrite terminator uses
|
|
53
|
+
const term = rewriteUses(block.term, copies)
|
|
54
|
+
|
|
55
|
+
return { ...block, instrs, term }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolve(op: Operand, copies: Map<Temp, Operand>): Operand {
|
|
59
|
+
if (op.kind === 'temp') {
|
|
60
|
+
const replacement = copies.get(op.name)
|
|
61
|
+
if (replacement) return replacement
|
|
62
|
+
}
|
|
63
|
+
return op
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function rewriteUses(instr: MIRInstr, copies: Map<Temp, Operand>): MIRInstr {
|
|
67
|
+
switch (instr.kind) {
|
|
68
|
+
case 'copy':
|
|
69
|
+
return { ...instr, src: resolve(instr.src, copies) }
|
|
70
|
+
case 'neg':
|
|
71
|
+
case 'not':
|
|
72
|
+
return { ...instr, src: resolve(instr.src, copies) }
|
|
73
|
+
case 'add': case 'sub': case 'mul': case 'div': case 'mod':
|
|
74
|
+
case 'and': case 'or':
|
|
75
|
+
return { ...instr, a: resolve(instr.a, copies), b: resolve(instr.b, copies) }
|
|
76
|
+
case 'cmp':
|
|
77
|
+
return { ...instr, a: resolve(instr.a, copies), b: resolve(instr.b, copies) }
|
|
78
|
+
case 'nbt_write':
|
|
79
|
+
return { ...instr, src: resolve(instr.src, copies) }
|
|
80
|
+
case 'call':
|
|
81
|
+
return { ...instr, args: instr.args.map(a => resolve(a, copies)) }
|
|
82
|
+
case 'call_macro':
|
|
83
|
+
return { ...instr, args: instr.args.map(a => ({ ...a, value: resolve(a.value, copies) })) }
|
|
84
|
+
case 'branch':
|
|
85
|
+
return { ...instr, cond: resolve(instr.cond, copies) }
|
|
86
|
+
case 'return':
|
|
87
|
+
return { ...instr, value: instr.value ? resolve(instr.value, copies) : null }
|
|
88
|
+
default:
|
|
89
|
+
return instr
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getDst(instr: MIRInstr): Temp | null {
|
|
94
|
+
switch (instr.kind) {
|
|
95
|
+
case 'const': case 'copy':
|
|
96
|
+
case 'add': case 'sub': case 'mul': case 'div': case 'mod':
|
|
97
|
+
case 'neg': case 'cmp':
|
|
98
|
+
case 'and': case 'or': case 'not':
|
|
99
|
+
case 'nbt_read':
|
|
100
|
+
return instr.dst
|
|
101
|
+
case 'call': case 'call_macro':
|
|
102
|
+
return instr.dst
|
|
103
|
+
default:
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead Code Elimination — MIR optimization pass.
|
|
3
|
+
*
|
|
4
|
+
* 1. Removes definitions of temps that are never used anywhere in the function.
|
|
5
|
+
* 2. Removes unreachable blocks (no predecessors and not the entry block).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { MIRFunction, MIRBlock, MIRInstr, Operand, Temp, BlockId } from '../mir/types'
|
|
9
|
+
|
|
10
|
+
export function dce(fn: MIRFunction): MIRFunction {
|
|
11
|
+
// Phase 1: Remove unreachable blocks
|
|
12
|
+
let blocks = removeUnreachable(fn)
|
|
13
|
+
|
|
14
|
+
// Phase 2: Remove unused temp definitions
|
|
15
|
+
blocks = removeDeadDefs(fn.params, blocks)
|
|
16
|
+
|
|
17
|
+
// Phase 3: Recompute preds after block removal
|
|
18
|
+
blocks = recomputePreds(blocks)
|
|
19
|
+
|
|
20
|
+
return { ...fn, blocks }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function removeUnreachable(fn: MIRFunction): MIRBlock[] {
|
|
24
|
+
const reachable = new Set<BlockId>()
|
|
25
|
+
const queue: BlockId[] = [fn.entry]
|
|
26
|
+
const blockMap = new Map(fn.blocks.map(b => [b.id, b]))
|
|
27
|
+
|
|
28
|
+
while (queue.length > 0) {
|
|
29
|
+
const id = queue.shift()!
|
|
30
|
+
if (reachable.has(id)) continue
|
|
31
|
+
reachable.add(id)
|
|
32
|
+
const block = blockMap.get(id)
|
|
33
|
+
if (block) {
|
|
34
|
+
for (const target of getTermTargets(block.term)) {
|
|
35
|
+
if (!reachable.has(target)) queue.push(target)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return fn.blocks.filter(b => reachable.has(b.id))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function removeDeadDefs(params: { name: Temp }[], blocks: MIRBlock[]): MIRBlock[] {
|
|
44
|
+
// Collect all used temps across the entire function
|
|
45
|
+
const used = new Set<Temp>()
|
|
46
|
+
for (const block of blocks) {
|
|
47
|
+
for (const instr of block.instrs) {
|
|
48
|
+
for (const t of getUsedTemps(instr)) used.add(t)
|
|
49
|
+
}
|
|
50
|
+
for (const t of getUsedTemps(block.term)) used.add(t)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Remove instructions whose dst is never used, unless they have side effects
|
|
54
|
+
return blocks.map(block => ({
|
|
55
|
+
...block,
|
|
56
|
+
instrs: block.instrs.filter(instr => {
|
|
57
|
+
const dst = getDst(instr)
|
|
58
|
+
if (dst === null) return true // no dst → keep (side-effectful)
|
|
59
|
+
if (hasSideEffects(instr)) return true
|
|
60
|
+
return used.has(dst)
|
|
61
|
+
}),
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function recomputePreds(blocks: MIRBlock[]): MIRBlock[] {
|
|
66
|
+
const predMap = new Map<BlockId, BlockId[]>()
|
|
67
|
+
for (const b of blocks) predMap.set(b.id, [])
|
|
68
|
+
|
|
69
|
+
for (const block of blocks) {
|
|
70
|
+
for (const target of getTermTargets(block.term)) {
|
|
71
|
+
const preds = predMap.get(target)
|
|
72
|
+
if (preds) preds.push(block.id)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return blocks.map(b => ({ ...b, preds: predMap.get(b.id) ?? [] }))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function hasSideEffects(instr: MIRInstr): boolean {
|
|
80
|
+
if (instr.kind === 'call' || instr.kind === 'call_macro' ||
|
|
81
|
+
instr.kind === 'call_context' || instr.kind === 'nbt_write') return true
|
|
82
|
+
// Return field temps (__rf_) write to global return slots — not dead even if unused locally
|
|
83
|
+
const dst = getDst(instr)
|
|
84
|
+
if (dst && dst.startsWith('__rf_')) return true
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getTermTargets(term: MIRInstr): BlockId[] {
|
|
89
|
+
switch (term.kind) {
|
|
90
|
+
case 'jump': return [term.target]
|
|
91
|
+
case 'branch': return [term.then, term.else]
|
|
92
|
+
default: return []
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getDst(instr: MIRInstr): Temp | null {
|
|
97
|
+
switch (instr.kind) {
|
|
98
|
+
case 'const': case 'copy':
|
|
99
|
+
case 'add': case 'sub': case 'mul': case 'div': case 'mod':
|
|
100
|
+
case 'neg': case 'cmp':
|
|
101
|
+
case 'and': case 'or': case 'not':
|
|
102
|
+
case 'nbt_read':
|
|
103
|
+
return instr.dst
|
|
104
|
+
case 'call': case 'call_macro':
|
|
105
|
+
return instr.dst
|
|
106
|
+
default:
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getUsedTemps(instr: MIRInstr): Temp[] {
|
|
112
|
+
const temps: Temp[] = []
|
|
113
|
+
const addOp = (op: Operand) => { if (op.kind === 'temp') temps.push(op.name) }
|
|
114
|
+
|
|
115
|
+
switch (instr.kind) {
|
|
116
|
+
case 'copy': case 'neg': case 'not':
|
|
117
|
+
addOp(instr.src); break
|
|
118
|
+
case 'add': case 'sub': case 'mul': case 'div': case 'mod':
|
|
119
|
+
case 'cmp': case 'and': case 'or':
|
|
120
|
+
addOp(instr.a); addOp(instr.b); break
|
|
121
|
+
case 'nbt_write':
|
|
122
|
+
addOp(instr.src); break
|
|
123
|
+
case 'call':
|
|
124
|
+
instr.args.forEach(addOp); break
|
|
125
|
+
case 'call_macro':
|
|
126
|
+
instr.args.forEach(a => addOp(a.value)); break
|
|
127
|
+
case 'branch':
|
|
128
|
+
addOp(instr.cond); break
|
|
129
|
+
case 'return':
|
|
130
|
+
if (instr.value) addOp(instr.value); break
|
|
131
|
+
}
|
|
132
|
+
return temps
|
|
133
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIR Optimization Pipeline — runs all passes to a fixpoint.
|
|
3
|
+
*
|
|
4
|
+
* Each pass is a function MIRFunction → MIRFunction.
|
|
5
|
+
* The pipeline iterates until no pass changes the function (fixpoint).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { MIRFunction, MIRModule } from '../mir/types'
|
|
9
|
+
import { constantFold } from './constant_fold'
|
|
10
|
+
import { copyProp } from './copy_prop'
|
|
11
|
+
import { dce } from './dce'
|
|
12
|
+
import { blockMerge } from './block_merge'
|
|
13
|
+
import { branchSimplify } from './branch_simplify'
|
|
14
|
+
|
|
15
|
+
export type Pass = (fn: MIRFunction) => MIRFunction
|
|
16
|
+
|
|
17
|
+
const defaultPasses: Pass[] = [
|
|
18
|
+
constantFold,
|
|
19
|
+
copyProp,
|
|
20
|
+
branchSimplify,
|
|
21
|
+
dce,
|
|
22
|
+
blockMerge,
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const MAX_ITERATIONS = 20
|
|
26
|
+
|
|
27
|
+
export function optimizeFunction(fn: MIRFunction, passes: Pass[] = defaultPasses): MIRFunction {
|
|
28
|
+
let current = fn
|
|
29
|
+
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
30
|
+
const before = JSON.stringify(current)
|
|
31
|
+
for (const pass of passes) {
|
|
32
|
+
current = pass(current)
|
|
33
|
+
}
|
|
34
|
+
if (JSON.stringify(current) === before) break
|
|
35
|
+
}
|
|
36
|
+
return current
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function optimizeModule(mod: MIRModule, passes?: Pass[]): MIRModule {
|
|
40
|
+
return {
|
|
41
|
+
...mod,
|
|
42
|
+
functions: mod.functions.map(fn => optimizeFunction(fn, passes)),
|
|
43
|
+
}
|
|
44
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"module": "commonjs",
|
|
5
5
|
"lib": ["ES2021"],
|
|
6
6
|
"outDir": "dist",
|
|
7
|
-
"rootDir": "
|
|
7
|
+
"rootDir": ".",
|
|
8
8
|
"declaration": true,
|
|
9
9
|
"sourceMap": true,
|
|
10
10
|
"strict": true,
|
|
11
11
|
"esModuleInterop": true,
|
|
12
12
|
"skipLibCheck": true
|
|
13
13
|
},
|
|
14
|
-
"include": ["src/**/*"],
|
|
14
|
+
"include": ["src/**/*", "src2/**/*"],
|
|
15
15
|
"exclude": ["dist", "node_modules"]
|
|
16
16
|
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { generateDatapack, generateDatapackWithStats } from '../codegen/mcfunction'
|
|
2
|
-
import type { IRModule } from '../ir/types'
|
|
3
|
-
|
|
4
|
-
describe('generateDatapack', () => {
|
|
5
|
-
it('generates pack.mcmeta', () => {
|
|
6
|
-
const mod: IRModule = { namespace: 'test', functions: [], globals: [] }
|
|
7
|
-
const files = generateDatapack(mod)
|
|
8
|
-
const meta = files.find(f => f.path === 'pack.mcmeta')
|
|
9
|
-
expect(meta).toBeDefined()
|
|
10
|
-
expect(JSON.parse(meta!.content).pack.pack_format).toBe(26)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('generates __load.mcfunction with objective setup', () => {
|
|
14
|
-
const mod: IRModule = { namespace: 'mypack', functions: [], globals: [{ name: 'counter', init: 0 }] }
|
|
15
|
-
const files = generateDatapack(mod)
|
|
16
|
-
const load = files.find(f => f.path.includes('__load.mcfunction'))
|
|
17
|
-
expect(load?.content).toContain('scoreboard objectives add rs dummy')
|
|
18
|
-
expect(load?.content).toContain('scoreboard players set $counter rs 0')
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('generates function file for simple add(a, b)', () => {
|
|
22
|
-
// IR now uses { kind: 'param', index: i } for param-copy instructions,
|
|
23
|
-
// matching what the lowering emits. Pass mangle:false so we can check
|
|
24
|
-
// readable names without worrying about sequential mangled names.
|
|
25
|
-
const mod: IRModule = {
|
|
26
|
-
namespace: 'mypack',
|
|
27
|
-
globals: [],
|
|
28
|
-
functions: [{
|
|
29
|
-
name: 'add',
|
|
30
|
-
params: ['$a', '$b'],
|
|
31
|
-
locals: ['$a', '$b', '$result'],
|
|
32
|
-
blocks: [{
|
|
33
|
-
label: 'entry',
|
|
34
|
-
instrs: [
|
|
35
|
-
// param-copy instructions (what the lowering now emits)
|
|
36
|
-
{ op: 'assign', dst: '$a', src: { kind: 'param', index: 0 } },
|
|
37
|
-
{ op: 'assign', dst: '$b', src: { kind: 'param', index: 1 } },
|
|
38
|
-
{ op: 'binop', dst: '$result', lhs: { kind: 'var', name: '$a' }, bop: '+', rhs: { kind: 'var', name: '$b' } },
|
|
39
|
-
],
|
|
40
|
-
term: { op: 'return', value: { kind: 'var', name: '$result' } },
|
|
41
|
-
}],
|
|
42
|
-
}],
|
|
43
|
-
}
|
|
44
|
-
const files = generateDatapackWithStats(mod, { mangle: false }).files
|
|
45
|
-
const fn = files.find(f => f.path.includes('add.mcfunction'))
|
|
46
|
-
expect(fn).toBeDefined()
|
|
47
|
-
// param setup emitted from the IR
|
|
48
|
-
expect(fn!.content).toContain('scoreboard players operation $a rs = $p0 rs')
|
|
49
|
-
expect(fn!.content).toContain('scoreboard players operation $b rs = $p1 rs')
|
|
50
|
-
// Should have add operation
|
|
51
|
-
expect(fn!.content).toContain('+=')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('generates tick tag for tick loop function', () => {
|
|
55
|
-
const mod: IRModule = {
|
|
56
|
-
namespace: 'mypack',
|
|
57
|
-
globals: [],
|
|
58
|
-
functions: [{
|
|
59
|
-
name: 'game_loop',
|
|
60
|
-
params: [],
|
|
61
|
-
locals: [],
|
|
62
|
-
blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
|
|
63
|
-
isTickLoop: true,
|
|
64
|
-
}],
|
|
65
|
-
}
|
|
66
|
-
const files = generateDatapack(mod)
|
|
67
|
-
|
|
68
|
-
// tick.json should point to __tick
|
|
69
|
-
const tickTag = files.find(f => f.path.includes('tick.json'))
|
|
70
|
-
expect(tickTag).toBeDefined()
|
|
71
|
-
expect(JSON.parse(tickTag!.content).values).toContain('mypack:__tick')
|
|
72
|
-
|
|
73
|
-
// __tick.mcfunction should call the game_loop function
|
|
74
|
-
const tickFn = files.find(f => f.path.includes('__tick.mcfunction'))
|
|
75
|
-
expect(tickFn).toBeDefined()
|
|
76
|
-
expect(tickFn!.content).toContain('function mypack:game_loop')
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('generates conditional branches with execute if/unless', () => {
|
|
80
|
-
const mod: IRModule = {
|
|
81
|
-
namespace: 'mypack',
|
|
82
|
-
globals: [],
|
|
83
|
-
functions: [{
|
|
84
|
-
name: 'check',
|
|
85
|
-
params: [],
|
|
86
|
-
locals: ['cond'],
|
|
87
|
-
blocks: [
|
|
88
|
-
{
|
|
89
|
-
label: 'entry',
|
|
90
|
-
instrs: [
|
|
91
|
-
{ op: 'assign', dst: 'cond', src: { kind: 'const', value: 1 } },
|
|
92
|
-
],
|
|
93
|
-
term: { op: 'jump_if', cond: 'cond', then: 'then_block', else_: 'else_block' },
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
label: 'then_block',
|
|
97
|
-
instrs: [{ op: 'raw', cmd: 'say hello' }],
|
|
98
|
-
term: { op: 'return' },
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
label: 'else_block',
|
|
102
|
-
instrs: [{ op: 'raw', cmd: 'say goodbye' }],
|
|
103
|
-
term: { op: 'return' },
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
}],
|
|
107
|
-
}
|
|
108
|
-
const files = generateDatapack(mod)
|
|
109
|
-
const entry = files.find(f => f.path.endsWith('check.mcfunction'))
|
|
110
|
-
expect(entry?.content).toContain('execute if score $cond rs matches 1..')
|
|
111
|
-
expect(entry?.content).toContain('execute if score $cond rs matches ..0')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('generates advancement json for event decorators', () => {
|
|
115
|
-
const mod: IRModule = {
|
|
116
|
-
namespace: 'mypack',
|
|
117
|
-
globals: [],
|
|
118
|
-
functions: [{
|
|
119
|
-
name: 'on_mine_diamond',
|
|
120
|
-
params: [],
|
|
121
|
-
locals: [],
|
|
122
|
-
blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
|
|
123
|
-
eventTrigger: { kind: 'advancement', value: 'story/mine_diamond' },
|
|
124
|
-
}],
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const result = generateDatapackWithStats(mod)
|
|
128
|
-
const advancement = result.advancements.find(f => f.path === 'data/mypack/advancements/on_advancement_on_mine_diamond.json')
|
|
129
|
-
expect(advancement).toBeDefined()
|
|
130
|
-
const json = JSON.parse(advancement!.content)
|
|
131
|
-
expect(json.criteria.trigger.trigger).toBe('minecraft:story/mine_diamond')
|
|
132
|
-
expect(json.rewards.function).toBe('mypack:on_mine_diamond')
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('generates static event dispatcher in __tick', () => {
|
|
136
|
-
const mod: IRModule = {
|
|
137
|
-
namespace: 'mypack',
|
|
138
|
-
globals: [],
|
|
139
|
-
functions: [{
|
|
140
|
-
name: 'handle_death',
|
|
141
|
-
params: [],
|
|
142
|
-
locals: [],
|
|
143
|
-
blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
|
|
144
|
-
eventHandler: { eventType: 'PlayerDeath', tag: 'rs.just_died' },
|
|
145
|
-
}, {
|
|
146
|
-
name: 'handle_death_2',
|
|
147
|
-
params: [],
|
|
148
|
-
locals: [],
|
|
149
|
-
blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
|
|
150
|
-
eventHandler: { eventType: 'PlayerDeath', tag: 'rs.just_died' },
|
|
151
|
-
}],
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const files = generateDatapack(mod)
|
|
155
|
-
const tickFn = files.find(f => f.path.includes('__tick.mcfunction'))
|
|
156
|
-
expect(tickFn).toBeDefined()
|
|
157
|
-
expect(tickFn!.content).toContain('execute as @a[tag=rs.just_died] run function mypack:handle_death')
|
|
158
|
-
expect(tickFn!.content).toContain('execute as @a[tag=rs.just_died] run function mypack:handle_death_2')
|
|
159
|
-
expect(tickFn!.content).toContain('tag @a[tag=rs.just_died] remove rs.just_died')
|
|
160
|
-
})
|
|
161
|
-
})
|