redscript-mc 1.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/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
- package/.github/workflows/ci.yml +29 -0
- package/.github/workflows/publish-extension.yml +35 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/README.zh.md +261 -0
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +140 -0
- package/dist/__tests__/codegen.test.d.ts +1 -0
- package/dist/__tests__/codegen.test.js +121 -0
- package/dist/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/__tests__/diagnostics.test.js +149 -0
- package/dist/__tests__/e2e.test.d.ts +6 -0
- package/dist/__tests__/e2e.test.js +1528 -0
- package/dist/__tests__/lexer.test.d.ts +1 -0
- package/dist/__tests__/lexer.test.js +316 -0
- package/dist/__tests__/lowering.test.d.ts +1 -0
- package/dist/__tests__/lowering.test.js +819 -0
- package/dist/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/__tests__/mc-integration.test.js +395 -0
- package/dist/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/__tests__/mc-syntax.test.js +112 -0
- package/dist/__tests__/nbt.test.d.ts +1 -0
- package/dist/__tests__/nbt.test.js +82 -0
- package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/__tests__/optimizer.test.d.ts +1 -0
- package/dist/__tests__/optimizer.test.js +118 -0
- package/dist/__tests__/parser.test.d.ts +1 -0
- package/dist/__tests__/parser.test.js +717 -0
- package/dist/__tests__/repl.test.d.ts +1 -0
- package/dist/__tests__/repl.test.js +27 -0
- package/dist/__tests__/runtime.test.d.ts +1 -0
- package/dist/__tests__/runtime.test.js +276 -0
- package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/__tests__/structure-optimizer.test.js +33 -0
- package/dist/__tests__/typechecker.test.d.ts +1 -0
- package/dist/__tests__/typechecker.test.js +364 -0
- package/dist/ast/types.d.ts +357 -0
- package/dist/ast/types.js +9 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +407 -0
- package/dist/codegen/cmdblock/index.d.ts +26 -0
- package/dist/codegen/cmdblock/index.js +45 -0
- package/dist/codegen/mcfunction/index.d.ts +34 -0
- package/dist/codegen/mcfunction/index.js +413 -0
- package/dist/codegen/structure/index.d.ts +18 -0
- package/dist/codegen/structure/index.js +249 -0
- package/dist/compile.d.ts +30 -0
- package/dist/compile.js +152 -0
- package/dist/data/arena/function/__load.mcfunction +6 -0
- package/dist/data/arena/function/__tick.mcfunction +2 -0
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
- package/dist/data/arena/function/arena_tick.mcfunction +11 -0
- package/dist/data/counter/function/__load.mcfunction +5 -0
- package/dist/data/counter/function/__tick.mcfunction +2 -0
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
- package/dist/data/counter/function/counter_tick.mcfunction +11 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/minecraft/tags/function/tick.json +5 -0
- package/dist/data/quiz/function/__load.mcfunction +16 -0
- package/dist/data/quiz/function/__tick.mcfunction +6 -0
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/answer_a.mcfunction +4 -0
- package/dist/data/quiz/function/answer_b.mcfunction +4 -0
- package/dist/data/quiz/function/answer_c.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question.mcfunction +7 -0
- package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
- package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
- package/dist/data/shop/function/__load.mcfunction +7 -0
- package/dist/data/shop/function/__tick.mcfunction +3 -0
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
- package/dist/data/turret/function/__load.mcfunction +5 -0
- package/dist/data/turret/function/__tick.mcfunction +4 -0
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
- package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
- package/dist/data/turret/function/turret_tick.mcfunction +5 -0
- package/dist/diagnostics/index.d.ts +44 -0
- package/dist/diagnostics/index.js +140 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +126 -0
- package/dist/ir/builder.d.ts +32 -0
- package/dist/ir/builder.js +99 -0
- package/dist/ir/types.d.ts +117 -0
- package/dist/ir/types.js +15 -0
- package/dist/lexer/index.d.ts +36 -0
- package/dist/lexer/index.js +458 -0
- package/dist/lowering/index.d.ts +106 -0
- package/dist/lowering/index.js +2041 -0
- package/dist/mc-test/client.d.ts +128 -0
- package/dist/mc-test/client.js +174 -0
- package/dist/mc-test/runner.d.ts +28 -0
- package/dist/mc-test/runner.js +150 -0
- package/dist/mc-test/setup.d.ts +11 -0
- package/dist/mc-test/setup.js +98 -0
- package/dist/mc-validator/index.d.ts +17 -0
- package/dist/mc-validator/index.js +322 -0
- package/dist/nbt/index.d.ts +86 -0
- package/dist/nbt/index.js +250 -0
- package/dist/optimizer/commands.d.ts +36 -0
- package/dist/optimizer/commands.js +349 -0
- package/dist/optimizer/passes.d.ts +34 -0
- package/dist/optimizer/passes.js +227 -0
- package/dist/optimizer/structure.d.ts +8 -0
- package/dist/optimizer/structure.js +344 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/parser/index.d.ts +76 -0
- package/dist/parser/index.js +1193 -0
- package/dist/repl.d.ts +16 -0
- package/dist/repl.js +165 -0
- package/dist/runtime/index.d.ts +101 -0
- package/dist/runtime/index.js +1288 -0
- package/dist/typechecker/index.d.ts +42 -0
- package/dist/typechecker/index.js +629 -0
- package/docs/COMPILATION_STATS.md +142 -0
- package/docs/IMPLEMENTATION_GUIDE.md +512 -0
- package/docs/LANGUAGE_REFERENCE.md +415 -0
- package/docs/MC_MAPPING.md +280 -0
- package/docs/STRUCTURE_TARGET.md +80 -0
- package/docs/mc-reference/commands.md +259 -0
- package/editors/vscode/.vscodeignore +10 -0
- package/editors/vscode/LICENSE +21 -0
- package/editors/vscode/README.md +78 -0
- package/editors/vscode/build.mjs +28 -0
- package/editors/vscode/icon.png +0 -0
- package/editors/vscode/mcfunction-language-configuration.json +28 -0
- package/editors/vscode/out/extension.js +7236 -0
- package/editors/vscode/package-lock.json +566 -0
- package/editors/vscode/package.json +137 -0
- package/editors/vscode/redscript-language-configuration.json +28 -0
- package/editors/vscode/snippets/redscript.json +114 -0
- package/editors/vscode/src/codeactions.ts +89 -0
- package/editors/vscode/src/completion.ts +130 -0
- package/editors/vscode/src/extension.ts +239 -0
- package/editors/vscode/src/hover.ts +1120 -0
- package/editors/vscode/src/symbols.ts +207 -0
- package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
- package/editors/vscode/tsconfig.json +13 -0
- package/jest.config.js +5 -0
- package/package.json +38 -0
- package/src/__tests__/cli.test.ts +130 -0
- package/src/__tests__/codegen.test.ts +128 -0
- package/src/__tests__/diagnostics.test.ts +195 -0
- package/src/__tests__/e2e.test.ts +1721 -0
- package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
- package/src/__tests__/formatter.test.ts +46 -0
- package/src/__tests__/lexer.test.ts +356 -0
- package/src/__tests__/lowering.test.ts +962 -0
- package/src/__tests__/mc-integration.test.ts +409 -0
- package/src/__tests__/mc-syntax.test.ts +96 -0
- package/src/__tests__/nbt.test.ts +58 -0
- package/src/__tests__/optimizer-advanced.test.ts +144 -0
- package/src/__tests__/optimizer.test.ts +129 -0
- package/src/__tests__/parser.test.ts +800 -0
- package/src/__tests__/repl.test.ts +33 -0
- package/src/__tests__/runtime.test.ts +289 -0
- package/src/__tests__/structure-optimizer.test.ts +38 -0
- package/src/__tests__/typechecker.test.ts +395 -0
- package/src/ast/types.ts +248 -0
- package/src/cli.ts +445 -0
- package/src/codegen/cmdblock/index.ts +63 -0
- package/src/codegen/mcfunction/index.ts +471 -0
- package/src/codegen/structure/index.ts +305 -0
- package/src/compile.ts +188 -0
- package/src/diagnostics/index.ts +186 -0
- package/src/examples/README.md +77 -0
- package/src/examples/SHOWCASE_GAME.md +43 -0
- package/src/examples/arena.rs +44 -0
- package/src/examples/counter.rs +12 -0
- package/src/examples/pvp_arena.rs +131 -0
- package/src/examples/quiz.rs +90 -0
- package/src/examples/rpg.rs +13 -0
- package/src/examples/shop.rs +30 -0
- package/src/examples/showcase_game.rs +552 -0
- package/src/examples/stdlib_demo.rs +181 -0
- package/src/examples/turret.rs +27 -0
- package/src/examples/world_manager.rs +23 -0
- package/src/formatter/index.ts +22 -0
- package/src/index.ts +161 -0
- package/src/ir/builder.ts +114 -0
- package/src/ir/types.ts +119 -0
- package/src/lexer/index.ts +555 -0
- package/src/lowering/index.ts +2406 -0
- package/src/mc-test/client.ts +259 -0
- package/src/mc-test/runner.ts +140 -0
- package/src/mc-test/setup.ts +70 -0
- package/src/mc-validator/index.ts +367 -0
- package/src/nbt/index.ts +321 -0
- package/src/optimizer/commands.ts +416 -0
- package/src/optimizer/passes.ts +233 -0
- package/src/optimizer/structure.ts +441 -0
- package/src/parser/index.ts +1437 -0
- package/src/repl.ts +165 -0
- package/src/runtime/index.ts +1403 -0
- package/src/stdlib/README.md +156 -0
- package/src/stdlib/combat.rs +20 -0
- package/src/stdlib/cooldown.rs +45 -0
- package/src/stdlib/math.rs +49 -0
- package/src/stdlib/mobs.rs +99 -0
- package/src/stdlib/player.rs +29 -0
- package/src/stdlib/strings.rs +7 -0
- package/src/stdlib/timer.rs +51 -0
- package/src/templates/README.md +126 -0
- package/src/templates/combat.rs +96 -0
- package/src/templates/economy.rs +40 -0
- package/src/templates/mini-game-framework.rs +117 -0
- package/src/templates/quest.rs +78 -0
- package/src/test_programs/zombie_game.rs +25 -0
- package/src/typechecker/index.ts +737 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import type { IRCommand } from '../ir/types'
|
|
2
|
+
|
|
3
|
+
export interface OptimizationStats {
|
|
4
|
+
licmHoists: number
|
|
5
|
+
licmLoopBodies: number
|
|
6
|
+
cseRedundantReads: number
|
|
7
|
+
cseArithmetic: number
|
|
8
|
+
setblockMergedCommands: number
|
|
9
|
+
setblockFillCommands: number
|
|
10
|
+
setblockSavedCommands: number
|
|
11
|
+
deadCodeRemoved: number
|
|
12
|
+
constantFolds: number
|
|
13
|
+
totalCommandsBefore: number
|
|
14
|
+
totalCommandsAfter: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CommandFunction {
|
|
18
|
+
name: string
|
|
19
|
+
commands: IRCommand[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const SCOREBOARD_READ_RE =
|
|
23
|
+
/^execute store result score (\$[A-Za-z0-9_]+) rs run scoreboard players get (\S+) (\S+)$/
|
|
24
|
+
const SCOREBOARD_WRITE_RE =
|
|
25
|
+
/^(?:scoreboard players (?:set|add|remove|reset)\s+(\S+)\s+(\S+)|scoreboard players operation\s+(\S+)\s+(\S+)\s+[+\-*/%]?= )/
|
|
26
|
+
const EXECUTE_STORE_SCORE_RE =
|
|
27
|
+
/^execute store result score (\S+) (\S+) run /
|
|
28
|
+
const FUNCTION_CALL_RE = /^execute as (.+) run function ([^:]+):(.+)$/
|
|
29
|
+
const TEMP_RE = /\$[A-Za-z0-9_]+/g
|
|
30
|
+
const SETBLOCK_RE = /^setblock (-?\d+) (-?\d+) (-?\d+) (\S+)$/
|
|
31
|
+
|
|
32
|
+
export function createEmptyOptimizationStats(): OptimizationStats {
|
|
33
|
+
return {
|
|
34
|
+
licmHoists: 0,
|
|
35
|
+
licmLoopBodies: 0,
|
|
36
|
+
cseRedundantReads: 0,
|
|
37
|
+
cseArithmetic: 0,
|
|
38
|
+
setblockMergedCommands: 0,
|
|
39
|
+
setblockFillCommands: 0,
|
|
40
|
+
setblockSavedCommands: 0,
|
|
41
|
+
deadCodeRemoved: 0,
|
|
42
|
+
constantFolds: 0,
|
|
43
|
+
totalCommandsBefore: 0,
|
|
44
|
+
totalCommandsAfter: 0,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cloneCommand(command: IRCommand): IRCommand {
|
|
49
|
+
return { ...command }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cloneFunctions(functions: CommandFunction[]): CommandFunction[] {
|
|
53
|
+
return functions.map(fn => ({
|
|
54
|
+
name: fn.name,
|
|
55
|
+
commands: fn.commands.map(cloneCommand),
|
|
56
|
+
}))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function mergeOptimizationStats(base: OptimizationStats, delta: Partial<OptimizationStats>): void {
|
|
60
|
+
for (const [key, value] of Object.entries(delta)) {
|
|
61
|
+
base[key as keyof OptimizationStats] += value as number
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseScoreboardWrite(command: string): { player: string; objective: string } | null {
|
|
66
|
+
const executeStoreMatch = command.match(EXECUTE_STORE_SCORE_RE)
|
|
67
|
+
if (executeStoreMatch) {
|
|
68
|
+
return { player: executeStoreMatch[1], objective: executeStoreMatch[2] }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const match = command.match(SCOREBOARD_WRITE_RE)
|
|
72
|
+
if (!match) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (match[1] && match[2]) {
|
|
77
|
+
return { player: match[1], objective: match[2] }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (match[3] && match[4]) {
|
|
81
|
+
return { player: match[3], objective: match[4] }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function replaceTemp(command: string, from: string, to: string): string {
|
|
88
|
+
const re = new RegExp(`\\${from}(?![A-Za-z0-9_])`, 'g')
|
|
89
|
+
return command.replace(re, to)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function collectObjectiveWrites(functions: CommandFunction[]): Map<string, number> {
|
|
93
|
+
const writes = new Map<string, number>()
|
|
94
|
+
|
|
95
|
+
for (const fn of functions) {
|
|
96
|
+
for (const command of fn.commands) {
|
|
97
|
+
const write = parseScoreboardWrite(command.cmd)
|
|
98
|
+
if (!write) continue
|
|
99
|
+
writes.set(write.objective, (writes.get(write.objective) ?? 0) + 1)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return writes
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function applyLICMInternal(functions: CommandFunction[]): Partial<OptimizationStats> {
|
|
107
|
+
const stats: Partial<OptimizationStats> = { licmHoists: 0, licmLoopBodies: 0 }
|
|
108
|
+
const functionMap = new Map(functions.map(fn => [fn.name, fn]))
|
|
109
|
+
const objectiveWrites = collectObjectiveWrites(functions)
|
|
110
|
+
|
|
111
|
+
for (const fn of functions) {
|
|
112
|
+
const nextCommands: IRCommand[] = []
|
|
113
|
+
|
|
114
|
+
for (const command of fn.commands) {
|
|
115
|
+
const match = command.cmd.match(FUNCTION_CALL_RE)
|
|
116
|
+
if (!match) {
|
|
117
|
+
nextCommands.push(command)
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const loopFn = functionMap.get(match[3])
|
|
122
|
+
if (!loopFn) {
|
|
123
|
+
nextCommands.push(command)
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const readInfo = new Map<string, { temp: string; player: string; objective: string; uses: number }>()
|
|
128
|
+
const scoreboardWrites = new Set<string>()
|
|
129
|
+
|
|
130
|
+
for (const inner of loopFn.commands) {
|
|
131
|
+
const readMatch = inner.cmd.match(SCOREBOARD_READ_RE)
|
|
132
|
+
if (readMatch) {
|
|
133
|
+
const [, temp, player, objective] = readMatch
|
|
134
|
+
const key = `${player} ${objective}`
|
|
135
|
+
readInfo.set(key, { temp, player, objective, uses: 0 })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const write = parseScoreboardWrite(inner.cmd)
|
|
139
|
+
if (write) {
|
|
140
|
+
scoreboardWrites.add(`${write.player} ${write.objective}`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const inner of loopFn.commands) {
|
|
145
|
+
for (const info of readInfo.values()) {
|
|
146
|
+
const matches = inner.cmd.match(TEMP_RE) ?? []
|
|
147
|
+
const usageCount = matches.filter(name => name === info.temp).length
|
|
148
|
+
const isDef = inner.cmd.startsWith(`execute store result score ${info.temp} rs run scoreboard players get `)
|
|
149
|
+
if (!isDef) {
|
|
150
|
+
info.uses += usageCount
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const hoistable = Array.from(readInfo.entries())
|
|
156
|
+
.filter(([key, info]) => {
|
|
157
|
+
if (info.uses < 2) return false
|
|
158
|
+
if ((objectiveWrites.get(info.objective) ?? 0) !== 0) return false
|
|
159
|
+
if (scoreboardWrites.has(key)) return false
|
|
160
|
+
return true
|
|
161
|
+
})
|
|
162
|
+
.map(([, info]) => info)
|
|
163
|
+
|
|
164
|
+
if (hoistable.length === 0) {
|
|
165
|
+
nextCommands.push(command)
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const hoistedTemps = new Set(hoistable.map(item => item.temp))
|
|
170
|
+
const rewrittenLoopCommands: IRCommand[] = []
|
|
171
|
+
|
|
172
|
+
for (const inner of loopFn.commands) {
|
|
173
|
+
const readMatch = inner.cmd.match(SCOREBOARD_READ_RE)
|
|
174
|
+
if (readMatch && hoistedTemps.has(readMatch[1])) {
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
rewrittenLoopCommands.push(inner)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
loopFn.commands = rewrittenLoopCommands
|
|
181
|
+
nextCommands.push(
|
|
182
|
+
...hoistable.map(item => ({
|
|
183
|
+
cmd: `execute store result score ${item.temp} rs run scoreboard players get ${item.player} ${item.objective}`,
|
|
184
|
+
})),
|
|
185
|
+
command
|
|
186
|
+
)
|
|
187
|
+
stats.licmHoists = (stats.licmHoists ?? 0) + hoistable.length
|
|
188
|
+
stats.licmLoopBodies = (stats.licmLoopBodies ?? 0) + 1
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fn.commands = nextCommands
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return stats
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function extractArithmeticExpression(commands: IRCommand[], index: number): { key: string; dst: string } | null {
|
|
198
|
+
const assign =
|
|
199
|
+
commands[index]?.cmd.match(/^scoreboard players operation (\$[A-Za-z0-9_]+) rs = (\$[A-Za-z0-9_]+|\$const_-?\d+) rs$/) ??
|
|
200
|
+
commands[index]?.cmd.match(/^scoreboard players set (\$[A-Za-z0-9_]+) rs (-?\d+)$/)
|
|
201
|
+
const op = commands[index + 1]?.cmd.match(/^scoreboard players operation (\$[A-Za-z0-9_]+) rs ([+\-*/%]=) (\$[A-Za-z0-9_]+|\$const_-?\d+) rs$/)
|
|
202
|
+
if (!assign || !op || assign[1] !== op[1]) {
|
|
203
|
+
return null
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
key: `${assign[2]} ${op[2]} ${op[3]}`,
|
|
207
|
+
dst: assign[1],
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function applyCSEInternal(functions: CommandFunction[]): Partial<OptimizationStats> {
|
|
212
|
+
const stats: Partial<OptimizationStats> = { cseRedundantReads: 0, cseArithmetic: 0 }
|
|
213
|
+
|
|
214
|
+
for (const fn of functions) {
|
|
215
|
+
const commands = fn.commands.map(cloneCommand)
|
|
216
|
+
const readCache = new Map<string, string>()
|
|
217
|
+
const exprCache = new Map<string, string>()
|
|
218
|
+
const rewritten: IRCommand[] = []
|
|
219
|
+
|
|
220
|
+
function invalidateByTemp(temp: string): void {
|
|
221
|
+
for (const [key, value] of readCache.entries()) {
|
|
222
|
+
if (value === temp || key.includes(`${temp} `) || key.endsWith(` ${temp}`)) {
|
|
223
|
+
readCache.delete(key)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const [key, value] of exprCache.entries()) {
|
|
227
|
+
if (value === temp || key.includes(temp)) {
|
|
228
|
+
exprCache.delete(key)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < commands.length; i++) {
|
|
234
|
+
const command = commands[i]
|
|
235
|
+
const readMatch = command.cmd.match(SCOREBOARD_READ_RE)
|
|
236
|
+
if (readMatch) {
|
|
237
|
+
const [, dst, player, objective] = readMatch
|
|
238
|
+
const key = `${player} ${objective}`
|
|
239
|
+
const cached = readCache.get(key)
|
|
240
|
+
if (cached) {
|
|
241
|
+
stats.cseRedundantReads = (stats.cseRedundantReads ?? 0) + 1
|
|
242
|
+
rewritten.push({ ...command, cmd: `scoreboard players operation ${dst} rs = ${cached} rs` })
|
|
243
|
+
} else {
|
|
244
|
+
readCache.set(key, dst)
|
|
245
|
+
rewritten.push(command)
|
|
246
|
+
}
|
|
247
|
+
invalidateByTemp(dst)
|
|
248
|
+
readCache.set(key, dst)
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const expr = extractArithmeticExpression(commands, i)
|
|
253
|
+
if (expr) {
|
|
254
|
+
const cached = exprCache.get(expr.key)
|
|
255
|
+
if (cached) {
|
|
256
|
+
rewritten.push({ ...commands[i], cmd: `scoreboard players operation ${expr.dst} rs = ${cached} rs` })
|
|
257
|
+
stats.cseArithmetic = (stats.cseArithmetic ?? 0) + 1
|
|
258
|
+
i += 1
|
|
259
|
+
} else {
|
|
260
|
+
rewritten.push(command)
|
|
261
|
+
rewritten.push(commands[i + 1])
|
|
262
|
+
exprCache.set(expr.key, expr.dst)
|
|
263
|
+
i += 1
|
|
264
|
+
}
|
|
265
|
+
invalidateByTemp(expr.dst)
|
|
266
|
+
exprCache.set(expr.key, expr.dst)
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const write = parseScoreboardWrite(command.cmd)
|
|
271
|
+
if (write) {
|
|
272
|
+
readCache.delete(`${write.player} ${write.objective}`)
|
|
273
|
+
if (write.player.startsWith('$')) {
|
|
274
|
+
invalidateByTemp(write.player)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
rewritten.push(command)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fn.commands = rewritten
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return stats
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function batchSetblocksInCommands(commands: IRCommand[]): { commands: IRCommand[]; stats: Partial<OptimizationStats> } {
|
|
288
|
+
const rewritten: IRCommand[] = []
|
|
289
|
+
const stats: Partial<OptimizationStats> = {
|
|
290
|
+
setblockMergedCommands: 0,
|
|
291
|
+
setblockFillCommands: 0,
|
|
292
|
+
setblockSavedCommands: 0,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (let i = 0; i < commands.length; ) {
|
|
296
|
+
const start = commands[i].cmd.match(SETBLOCK_RE)
|
|
297
|
+
if (!start) {
|
|
298
|
+
rewritten.push(commands[i])
|
|
299
|
+
i++
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const block = start[4]
|
|
304
|
+
const run = [{ index: i, x: Number(start[1]), y: Number(start[2]), z: Number(start[3]) }]
|
|
305
|
+
let axis: 'x' | 'z' | null = null
|
|
306
|
+
let j = i + 1
|
|
307
|
+
|
|
308
|
+
while (j < commands.length) {
|
|
309
|
+
const next = commands[j].cmd.match(SETBLOCK_RE)
|
|
310
|
+
if (!next || next[4] !== block) break
|
|
311
|
+
|
|
312
|
+
const point = { x: Number(next[1]), y: Number(next[2]), z: Number(next[3]) }
|
|
313
|
+
const prev = run[run.length - 1]
|
|
314
|
+
if (point.y !== prev.y) break
|
|
315
|
+
|
|
316
|
+
const stepX = point.x - prev.x
|
|
317
|
+
const stepZ = point.z - prev.z
|
|
318
|
+
if (axis === null) {
|
|
319
|
+
if (stepX === 1 && stepZ === 0) axis = 'x'
|
|
320
|
+
else if (stepX === 0 && stepZ === 1) axis = 'z'
|
|
321
|
+
else break
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const valid = axis === 'x'
|
|
325
|
+
? point.z === prev.z && stepX === 1 && stepZ === 0
|
|
326
|
+
: point.x === prev.x && stepX === 0 && stepZ === 1
|
|
327
|
+
if (!valid) break
|
|
328
|
+
|
|
329
|
+
run.push({ index: j, ...point })
|
|
330
|
+
j++
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (run.length >= 2) {
|
|
334
|
+
const first = run[0]
|
|
335
|
+
const last = run[run.length - 1]
|
|
336
|
+
rewritten.push({
|
|
337
|
+
...commands[i],
|
|
338
|
+
cmd: `fill ${first.x} ${first.y} ${first.z} ${last.x} ${last.y} ${last.z} ${block}`,
|
|
339
|
+
})
|
|
340
|
+
stats.setblockMergedCommands = (stats.setblockMergedCommands ?? 0) + run.length
|
|
341
|
+
stats.setblockFillCommands = (stats.setblockFillCommands ?? 0) + 1
|
|
342
|
+
stats.setblockSavedCommands = (stats.setblockSavedCommands ?? 0) + (run.length - 1)
|
|
343
|
+
i = j
|
|
344
|
+
continue
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
rewritten.push(commands[i])
|
|
348
|
+
i++
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return { commands: rewritten, stats }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function applySetblockBatchingInternal(functions: CommandFunction[]): Partial<OptimizationStats> {
|
|
355
|
+
const stats: Partial<OptimizationStats> = {
|
|
356
|
+
setblockMergedCommands: 0,
|
|
357
|
+
setblockFillCommands: 0,
|
|
358
|
+
setblockSavedCommands: 0,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const fn of functions) {
|
|
362
|
+
const batched = batchSetblocksInCommands(fn.commands)
|
|
363
|
+
fn.commands = batched.commands
|
|
364
|
+
mergeOptimizationStats(stats as OptimizationStats, batched.stats)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return stats
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function applyLICM(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
|
|
371
|
+
const optimized = cloneFunctions(functions)
|
|
372
|
+
const stats = createEmptyOptimizationStats()
|
|
373
|
+
stats.totalCommandsBefore = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
374
|
+
mergeOptimizationStats(stats, applyLICMInternal(optimized))
|
|
375
|
+
stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
376
|
+
return { functions: optimized, stats }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function applyCSE(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
|
|
380
|
+
const optimized = cloneFunctions(functions)
|
|
381
|
+
const stats = createEmptyOptimizationStats()
|
|
382
|
+
stats.totalCommandsBefore = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
383
|
+
mergeOptimizationStats(stats, applyCSEInternal(optimized))
|
|
384
|
+
stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
385
|
+
return { functions: optimized, stats }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function batchSetblocks(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
|
|
389
|
+
const optimized = cloneFunctions(functions)
|
|
390
|
+
const stats = createEmptyOptimizationStats()
|
|
391
|
+
stats.totalCommandsBefore = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
392
|
+
mergeOptimizationStats(stats, applySetblockBatchingInternal(optimized))
|
|
393
|
+
stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
394
|
+
return { functions: optimized, stats }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function optimizeCommandFunctions(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
|
|
398
|
+
const initial = cloneFunctions(functions)
|
|
399
|
+
const stats = createEmptyOptimizationStats()
|
|
400
|
+
stats.totalCommandsBefore = initial.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
401
|
+
|
|
402
|
+
const licm = applyLICM(initial)
|
|
403
|
+
mergeOptimizationStats(stats, licm.stats)
|
|
404
|
+
|
|
405
|
+
const cse = applyCSE(licm.functions)
|
|
406
|
+
mergeOptimizationStats(stats, cse.stats)
|
|
407
|
+
|
|
408
|
+
const batched = batchSetblocks(cse.functions)
|
|
409
|
+
mergeOptimizationStats(stats, batched.stats)
|
|
410
|
+
stats.totalCommandsAfter = batched.functions.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
functions: batched.functions,
|
|
414
|
+
stats,
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
const newInstrs: IRInstr[] = []
|
|
99
|
+
for (const instr of block.instrs) {
|
|
100
|
+
switch (instr.op) {
|
|
101
|
+
case 'assign': {
|
|
102
|
+
const src = resolve(instr.src)
|
|
103
|
+
// Only propagate scalars (var or const), not storage
|
|
104
|
+
if (src.kind === 'var' || src.kind === 'const') {
|
|
105
|
+
copies.set(instr.dst, src)
|
|
106
|
+
} else {
|
|
107
|
+
copies.delete(instr.dst)
|
|
108
|
+
}
|
|
109
|
+
newInstrs.push({ ...instr, src })
|
|
110
|
+
break
|
|
111
|
+
}
|
|
112
|
+
case 'binop':
|
|
113
|
+
copies.delete(instr.dst)
|
|
114
|
+
newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) })
|
|
115
|
+
break
|
|
116
|
+
case 'cmp':
|
|
117
|
+
copies.delete(instr.dst)
|
|
118
|
+
newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) })
|
|
119
|
+
break
|
|
120
|
+
case 'call':
|
|
121
|
+
if (instr.dst) copies.delete(instr.dst)
|
|
122
|
+
newInstrs.push({ ...instr, args: instr.args.map(resolve) })
|
|
123
|
+
break
|
|
124
|
+
default:
|
|
125
|
+
newInstrs.push(instr)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { ...block, instrs: newInstrs }
|
|
129
|
+
})
|
|
130
|
+
return { ...fn, blocks: newBlocks }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Pass 3: Dead Code Elimination
|
|
135
|
+
// Removes assignments to variables that are never read afterward.
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
export function deadCodeElimination(fn: IRFunction): IRFunction {
|
|
139
|
+
return deadCodeEliminationWithStats(fn).fn
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function deadCodeEliminationWithStats(fn: IRFunction): { fn: IRFunction; stats: Partial<OptimizationStats> } {
|
|
143
|
+
// Collect all reads across all blocks
|
|
144
|
+
const readVars = new Set<string>()
|
|
145
|
+
|
|
146
|
+
function markRead(op: Operand) {
|
|
147
|
+
if (op.kind === 'var') readVars.add(op.name)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function markRawReads(cmd: string) {
|
|
151
|
+
for (const match of cmd.matchAll(/\$[A-Za-z0-9_]+/g)) {
|
|
152
|
+
readVars.add(match[0])
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const block of fn.blocks) {
|
|
157
|
+
for (const instr of block.instrs) {
|
|
158
|
+
if (instr.op === 'binop') { markRead(instr.lhs); markRead(instr.rhs) }
|
|
159
|
+
if (instr.op === 'cmp') { markRead(instr.lhs); markRead(instr.rhs) }
|
|
160
|
+
if (instr.op === 'call') { instr.args.forEach(markRead) }
|
|
161
|
+
if (instr.op === 'assign') { markRead(instr.src) }
|
|
162
|
+
if (instr.op === 'raw') { markRawReads(instr.cmd) }
|
|
163
|
+
}
|
|
164
|
+
// Terminator reads
|
|
165
|
+
const t = block.term
|
|
166
|
+
if (t.op === 'jump_if' || t.op === 'jump_unless') readVars.add(t.cond)
|
|
167
|
+
if (t.op === 'return' && t.value) markRead(t.value)
|
|
168
|
+
if (t.op === 'tick_yield') { /* no reads */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Also keep params and globals
|
|
172
|
+
fn.params.forEach(p => readVars.add(p))
|
|
173
|
+
|
|
174
|
+
let removed = 0
|
|
175
|
+
const newBlocks = fn.blocks.map(block => ({
|
|
176
|
+
...block,
|
|
177
|
+
instrs: block.instrs.filter(instr => {
|
|
178
|
+
// Only assignments/binops/cmps with an unused dst are candidates for removal
|
|
179
|
+
if (instr.op === 'assign' || instr.op === 'binop' || instr.op === 'cmp') {
|
|
180
|
+
const keep = readVars.has(instr.dst)
|
|
181
|
+
if (!keep) removed++
|
|
182
|
+
return keep
|
|
183
|
+
}
|
|
184
|
+
// calls may have side effects — keep them always
|
|
185
|
+
return true
|
|
186
|
+
}),
|
|
187
|
+
}))
|
|
188
|
+
|
|
189
|
+
return { fn: { ...fn, blocks: newBlocks }, stats: { deadCodeRemoved: removed } }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Pipeline
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
export interface OptimizationPass {
|
|
197
|
+
name: string
|
|
198
|
+
run: (fn: IRFunction) => IRFunction
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const defaultPipeline: OptimizationPass[] = [
|
|
202
|
+
{ name: 'constant-folding', run: constantFolding },
|
|
203
|
+
{ name: 'copy-propagation', run: copyPropagation },
|
|
204
|
+
{ name: 'dead-code-elimination', run: deadCodeElimination },
|
|
205
|
+
// commandMerging is applied during codegen (MC-specific)
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
export function optimize(fn: IRFunction, passes = defaultPipeline): IRFunction {
|
|
209
|
+
return optimizeWithStats(fn, passes).fn
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function optimizeWithStats(fn: IRFunction, passes = defaultPipeline): { fn: IRFunction; stats: OptimizationStats } {
|
|
213
|
+
let current = fn
|
|
214
|
+
const stats = createEmptyOptimizationStats()
|
|
215
|
+
|
|
216
|
+
for (const pass of passes) {
|
|
217
|
+
if (pass.name === 'constant-folding') {
|
|
218
|
+
const result = constantFoldingWithStats(current)
|
|
219
|
+
current = result.fn
|
|
220
|
+
mergeOptimizationStats(stats, result.stats)
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
if (pass.name === 'dead-code-elimination') {
|
|
224
|
+
const result = deadCodeEliminationWithStats(current)
|
|
225
|
+
current = result.fn
|
|
226
|
+
mergeOptimizationStats(stats, result.stats)
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
current = pass.run(current)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { fn: current, stats }
|
|
233
|
+
}
|