redscript-mc 1.2.30 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/demo.gif +0 -0
- package/dist/cli.js +2 -554
- package/dist/compile.js +2 -266
- package/dist/index.js +2 -159
- package/dist/lowering/index.js +5 -3
- 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/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/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 -3876
- 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
package/src/optimizer/passes.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Optimization passes over IR.
|
|
3
|
-
*
|
|
4
|
-
* Each pass: IRFunction → IRFunction (pure transformation)
|
|
5
|
-
*
|
|
6
|
-
* Pipeline order:
|
|
7
|
-
* 1. constantFolding — evaluate constant expressions at compile time
|
|
8
|
-
* 2. copyPropagation — eliminate redundant copies
|
|
9
|
-
* 3. deadCodeElimination — remove unused assignments
|
|
10
|
-
* 4. commandMerging — MC-specific: merge chained execute conditions
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { IRBlock, IRFunction, IRInstr, Operand } from '../ir/types'
|
|
14
|
-
import { createEmptyOptimizationStats, mergeOptimizationStats, type OptimizationStats } from './commands'
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Helpers
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
function isConst(op: Operand): op is { kind: 'const'; value: number } {
|
|
21
|
-
return op.kind === 'const'
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function evalBinop(lhs: number, bop: string, rhs: number): number | null {
|
|
25
|
-
switch (bop) {
|
|
26
|
-
case '+': return lhs + rhs
|
|
27
|
-
case '-': return lhs - rhs
|
|
28
|
-
case '*': return lhs * rhs
|
|
29
|
-
case '/': return rhs === 0 ? null : Math.trunc(lhs / rhs) // MC uses truncated int division
|
|
30
|
-
case '%': return rhs === 0 ? null : lhs % rhs
|
|
31
|
-
default: return null
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function evalCmp(lhs: number, cop: string, rhs: number): number {
|
|
36
|
-
switch (cop) {
|
|
37
|
-
case '==': return lhs === rhs ? 1 : 0
|
|
38
|
-
case '!=': return lhs !== rhs ? 1 : 0
|
|
39
|
-
case '<': return lhs < rhs ? 1 : 0
|
|
40
|
-
case '<=': return lhs <= rhs ? 1 : 0
|
|
41
|
-
case '>': return lhs > rhs ? 1 : 0
|
|
42
|
-
case '>=': return lhs >= rhs ? 1 : 0
|
|
43
|
-
default: return 0
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Pass 1: Constant Folding
|
|
49
|
-
// Evaluates expressions with all-constant operands at compile time.
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
export function constantFolding(fn: IRFunction): IRFunction {
|
|
53
|
-
return constantFoldingWithStats(fn).fn
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function constantFoldingWithStats(fn: IRFunction): { fn: IRFunction; stats: Partial<OptimizationStats> } {
|
|
57
|
-
let folded = 0
|
|
58
|
-
const newBlocks = fn.blocks.map(block => {
|
|
59
|
-
const newInstrs: IRInstr[] = []
|
|
60
|
-
for (const instr of block.instrs) {
|
|
61
|
-
if (instr.op === 'binop' && isConst(instr.lhs) && isConst(instr.rhs)) {
|
|
62
|
-
const result = evalBinop(instr.lhs.value, instr.bop, instr.rhs.value)
|
|
63
|
-
if (result !== null) {
|
|
64
|
-
folded++
|
|
65
|
-
newInstrs.push({ op: 'assign', dst: instr.dst, src: { kind: 'const', value: result } })
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (instr.op === 'cmp' && isConst(instr.lhs) && isConst(instr.rhs)) {
|
|
70
|
-
const result = evalCmp(instr.lhs.value, instr.cop, instr.rhs.value)
|
|
71
|
-
folded++
|
|
72
|
-
newInstrs.push({ op: 'assign', dst: instr.dst, src: { kind: 'const', value: result } })
|
|
73
|
-
continue
|
|
74
|
-
}
|
|
75
|
-
newInstrs.push(instr)
|
|
76
|
-
}
|
|
77
|
-
return { ...block, instrs: newInstrs }
|
|
78
|
-
})
|
|
79
|
-
return { fn: { ...fn, blocks: newBlocks }, stats: { constantFolds: folded } }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Pass 2: Copy Propagation
|
|
84
|
-
// Replaces uses of variables that are just copies with their source.
|
|
85
|
-
// e.g. t0 = x; y = t0 + 1 → y = x + 1
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
export function copyPropagation(fn: IRFunction): IRFunction {
|
|
89
|
-
// Build copy map within each block (single-block analysis for simplicity)
|
|
90
|
-
const newBlocks = fn.blocks.map(block => {
|
|
91
|
-
const copies = new Map<string, Operand>() // var → its source if it's a copy
|
|
92
|
-
|
|
93
|
-
function resolve(op: Operand): Operand {
|
|
94
|
-
if (op.kind !== 'var') return op
|
|
95
|
-
return copies.get(op.name) ?? op
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Invalidate all copies that became stale because `written` was modified.
|
|
100
|
-
* When $y is overwritten, any mapping copies[$tmp] = $y is now stale:
|
|
101
|
-
* reading $tmp would return the OLD $y value via the copy, but $y now holds
|
|
102
|
-
* a different value. Remove both the direct entry (copies[$y]) and every
|
|
103
|
-
* reverse entry that points at $y.
|
|
104
|
-
*/
|
|
105
|
-
function invalidate(written: string): void {
|
|
106
|
-
copies.delete(written)
|
|
107
|
-
for (const [k, v] of copies) {
|
|
108
|
-
if (v.kind === 'var' && v.name === written) copies.delete(k)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const newInstrs: IRInstr[] = []
|
|
113
|
-
for (const instr of block.instrs) {
|
|
114
|
-
switch (instr.op) {
|
|
115
|
-
case 'assign': {
|
|
116
|
-
const src = resolve(instr.src)
|
|
117
|
-
invalidate(instr.dst)
|
|
118
|
-
// Only propagate scalars (var or const), not storage
|
|
119
|
-
if (src.kind === 'var' || src.kind === 'const') {
|
|
120
|
-
copies.set(instr.dst, src)
|
|
121
|
-
}
|
|
122
|
-
newInstrs.push({ ...instr, src })
|
|
123
|
-
break
|
|
124
|
-
}
|
|
125
|
-
case 'binop':
|
|
126
|
-
invalidate(instr.dst)
|
|
127
|
-
newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) })
|
|
128
|
-
break
|
|
129
|
-
case 'cmp':
|
|
130
|
-
invalidate(instr.dst)
|
|
131
|
-
newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) })
|
|
132
|
-
break
|
|
133
|
-
case 'call':
|
|
134
|
-
if (instr.dst) invalidate(instr.dst)
|
|
135
|
-
newInstrs.push({ ...instr, args: instr.args.map(resolve) })
|
|
136
|
-
break
|
|
137
|
-
default:
|
|
138
|
-
newInstrs.push(instr)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return { ...block, instrs: newInstrs }
|
|
142
|
-
})
|
|
143
|
-
return { ...fn, blocks: newBlocks }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------------------------
|
|
147
|
-
// Pass 3: Dead Code Elimination
|
|
148
|
-
// Removes assignments to variables that are never read afterward.
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
export function deadCodeElimination(fn: IRFunction): IRFunction {
|
|
152
|
-
return deadCodeEliminationWithStats(fn).fn
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function deadCodeEliminationWithStats(fn: IRFunction): { fn: IRFunction; stats: Partial<OptimizationStats> } {
|
|
156
|
-
// Collect all reads across all blocks
|
|
157
|
-
const readVars = new Set<string>()
|
|
158
|
-
|
|
159
|
-
function markRead(op: Operand) {
|
|
160
|
-
if (op.kind === 'var') readVars.add(op.name)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function markRawReads(cmd: string) {
|
|
164
|
-
for (const match of cmd.matchAll(/\$[A-Za-z0-9_]+/g)) {
|
|
165
|
-
readVars.add(match[0])
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
for (const block of fn.blocks) {
|
|
170
|
-
for (const instr of block.instrs) {
|
|
171
|
-
if (instr.op === 'binop') { markRead(instr.lhs); markRead(instr.rhs) }
|
|
172
|
-
if (instr.op === 'cmp') { markRead(instr.lhs); markRead(instr.rhs) }
|
|
173
|
-
if (instr.op === 'call') { instr.args.forEach(markRead) }
|
|
174
|
-
if (instr.op === 'assign') { markRead(instr.src) }
|
|
175
|
-
if (instr.op === 'raw') { markRawReads(instr.cmd) }
|
|
176
|
-
}
|
|
177
|
-
// Terminator reads
|
|
178
|
-
const t = block.term
|
|
179
|
-
if (t.op === 'jump_if' || t.op === 'jump_unless') readVars.add(t.cond)
|
|
180
|
-
if (t.op === 'return' && t.value) markRead(t.value)
|
|
181
|
-
if (t.op === 'tick_yield') { /* no reads */ }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Also keep params and globals
|
|
185
|
-
fn.params.forEach(p => readVars.add(p))
|
|
186
|
-
|
|
187
|
-
let removed = 0
|
|
188
|
-
const newBlocks = fn.blocks.map(block => ({
|
|
189
|
-
...block,
|
|
190
|
-
instrs: block.instrs.filter(instr => {
|
|
191
|
-
// Only assignments/binops/cmps with an unused dst are candidates for removal
|
|
192
|
-
if (instr.op === 'assign' || instr.op === 'binop' || instr.op === 'cmp') {
|
|
193
|
-
// Always keep assignments to global variables (they may be read by other functions)
|
|
194
|
-
// Temps are $t0, $t1, ...; params are $p0, $p1, ...; locals are $_0, $_1, ...
|
|
195
|
-
// Everything else is a potential global
|
|
196
|
-
const isTemp = /^\$t\d+$/.test(instr.dst) || /^\$p\d+$/.test(instr.dst) || /^\$_\d+$/.test(instr.dst)
|
|
197
|
-
const keep = !isTemp || readVars.has(instr.dst)
|
|
198
|
-
if (!keep) removed++
|
|
199
|
-
return keep
|
|
200
|
-
}
|
|
201
|
-
// calls may have side effects — keep them always
|
|
202
|
-
return true
|
|
203
|
-
}),
|
|
204
|
-
}))
|
|
205
|
-
|
|
206
|
-
return { fn: { ...fn, blocks: newBlocks }, stats: { deadCodeRemoved: removed } }
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
// Pipeline
|
|
211
|
-
// ---------------------------------------------------------------------------
|
|
212
|
-
|
|
213
|
-
export interface OptimizationPass {
|
|
214
|
-
name: string
|
|
215
|
-
run: (fn: IRFunction) => IRFunction
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export const defaultPipeline: OptimizationPass[] = [
|
|
219
|
-
{ name: 'constant-folding', run: constantFolding },
|
|
220
|
-
{ name: 'copy-propagation', run: copyPropagation },
|
|
221
|
-
{ name: 'dead-code-elimination', run: deadCodeElimination },
|
|
222
|
-
// commandMerging is applied during codegen (MC-specific)
|
|
223
|
-
]
|
|
224
|
-
|
|
225
|
-
export function optimize(fn: IRFunction, passes = defaultPipeline): IRFunction {
|
|
226
|
-
return optimizeWithStats(fn, passes).fn
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function optimizeWithStats(fn: IRFunction, passes = defaultPipeline): { fn: IRFunction; stats: OptimizationStats } {
|
|
230
|
-
let current = fn
|
|
231
|
-
const stats = createEmptyOptimizationStats()
|
|
232
|
-
|
|
233
|
-
for (const pass of passes) {
|
|
234
|
-
if (pass.name === 'constant-folding') {
|
|
235
|
-
const result = constantFoldingWithStats(current)
|
|
236
|
-
current = result.fn
|
|
237
|
-
mergeOptimizationStats(stats, result.stats)
|
|
238
|
-
continue
|
|
239
|
-
}
|
|
240
|
-
if (pass.name === 'dead-code-elimination') {
|
|
241
|
-
const result = deadCodeEliminationWithStats(current)
|
|
242
|
-
current = result.fn
|
|
243
|
-
mergeOptimizationStats(stats, result.stats)
|
|
244
|
-
continue
|
|
245
|
-
}
|
|
246
|
-
current = pass.run(current)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return { fn: current, stats }
|
|
250
|
-
}
|
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
import type { IRBlock, IRCommand, IRFunction, IRInstr, Operand, Terminator } from '../ir/types'
|
|
2
|
-
import { createEmptyOptimizationStats, mergeOptimizationStats, optimizeCommandFunctions, setOptimizerObjective, type OptimizationStats } from './commands'
|
|
3
|
-
|
|
4
|
-
let OBJ = 'rs'
|
|
5
|
-
export function setStructureObjective(obj: string): void {
|
|
6
|
-
OBJ = obj
|
|
7
|
-
setOptimizerObjective(obj)
|
|
8
|
-
}
|
|
9
|
-
const INLINE_THRESHOLD = 8
|
|
10
|
-
|
|
11
|
-
const BOP_OP: Record<string, string> = {
|
|
12
|
-
'+': '+=',
|
|
13
|
-
'-': '-=',
|
|
14
|
-
'*': '*=',
|
|
15
|
-
'/': '/=',
|
|
16
|
-
'%': '%=',
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface InlineBlock {
|
|
20
|
-
commands: IRCommand[]
|
|
21
|
-
continuation?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function varRef(name: string): string {
|
|
25
|
-
return name.startsWith('$') ? name : `$${name}`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function operandToScore(op: Operand): string {
|
|
29
|
-
if (op.kind === 'var') return `${varRef(op.name)} ${OBJ}`
|
|
30
|
-
if (op.kind === 'const') return `$const_${op.value} ${OBJ}`
|
|
31
|
-
if (op.kind === 'param') return `$p${op.index} ${OBJ}`
|
|
32
|
-
throw new Error(`Cannot convert storage operand to score: ${(op as any).path}`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function emitInstr(instr: IRInstr, namespace: string): IRCommand[] {
|
|
36
|
-
const commands: IRCommand[] = []
|
|
37
|
-
|
|
38
|
-
switch (instr.op) {
|
|
39
|
-
case 'assign':
|
|
40
|
-
if (instr.src.kind === 'const') {
|
|
41
|
-
commands.push({ cmd: `scoreboard players set ${varRef(instr.dst)} ${OBJ} ${instr.src.value}` })
|
|
42
|
-
} else if (instr.src.kind === 'var') {
|
|
43
|
-
commands.push({
|
|
44
|
-
cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = ${varRef(instr.src.name)} ${OBJ}`,
|
|
45
|
-
})
|
|
46
|
-
} else if (instr.src.kind === 'param') {
|
|
47
|
-
commands.push({
|
|
48
|
-
cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = $p${instr.src.index} ${OBJ}`,
|
|
49
|
-
})
|
|
50
|
-
} else {
|
|
51
|
-
commands.push({
|
|
52
|
-
cmd: `execute store result score ${varRef(instr.dst)} ${OBJ} run data get storage ${instr.src.path}`,
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
break
|
|
56
|
-
|
|
57
|
-
case 'binop':
|
|
58
|
-
commands.push(...emitInstr({ op: 'assign', dst: instr.dst, src: instr.lhs }, namespace))
|
|
59
|
-
commands.push({
|
|
60
|
-
cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} ${BOP_OP[instr.bop]} ${operandToScore(instr.rhs)}`,
|
|
61
|
-
})
|
|
62
|
-
break
|
|
63
|
-
|
|
64
|
-
case 'cmp': {
|
|
65
|
-
const dst = varRef(instr.dst)
|
|
66
|
-
const lhs = operandToScore(instr.lhs)
|
|
67
|
-
const rhs = operandToScore(instr.rhs)
|
|
68
|
-
commands.push({ cmd: `scoreboard players set ${dst} ${OBJ} 0` })
|
|
69
|
-
const op =
|
|
70
|
-
instr.cop === '==' ? 'if score' :
|
|
71
|
-
instr.cop === '!=' ? 'unless score' :
|
|
72
|
-
instr.cop === '<' ? 'if score' :
|
|
73
|
-
instr.cop === '<=' ? 'if score' :
|
|
74
|
-
instr.cop === '>' ? 'if score' :
|
|
75
|
-
'if score'
|
|
76
|
-
const cmp =
|
|
77
|
-
instr.cop === '==' || instr.cop === '!=' ? '=' :
|
|
78
|
-
instr.cop
|
|
79
|
-
commands.push({
|
|
80
|
-
cmd: `execute ${op} ${lhs} ${cmp} ${rhs} run scoreboard players set ${dst} ${OBJ} 1`,
|
|
81
|
-
})
|
|
82
|
-
break
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
case 'call':
|
|
86
|
-
for (let i = 0; i < instr.args.length; i++) {
|
|
87
|
-
commands.push(...emitInstr({ op: 'assign', dst: `$p${i}`, src: instr.args[i] }, namespace))
|
|
88
|
-
}
|
|
89
|
-
commands.push({ cmd: `function ${namespace}:${instr.fn}` })
|
|
90
|
-
if (instr.dst) {
|
|
91
|
-
commands.push({
|
|
92
|
-
cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = $ret ${OBJ}`,
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
break
|
|
96
|
-
|
|
97
|
-
case 'raw':
|
|
98
|
-
commands.push({ cmd: instr.cmd })
|
|
99
|
-
break
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return commands
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function emitReturn(term: Extract<Terminator, { op: 'return' }>): IRCommand[] {
|
|
106
|
-
const commands: IRCommand[] = []
|
|
107
|
-
if (term.value) {
|
|
108
|
-
commands.push(...emitInstr({ op: 'assign', dst: '$ret', src: term.value }, ''))
|
|
109
|
-
}
|
|
110
|
-
if (term.value?.kind === 'const') {
|
|
111
|
-
commands.push({ cmd: `return ${term.value.value}` })
|
|
112
|
-
} else if (term.value?.kind === 'var') {
|
|
113
|
-
commands.push({ cmd: `return run scoreboard players get ${varRef(term.value.name)} ${OBJ}` })
|
|
114
|
-
}
|
|
115
|
-
return commands
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function markConditional(commands: IRCommand[]): IRCommand[] {
|
|
119
|
-
return commands.map(command => ({
|
|
120
|
-
...command,
|
|
121
|
-
conditional: true,
|
|
122
|
-
}))
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function cloneVisited(visited: Set<string>): Set<string> {
|
|
126
|
-
return new Set(visited)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function isRecursiveCommand(command: string, currentFn: string, namespace: string): boolean {
|
|
130
|
-
return command.includes(`function ${namespace}:${currentFn}`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function getInlineableBlock(
|
|
134
|
-
block: IRBlock | undefined,
|
|
135
|
-
currentFn: string,
|
|
136
|
-
namespace: string
|
|
137
|
-
): InlineBlock | null {
|
|
138
|
-
if (!block) return null
|
|
139
|
-
if (block.term.op === 'jump_if' || block.term.op === 'jump_unless' || block.term.op === 'tick_yield') {
|
|
140
|
-
return null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const commands = block.instrs.flatMap(instr => emitInstr(instr, namespace))
|
|
144
|
-
if (commands.some(command => isRecursiveCommand(command.cmd, currentFn, namespace))) {
|
|
145
|
-
return null
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (block.term.op === 'return') {
|
|
149
|
-
commands.push(...emitReturn(block.term))
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (commands.length > INLINE_THRESHOLD) {
|
|
153
|
-
return null
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
commands,
|
|
158
|
-
continuation: block.term.op === 'jump' ? block.term.target : undefined,
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function flattenBlock(
|
|
163
|
-
fn: IRFunction,
|
|
164
|
-
label: string,
|
|
165
|
-
namespace: string,
|
|
166
|
-
visited: Set<string>
|
|
167
|
-
): IRCommand[] {
|
|
168
|
-
const blockMap = new Map(fn.blocks.map(block => [block.label, block]))
|
|
169
|
-
const block = blockMap.get(label)
|
|
170
|
-
if (!block) {
|
|
171
|
-
return []
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (visited.has(label)) {
|
|
175
|
-
return [{ cmd: `function ${namespace}:${fn.name}/${label}`, label }]
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
visited.add(label)
|
|
179
|
-
|
|
180
|
-
const commands: IRCommand[] = []
|
|
181
|
-
if (label === fn.blocks[0]?.label) {
|
|
182
|
-
for (let i = 0; i < fn.params.length; i++) {
|
|
183
|
-
commands.push({
|
|
184
|
-
cmd: `scoreboard players operation ${varRef(fn.params[i])} ${OBJ} = $p${i} ${OBJ}`,
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
commands.push(...block.instrs.flatMap(instr => emitInstr(instr, namespace)))
|
|
189
|
-
const term = block.term
|
|
190
|
-
|
|
191
|
-
switch (term.op) {
|
|
192
|
-
case 'jump':
|
|
193
|
-
commands.push(...flattenBlock(fn, term.target, namespace, visited))
|
|
194
|
-
return commands
|
|
195
|
-
|
|
196
|
-
case 'jump_if':
|
|
197
|
-
case 'jump_unless': {
|
|
198
|
-
const trueLabel = term.op === 'jump_if' ? term.then : term.else_
|
|
199
|
-
const falseLabel = term.op === 'jump_if' ? term.else_ : term.then
|
|
200
|
-
const trueRange = term.op === 'jump_if' ? '1..' : '..0'
|
|
201
|
-
const falseRange = term.op === 'jump_if' ? '..0' : '1..'
|
|
202
|
-
const trueBlock = getInlineableBlock(blockMap.get(trueLabel), fn.name, namespace)
|
|
203
|
-
const falseBlock = getInlineableBlock(blockMap.get(falseLabel), fn.name, namespace)
|
|
204
|
-
|
|
205
|
-
if (trueBlock && falseBlock) {
|
|
206
|
-
if (trueBlock.commands.length > 0) {
|
|
207
|
-
commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${trueRange}`, label: trueLabel })
|
|
208
|
-
commands.push(...markConditional(trueBlock.commands))
|
|
209
|
-
}
|
|
210
|
-
if (falseBlock.commands.length > 0) {
|
|
211
|
-
commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${falseRange}`, label: falseLabel })
|
|
212
|
-
commands.push(...markConditional(falseBlock.commands))
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const continuation = trueBlock.continuation && trueBlock.continuation === falseBlock.continuation
|
|
216
|
-
? trueBlock.continuation
|
|
217
|
-
: undefined
|
|
218
|
-
if (continuation) {
|
|
219
|
-
commands.push(...flattenBlock(fn, continuation, namespace, cloneVisited(visited)))
|
|
220
|
-
}
|
|
221
|
-
return commands
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${trueRange} run function ${namespace}:${fn.name}/${trueLabel}` })
|
|
225
|
-
commands.push({ cmd: `execute if score ${varRef(term.cond)} ${OBJ} matches ${falseRange} run function ${namespace}:${fn.name}/${falseLabel}` })
|
|
226
|
-
return commands
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
case 'return':
|
|
230
|
-
commands.push(...emitReturn(term))
|
|
231
|
-
return commands
|
|
232
|
-
|
|
233
|
-
case 'tick_yield':
|
|
234
|
-
commands.push({ cmd: `schedule function ${namespace}:${fn.name}/${term.continuation} 1t replace` })
|
|
235
|
-
return commands
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function findVars(command: string): string[] {
|
|
240
|
-
return Array.from(command.matchAll(/\$[A-Za-z0-9_]+/g), match => match[0])
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function parsePureWrite(command: string): { dst: string; reads: string[] } | null {
|
|
244
|
-
let match = command.match(/^scoreboard players set (\$[A-Za-z0-9_]+) rs -?\d+$/)
|
|
245
|
-
if (match) {
|
|
246
|
-
return { dst: match[1], reads: [] }
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
match = command.match(/^scoreboard players operation (\$[A-Za-z0-9_]+) rs = (\$[A-Za-z0-9_]+) rs$/)
|
|
250
|
-
if (match) {
|
|
251
|
-
return { dst: match[1], reads: [match[2]] }
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
match = command.match(/^execute .* run scoreboard players set (\$[A-Za-z0-9_]+) rs -?\d+$/)
|
|
255
|
-
if (match) {
|
|
256
|
-
return {
|
|
257
|
-
dst: match[1],
|
|
258
|
-
reads: findVars(command).filter(name => name !== match![1]),
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return null
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function deadStoreEliminate(commands: IRCommand[]): IRCommand[] {
|
|
266
|
-
const live = new Set<string>()
|
|
267
|
-
const kept: IRCommand[] = []
|
|
268
|
-
|
|
269
|
-
for (let i = commands.length - 1; i >= 0; i--) {
|
|
270
|
-
const command = commands[i]
|
|
271
|
-
const pureWrite = parsePureWrite(command.cmd)
|
|
272
|
-
|
|
273
|
-
if (pureWrite) {
|
|
274
|
-
pureWrite.reads.forEach(name => live.add(name))
|
|
275
|
-
if (!live.has(pureWrite.dst)) {
|
|
276
|
-
continue
|
|
277
|
-
}
|
|
278
|
-
live.delete(pureWrite.dst)
|
|
279
|
-
kept.push(command)
|
|
280
|
-
continue
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
findVars(command.cmd).forEach(name => live.add(name))
|
|
284
|
-
kept.push(command)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return kept.reverse()
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function isInlineableFunction(
|
|
291
|
-
fn: IRFunction | undefined,
|
|
292
|
-
currentFn: string,
|
|
293
|
-
namespace: string
|
|
294
|
-
): fn is IRFunction & { commands: IRCommand[] } {
|
|
295
|
-
if (!fn?.commands || fn.name === currentFn || fn.commands.length > INLINE_THRESHOLD) {
|
|
296
|
-
return false
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return !fn.commands.some(command =>
|
|
300
|
-
isRecursiveCommand(command.cmd, currentFn, namespace) ||
|
|
301
|
-
isRecursiveCommand(command.cmd, fn.name, namespace)
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function inlineConditionalCalls(
|
|
306
|
-
commands: IRCommand[],
|
|
307
|
-
functions: Map<string, IRFunction>,
|
|
308
|
-
currentFn: string,
|
|
309
|
-
namespace: string
|
|
310
|
-
): IRCommand[] {
|
|
311
|
-
const optimized: IRCommand[] = []
|
|
312
|
-
|
|
313
|
-
for (const command of commands) {
|
|
314
|
-
const match = command.cmd.match(/^(execute .+) run function ([^:]+):(.+)$/)
|
|
315
|
-
if (!match || match[2] !== namespace) {
|
|
316
|
-
optimized.push(command)
|
|
317
|
-
continue
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const target = functions.get(match[3])
|
|
321
|
-
if (!isInlineableFunction(target, currentFn, namespace)) {
|
|
322
|
-
optimized.push(command)
|
|
323
|
-
continue
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
optimized.push({ cmd: match[1], label: command.label })
|
|
327
|
-
optimized.push(...markConditional(target.commands))
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return optimized
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function invertExecuteCondition(command: string): string | null {
|
|
334
|
-
if (command.startsWith('execute if ')) {
|
|
335
|
-
return command.replace(/^execute if /, 'execute unless ')
|
|
336
|
-
}
|
|
337
|
-
if (command.startsWith('execute unless ')) {
|
|
338
|
-
return command.replace(/^execute unless /, 'execute if ')
|
|
339
|
-
}
|
|
340
|
-
return null
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function eliminateBranchVariables(
|
|
344
|
-
commands: IRCommand[],
|
|
345
|
-
functions: Map<string, IRFunction>,
|
|
346
|
-
currentFn: string,
|
|
347
|
-
namespace: string
|
|
348
|
-
): IRCommand[] {
|
|
349
|
-
const optimized: IRCommand[] = []
|
|
350
|
-
|
|
351
|
-
for (let i = 0; i < commands.length; i++) {
|
|
352
|
-
const init = commands[i]
|
|
353
|
-
const set = commands[i + 1]
|
|
354
|
-
const thenCmd = commands[i + 2]
|
|
355
|
-
const elseCmd = commands[i + 3]
|
|
356
|
-
|
|
357
|
-
const initMatch = init?.cmd.match(/^scoreboard players set (\$[A-Za-z0-9_]+) rs 0$/)
|
|
358
|
-
const setMatch = set?.cmd.match(/^((?:execute if|execute unless) .+) run scoreboard players set (\$[A-Za-z0-9_]+) rs 1$/)
|
|
359
|
-
const thenMatch = thenCmd?.cmd.match(/^execute if score (\$[A-Za-z0-9_]+) rs matches 1\.\. run function [^:]+:(.+)$/)
|
|
360
|
-
const elseMatch =
|
|
361
|
-
elseCmd?.cmd.match(/^execute if score (\$[A-Za-z0-9_]+) rs matches ..0 run function [^:]+:(.+)$/) ??
|
|
362
|
-
elseCmd?.cmd.match(/^execute unless score (\$[A-Za-z0-9_]+) rs matches 1\.\. run function [^:]+:(.+)$/)
|
|
363
|
-
|
|
364
|
-
if (!initMatch || !setMatch || !thenMatch || !elseMatch) {
|
|
365
|
-
optimized.push(init)
|
|
366
|
-
continue
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const branchVar = initMatch[1]
|
|
370
|
-
if (setMatch[2] !== branchVar || thenMatch[1] !== branchVar || elseMatch[1] !== branchVar) {
|
|
371
|
-
optimized.push(init)
|
|
372
|
-
continue
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const thenFn = functions.get(thenMatch[2])
|
|
376
|
-
const elseFn = functions.get(elseMatch[2])
|
|
377
|
-
if (!isInlineableFunction(thenFn, currentFn, namespace) || !isInlineableFunction(elseFn, currentFn, namespace)) {
|
|
378
|
-
optimized.push(init)
|
|
379
|
-
continue
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const thenCondition = setMatch[1]
|
|
383
|
-
const elseCondition = invertExecuteCondition(thenCondition)
|
|
384
|
-
if (!elseCondition) {
|
|
385
|
-
optimized.push(init)
|
|
386
|
-
continue
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
optimized.push({ cmd: thenCondition })
|
|
390
|
-
optimized.push(...markConditional(thenFn.commands))
|
|
391
|
-
if (elseFn.commands.length > 0) {
|
|
392
|
-
optimized.push({ cmd: elseCondition })
|
|
393
|
-
optimized.push(...markConditional(elseFn.commands))
|
|
394
|
-
}
|
|
395
|
-
i += 3
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return optimized
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
export function optimizeFunctionForStructure(
|
|
402
|
-
fn: IRFunction,
|
|
403
|
-
functions: Map<string, IRFunction>,
|
|
404
|
-
namespace: string
|
|
405
|
-
): IRCommand[] {
|
|
406
|
-
if (fn.blocks.length === 0) {
|
|
407
|
-
return []
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const linear = flattenBlock(fn, fn.blocks[0].label, namespace, new Set<string>())
|
|
411
|
-
const branchEliminated = eliminateBranchVariables(linear, functions, fn.name, namespace)
|
|
412
|
-
const inlined = inlineConditionalCalls(branchEliminated, functions, fn.name, namespace)
|
|
413
|
-
return deadStoreEliminate(inlined)
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export function optimizeForStructure(functions: IRFunction[], namespace = 'redscript'): IRFunction[] {
|
|
417
|
-
return optimizeForStructureWithStats(functions, namespace).functions
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export function optimizeForStructureWithStats(
|
|
421
|
-
functions: IRFunction[],
|
|
422
|
-
namespace = 'redscript'
|
|
423
|
-
): { functions: IRFunction[]; stats: OptimizationStats } {
|
|
424
|
-
const staged = new Map(functions.map(fn => [fn.name, { ...fn }]))
|
|
425
|
-
|
|
426
|
-
for (const fn of staged.values()) {
|
|
427
|
-
fn.commands = flattenBlock(fn, fn.blocks[0]?.label ?? 'entry', namespace, new Set<string>())
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
for (const fn of staged.values()) {
|
|
431
|
-
fn.commands = optimizeFunctionForStructure(fn, staged, namespace)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const optimizedCommands = optimizeCommandFunctions(
|
|
435
|
-
Array.from(staged.values()).map(fn => ({
|
|
436
|
-
name: fn.name,
|
|
437
|
-
commands: fn.commands ?? [],
|
|
438
|
-
}))
|
|
439
|
-
)
|
|
440
|
-
const stats = createEmptyOptimizationStats()
|
|
441
|
-
mergeOptimizationStats(stats, optimizedCommands.stats)
|
|
442
|
-
|
|
443
|
-
return {
|
|
444
|
-
functions: Array.from(staged.values()).map(fn => ({
|
|
445
|
-
...fn,
|
|
446
|
-
commands: optimizedCommands.functions.find(candidate => candidate.name === fn.name)?.commands ?? fn.commands,
|
|
447
|
-
})),
|
|
448
|
-
stats,
|
|
449
|
-
}
|
|
450
|
-
}
|