redscript-mc 2.6.2 → 3.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/workflows/ci.yml +11 -0
- package/CHANGELOG.md +18 -9
- package/README-benchmarks.md +48 -0
- package/README-vscode-test.md +251 -0
- package/RELEASE_NOTES.md +74 -0
- package/ROADMAP.md +131 -167
- package/benchmarks/_shared.ts +468 -0
- package/benchmarks/baseline.json +2816 -0
- package/benchmarks/baseline.md +13 -0
- package/benchmarks/compiler-perf.report.json +207 -0
- package/benchmarks/compiler-perf.ts +76 -0
- package/benchmarks/results.md +13 -0
- package/benchmarks/stdlib-complexity.report.json +2606 -0
- package/benchmarks/stdlib-complexity.ts +54 -0
- package/benchmarks/stdlib-size.md +57 -0
- package/benchmarks/stdlib-size.ts +91 -0
- package/coverage-report.md +177 -0
- package/dist/src/__tests__/budget.test.js +4 -0
- package/dist/src/__tests__/cache/cache-behavior.test.d.ts +10 -0
- package/dist/src/__tests__/cache/cache-behavior.test.js +425 -0
- package/dist/src/__tests__/cache-extra.test.d.ts +5 -0
- package/dist/src/__tests__/cache-extra.test.js +211 -0
- package/dist/src/__tests__/cli-init.test.d.ts +1 -0
- package/dist/src/__tests__/cli-init.test.js +97 -0
- package/dist/src/__tests__/cli-publish.test.d.ts +9 -0
- package/dist/src/__tests__/cli-publish.test.js +189 -0
- package/dist/src/__tests__/cli.test.js +76 -0
- package/dist/src/__tests__/compile-preprocess.test.d.ts +11 -0
- package/dist/src/__tests__/compile-preprocess.test.js +328 -0
- package/dist/src/__tests__/compiler/break-stmt.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/break-stmt.test.js +58 -0
- package/dist/src/__tests__/compiler/const-decl.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/const-decl.test.js +123 -0
- package/dist/src/__tests__/compiler/continue-stmt.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/continue-stmt.test.js +67 -0
- package/dist/src/__tests__/compiler/coroutine-extended.test.d.ts +17 -0
- package/dist/src/__tests__/compiler/coroutine-extended.test.js +565 -0
- package/dist/src/__tests__/compiler/deprecated.test.d.ts +4 -0
- package/dist/src/__tests__/compiler/deprecated.test.js +285 -0
- package/dist/src/__tests__/compiler/do-while.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/do-while.test.js +120 -0
- package/dist/src/__tests__/compiler/enum-payload.test.d.ts +9 -0
- package/dist/src/__tests__/compiler/enum-payload.test.js +272 -0
- package/dist/src/__tests__/compiler/interface.test.d.ts +10 -0
- package/dist/src/__tests__/compiler/interface.test.js +258 -0
- package/dist/src/__tests__/compiler/labeled-loops.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/labeled-loops.test.js +263 -0
- package/dist/src/__tests__/compiler/match-string.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/match-string.test.js +43 -0
- package/dist/src/__tests__/compiler/memoize.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/memoize.test.js +113 -0
- package/dist/src/__tests__/compiler/method-chain.test.d.ts +5 -0
- package/dist/src/__tests__/compiler/method-chain.test.js +115 -0
- package/dist/src/__tests__/compiler/module-import.test.d.ts +12 -0
- package/dist/src/__tests__/compiler/module-import.test.js +261 -0
- package/dist/src/__tests__/compiler/option-extensions.test.d.ts +6 -0
- package/dist/src/__tests__/compiler/option-extensions.test.js +191 -0
- package/dist/src/__tests__/compiler/profile-decorator.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/profile-decorator.test.js +69 -0
- package/dist/src/__tests__/compiler/string-advanced.test.d.ts +7 -0
- package/dist/src/__tests__/compiler/string-advanced.test.js +281 -0
- package/dist/src/__tests__/compiler/struct-extends.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/struct-extends.test.js +95 -0
- package/dist/src/__tests__/compiler/throttle-retry.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/throttle-retry.test.js +166 -0
- package/dist/src/__tests__/compiler/tuple-type.test.d.ts +10 -0
- package/dist/src/__tests__/compiler/tuple-type.test.js +229 -0
- package/dist/src/__tests__/compiler/watch-decorator.test.d.ts +1 -0
- package/dist/src/__tests__/compiler/watch-decorator.test.js +65 -0
- package/dist/src/__tests__/config/project-config.test.d.ts +1 -0
- package/dist/src/__tests__/config/project-config.test.js +199 -0
- package/dist/src/__tests__/config-decorator.test.d.ts +8 -0
- package/dist/src/__tests__/config-decorator.test.js +142 -0
- package/dist/src/__tests__/diagnostics-extra.test.d.ts +6 -0
- package/dist/src/__tests__/diagnostics-extra.test.js +132 -0
- package/dist/src/__tests__/emit/compile-branches.test.d.ts +1 -0
- package/dist/src/__tests__/emit/compile-branches.test.js +123 -0
- package/dist/src/__tests__/emit/compile-coverage.test.d.ts +25 -0
- package/dist/src/__tests__/emit/compile-coverage.test.js +617 -0
- package/dist/src/__tests__/emit/compile-extra-branches.test.d.ts +12 -0
- package/dist/src/__tests__/emit/compile-extra-branches.test.js +225 -0
- package/dist/src/__tests__/emit/compile-mocked-branches.test.d.ts +0 -0
- package/dist/src/__tests__/emit/compile-mocked-branches.test.js +238 -0
- package/dist/src/__tests__/emit/execute-chain.test.d.ts +10 -0
- package/dist/src/__tests__/emit/execute-chain.test.js +94 -0
- package/dist/src/__tests__/emit/index.test.js +2 -1
- package/dist/src/__tests__/emit/modules-branches.test.d.ts +1 -0
- package/dist/src/__tests__/emit/modules-branches.test.js +88 -0
- package/dist/src/__tests__/emit/modules-coverage.test.d.ts +15 -0
- package/dist/src/__tests__/emit/modules-coverage.test.js +221 -0
- package/dist/src/__tests__/emit/modules-errors.test.d.ts +12 -0
- package/dist/src/__tests__/emit/modules-errors.test.js +169 -0
- package/dist/src/__tests__/emit/modules-rewrite.test.d.ts +17 -0
- package/dist/src/__tests__/emit/modules-rewrite.test.js +204 -0
- package/dist/src/__tests__/emit/source-map.test.d.ts +1 -0
- package/dist/src/__tests__/emit/source-map.test.js +167 -0
- package/dist/src/__tests__/enum.test.js +9 -4
- package/dist/src/__tests__/error-recovery.test.d.ts +7 -0
- package/dist/src/__tests__/error-recovery.test.js +217 -0
- package/dist/src/__tests__/events-types-extra.test.d.ts +10 -0
- package/dist/src/__tests__/events-types-extra.test.js +91 -0
- package/dist/src/__tests__/events-types.test.d.ts +4 -0
- package/dist/src/__tests__/events-types.test.js +56 -0
- package/dist/src/__tests__/formatter.test.js +13 -5
- package/dist/src/__tests__/hir/lower-extra.test.d.ts +9 -0
- package/dist/src/__tests__/hir/lower-extra.test.js +140 -0
- package/dist/src/__tests__/hir/monomorphize-extra.test.d.ts +15 -0
- package/dist/src/__tests__/hir/monomorphize-extra.test.js +200 -0
- package/dist/src/__tests__/hir/monomorphize-extra2.test.d.ts +16 -0
- package/dist/src/__tests__/hir/monomorphize-extra2.test.js +316 -0
- package/dist/src/__tests__/incremental.test.js +10 -2
- package/dist/src/__tests__/index-extra.test.d.ts +10 -0
- package/dist/src/__tests__/index-extra.test.js +71 -0
- package/dist/src/__tests__/lexer.test.js +2 -2
- package/dist/src/__tests__/lint/rules.test.d.ts +5 -0
- package/dist/src/__tests__/lint/rules.test.js +208 -0
- package/dist/src/__tests__/lir/lower.test.js +29 -0
- package/dist/src/__tests__/lir/verify.test.js +30 -0
- package/dist/src/__tests__/lsp/completion.test.d.ts +7 -0
- package/dist/src/__tests__/lsp/completion.test.js +583 -0
- package/dist/src/__tests__/lsp/definition.test.d.ts +7 -0
- package/dist/src/__tests__/lsp/definition.test.js +454 -0
- package/dist/src/__tests__/lsp/diagnostics.test.d.ts +10 -0
- package/dist/src/__tests__/lsp/diagnostics.test.js +98 -0
- package/dist/src/__tests__/lsp/hover-docs.test.d.ts +10 -0
- package/dist/src/__tests__/lsp/hover-docs.test.js +210 -0
- package/dist/src/__tests__/lsp.test.js +4 -1
- package/dist/src/__tests__/mc-integration/item-entity-events.test.js +4 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +4 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.d.ts +13 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1227 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.d.ts +13 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1509 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.d.ts +14 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1374 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.d.ts +10 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +759 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.d.ts +13 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +855 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage.test.js +4 -0
- package/dist/src/__tests__/mc-integration/syntax-coverage.test.js +4 -0
- package/dist/src/__tests__/mc-validator-coverage.test.d.ts +13 -0
- package/dist/src/__tests__/mc-validator-coverage.test.js +296 -0
- package/dist/src/__tests__/mc-validator-extra.test.d.ts +13 -0
- package/dist/src/__tests__/mc-validator-extra.test.js +245 -0
- package/dist/src/__tests__/mir/lower-extra.test.d.ts +20 -0
- package/dist/src/__tests__/mir/lower-extra.test.js +361 -0
- package/dist/src/__tests__/mir/lower-extra2.test.d.ts +17 -0
- package/dist/src/__tests__/mir/lower-extra2.test.js +317 -0
- package/dist/src/__tests__/mir/lower-extra3.test.d.ts +19 -0
- package/dist/src/__tests__/mir/lower-extra3.test.js +249 -0
- package/dist/src/__tests__/mir/lower-extra4.test.d.ts +23 -0
- package/dist/src/__tests__/mir/lower-extra4.test.js +606 -0
- package/dist/src/__tests__/mir/lower-extra5.test.d.ts +25 -0
- package/dist/src/__tests__/mir/lower-extra5.test.js +543 -0
- package/dist/src/__tests__/mir/lower-extra6.test.d.ts +16 -0
- package/dist/src/__tests__/mir/lower-extra6.test.js +471 -0
- package/dist/src/__tests__/mir/lower-extra7.test.d.ts +35 -0
- package/dist/src/__tests__/mir/lower-extra7.test.js +921 -0
- package/dist/src/__tests__/mir/lower-extra8.test.d.ts +19 -0
- package/dist/src/__tests__/mir/lower-extra8.test.js +626 -0
- package/dist/src/__tests__/mir/lower-extra9.test.d.ts +14 -0
- package/dist/src/__tests__/mir/lower-extra9.test.js +717 -0
- package/dist/src/__tests__/optimizer/auto-inline.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/auto-inline.test.js +176 -0
- package/dist/src/__tests__/optimizer/cse.test.d.ts +4 -0
- package/dist/src/__tests__/optimizer/cse.test.js +178 -0
- package/dist/src/__tests__/optimizer/inline_fn.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/inline_fn.test.js +221 -0
- package/dist/src/__tests__/optimizer/licm.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/licm.test.js +244 -0
- package/dist/src/__tests__/optimizer/optimizer-extended.test.d.ts +12 -0
- package/dist/src/__tests__/optimizer/optimizer-extended.test.js +993 -0
- package/dist/src/__tests__/optimizer/strength-reduction.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer/strength-reduction.test.js +86 -0
- package/dist/src/__tests__/optimizer/tco.test.d.ts +14 -0
- package/dist/src/__tests__/optimizer/tco.test.js +203 -0
- package/dist/src/__tests__/parser-coverage.test.d.ts +25 -0
- package/dist/src/__tests__/parser-coverage.test.js +491 -0
- package/dist/src/__tests__/parser-extra.test.d.ts +6 -0
- package/dist/src/__tests__/parser-extra.test.js +451 -0
- package/dist/src/__tests__/parser.test.js +12 -0
- package/dist/src/__tests__/repl-extra.test.d.ts +13 -0
- package/dist/src/__tests__/repl-extra.test.js +174 -0
- package/dist/src/__tests__/repl-server-extra.test.d.ts +10 -0
- package/dist/src/__tests__/repl-server-extra.test.js +161 -0
- package/dist/src/__tests__/repl-server.test.d.ts +6 -0
- package/dist/src/__tests__/repl-server.test.js +146 -0
- package/dist/src/__tests__/runtime-extra.test.d.ts +15 -0
- package/dist/src/__tests__/runtime-extra.test.js +732 -0
- package/dist/src/__tests__/singleton-decorator.test.d.ts +11 -0
- package/dist/src/__tests__/singleton-decorator.test.js +260 -0
- package/dist/src/__tests__/sourcemap.test.js +1 -1
- package/dist/src/__tests__/stdlib/advanced.test.d.ts +5 -0
- package/dist/src/__tests__/stdlib/advanced.test.js +301 -0
- package/dist/src/__tests__/stdlib/bigint.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/bigint.test.js +83 -0
- package/dist/src/__tests__/stdlib/bits.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/bits.test.js +96 -0
- package/dist/src/__tests__/stdlib/bossbar.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/bossbar.test.js +72 -0
- package/dist/src/__tests__/stdlib/color.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/color.test.js +84 -0
- package/dist/src/__tests__/stdlib/combat.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/combat.test.js +64 -0
- package/dist/src/__tests__/stdlib/cooldown.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/cooldown.test.js +64 -0
- package/dist/src/__tests__/stdlib/dialog.test.js +15 -7
- package/dist/src/__tests__/stdlib/ecs.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/ecs.test.js +81 -0
- package/dist/src/__tests__/stdlib/effects.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/effects.test.js +72 -0
- package/dist/src/__tests__/stdlib/events.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/events.test.js +55 -0
- package/dist/src/__tests__/stdlib/expr.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/expr.test.js +77 -0
- package/dist/src/__tests__/stdlib/fft.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/fft.test.js +82 -0
- package/dist/src/__tests__/stdlib/graph.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/graph.test.js +102 -0
- package/dist/src/__tests__/stdlib/interactions.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/interactions.test.js +60 -0
- package/dist/src/__tests__/stdlib/inventory.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/inventory.test.js +68 -0
- package/dist/src/__tests__/stdlib/linalg.test.d.ts +5 -0
- package/dist/src/__tests__/stdlib/linalg.test.js +78 -0
- package/dist/src/__tests__/stdlib/map.test.d.ts +1 -0
- package/dist/src/__tests__/stdlib/map.test.js +84 -0
- package/dist/src/__tests__/stdlib/math.test.js +19 -6
- package/dist/src/__tests__/stdlib/math_hp.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/math_hp.test.js +80 -0
- package/dist/src/__tests__/stdlib/mobs.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/mobs.test.js +61 -0
- package/dist/src/__tests__/stdlib/noise.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/noise.test.js +73 -0
- package/dist/src/__tests__/stdlib/ode.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/ode.test.js +68 -0
- package/dist/src/__tests__/stdlib/parabola.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/parabola.test.js +77 -0
- package/dist/src/__tests__/stdlib/particles.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/particles.test.js +68 -0
- package/dist/src/__tests__/stdlib/physics.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/physics.test.js +76 -0
- package/dist/src/__tests__/stdlib/player.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/player.test.js +64 -0
- package/dist/src/__tests__/stdlib/quaternion.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/quaternion.test.js +73 -0
- package/dist/src/__tests__/stdlib/queue.test.d.ts +1 -0
- package/dist/src/__tests__/stdlib/queue.test.js +97 -0
- package/dist/src/__tests__/stdlib/random.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/random.test.js +76 -0
- package/dist/src/__tests__/stdlib/result.test.d.ts +12 -0
- package/dist/src/__tests__/stdlib/result.test.js +329 -0
- package/dist/src/__tests__/stdlib/scheduler.test.js +19 -8
- package/dist/src/__tests__/stdlib/set_int.test.d.ts +1 -0
- package/dist/src/__tests__/stdlib/set_int.test.js +88 -0
- package/dist/src/__tests__/stdlib/sets.test.d.ts +6 -0
- package/dist/src/__tests__/stdlib/sets.test.js +60 -0
- package/dist/src/__tests__/stdlib/signal.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/signal.test.js +84 -0
- package/dist/src/__tests__/stdlib/spawn.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/spawn.test.js +68 -0
- package/dist/src/__tests__/stdlib/string.test.d.ts +12 -0
- package/dist/src/__tests__/stdlib/string.test.js +231 -0
- package/dist/src/__tests__/stdlib/strings.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/strings.test.js +83 -0
- package/dist/src/__tests__/stdlib/tags.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/tags.test.js +57 -0
- package/dist/src/__tests__/stdlib/teams.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/teams.test.js +72 -0
- package/dist/src/__tests__/stdlib/timer.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/timer.test.js +79 -0
- package/dist/src/__tests__/stdlib/vec.test.d.ts +5 -0
- package/dist/src/__tests__/stdlib/vec.test.js +94 -0
- package/dist/src/__tests__/stdlib/world.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib/world.test.js +72 -0
- package/dist/src/__tests__/struct-display.test.d.ts +1 -0
- package/dist/src/__tests__/struct-display.test.js +64 -0
- package/dist/src/__tests__/test-framework/runner.test.d.ts +10 -0
- package/dist/src/__tests__/test-framework/runner.test.js +193 -0
- package/dist/src/__tests__/tuner/adapters.test.d.ts +14 -0
- package/dist/src/__tests__/tuner/adapters.test.js +194 -0
- package/dist/src/__tests__/tuner/simulator-extra.test.d.ts +4 -0
- package/dist/src/__tests__/tuner/simulator-extra.test.js +193 -0
- package/dist/src/__tests__/typechecker-coverage.test.d.ts +30 -0
- package/dist/src/__tests__/typechecker-coverage.test.js +627 -0
- package/dist/src/__tests__/typechecker.test.js +3 -3
- package/dist/src/__tests__/watch-decorator.test.d.ts +1 -0
- package/dist/src/__tests__/watch-decorator.test.js +54 -0
- package/dist/src/ast/types.d.ts +102 -3
- package/dist/src/cache/incremental.d.ts +13 -14
- package/dist/src/cache/incremental.js +106 -89
- package/dist/src/cache/index.d.ts +8 -2
- package/dist/src/cache/index.js +18 -6
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +466 -17
- package/dist/src/config/project-config.d.ts +29 -0
- package/dist/src/config/project-config.js +180 -0
- package/dist/src/diagnostics/index.d.ts +9 -0
- package/dist/src/diagnostics/index.js +18 -1
- package/dist/src/emit/compile.d.ts +10 -0
- package/dist/src/emit/compile.js +395 -50
- package/dist/src/emit/index.d.ts +40 -0
- package/dist/src/emit/index.js +307 -14
- package/dist/src/emit/modules.js +21 -3
- package/dist/src/emit/sourcemap.d.ts +23 -27
- package/dist/src/emit/sourcemap.js +52 -30
- package/dist/src/formatter/index.js +33 -8
- package/dist/src/hir/deprecated.d.ts +13 -0
- package/dist/src/hir/deprecated.js +218 -0
- package/dist/src/hir/lower.js +114 -8
- package/dist/src/hir/monomorphize.js +22 -2
- package/dist/src/hir/types.d.ts +65 -1
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +18 -3
- package/dist/src/lexer/index.d.ts +2 -1
- package/dist/src/lexer/index.js +39 -3
- package/dist/src/lint/index.d.ts +45 -0
- package/dist/src/lint/index.js +930 -0
- package/dist/src/lir/lower.js +29 -2
- package/dist/src/lir/types.d.ts +2 -0
- package/dist/src/lsp/server.js +92 -5
- package/dist/src/mir/lower.js +775 -34
- package/dist/src/mir/macro.js +36 -2
- package/dist/src/mir/types.d.ts +12 -0
- package/dist/src/mir/verify.js +9 -0
- package/dist/src/optimizer/auto-inline.d.ts +2 -0
- package/dist/src/optimizer/auto-inline.js +67 -0
- package/dist/src/optimizer/cse.d.ts +20 -0
- package/dist/src/optimizer/cse.js +234 -0
- package/dist/src/optimizer/inline.d.ts +26 -0
- package/dist/src/optimizer/inline.js +286 -0
- package/dist/src/optimizer/interprocedural.js +4 -0
- package/dist/src/optimizer/licm.d.ts +32 -0
- package/dist/src/optimizer/licm.js +371 -0
- package/dist/src/optimizer/pipeline.js +12 -2
- package/dist/src/optimizer/strength_reduction.d.ts +15 -0
- package/dist/src/optimizer/strength_reduction.js +90 -0
- package/dist/src/optimizer/tco.d.ts +53 -0
- package/dist/src/optimizer/tco.js +238 -0
- package/dist/src/parser/index.d.ts +32 -0
- package/dist/src/parser/index.js +421 -59
- package/dist/src/repl-server.d.ts +13 -0
- package/dist/src/repl-server.js +127 -0
- package/dist/src/structs/expand.d.ts +15 -0
- package/dist/src/structs/expand.js +46 -0
- package/dist/src/testing/runner.d.ts +40 -0
- package/dist/src/testing/runner.js +237 -0
- package/dist/src/typechecker/index.d.ts +3 -0
- package/dist/src/typechecker/index.js +254 -9
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/doc-drafts/redscript-docs/docs/en/stdlib/graph.md +104 -0
- package/doc-drafts/redscript-docs/docs/en/stdlib/parabola.md +113 -0
- package/doc-drafts/redscript-docs/docs/en/stdlib/pathfind.md +104 -0
- package/doc-drafts/redscript-docs/docs/en/stdlib/physics.md +134 -0
- package/doc-drafts/redscript-docs/docs/en/stdlib/quaternion.md +135 -0
- package/doc-drafts/redscript-docs/docs/zh/stdlib/graph.md +104 -0
- package/doc-drafts/redscript-docs/docs/zh/stdlib/parabola.md +113 -0
- package/doc-drafts/redscript-docs/docs/zh/stdlib/pathfind.md +104 -0
- package/doc-drafts/redscript-docs/docs/zh/stdlib/physics.md +134 -0
- package/doc-drafts/redscript-docs/docs/zh/stdlib/quaternion.md +135 -0
- package/docs/stdlib/result.md +156 -0
- package/docs/stdlib/result.zh.md +156 -0
- package/editors/vscode/fixtures/test.mcrs +7 -0
- package/editors/vscode/out/extension.js +2095 -225
- package/editors/vscode/out/lsp-server.js +519 -51
- package/editors/vscode/package-lock.json +9 -4
- package/editors/vscode/package.json +1 -1
- package/examples/display-demo.mcrs +64 -0
- package/examples/game/racing.mcrs +301 -0
- package/examples/game/tower_defense.mcrs +311 -0
- package/examples/math/physics_sim.mcrs +322 -0
- package/examples/rpg/boss_fight.mcrs +313 -0
- package/examples/rpg/health_system.mcrs +237 -0
- package/examples/rpg/inventory.mcrs +265 -0
- package/examples/util/debug_hud.mcrs +279 -0
- package/jest.config.js +10 -0
- package/package.json +12 -3
- package/playground/index.html +823 -0
- package/scripts/gen-docs.ts +533 -0
- package/scripts/update-redscript-docs-stdlib.sh +770 -0
- package/src/__tests__/budget.test.ts +5 -0
- package/src/__tests__/cache/cache-behavior.test.ts +480 -0
- package/src/__tests__/cache-extra.test.ts +199 -0
- package/src/__tests__/cli-docs.test.ts +77 -0
- package/src/__tests__/cli-init.test.ts +91 -0
- package/src/__tests__/cli-publish.test.ts +190 -0
- package/src/__tests__/cli.test.ts +117 -1
- package/src/__tests__/compile-preprocess.test.ts +366 -0
- package/src/__tests__/compiler/break-stmt.test.ts +66 -0
- package/src/__tests__/compiler/const-decl.test.ts +141 -0
- package/src/__tests__/compiler/continue-stmt.test.ts +81 -0
- package/src/__tests__/compiler/coroutine-extended.test.ts +723 -0
- package/src/__tests__/compiler/deprecated.test.ts +305 -0
- package/src/__tests__/compiler/do-while.test.ts +130 -0
- package/src/__tests__/compiler/enum-payload.test.ts +299 -0
- package/src/__tests__/compiler/interface.test.ts +287 -0
- package/src/__tests__/compiler/labeled-loops.test.ts +279 -0
- package/src/__tests__/compiler/match-string.test.ts +45 -0
- package/src/__tests__/compiler/memoize.test.ts +126 -0
- package/src/__tests__/compiler/method-chain.test.ts +121 -0
- package/src/__tests__/compiler/module-import.test.ts +240 -0
- package/src/__tests__/compiler/option-extensions.test.ts +207 -0
- package/src/__tests__/compiler/profile-decorator.test.ts +79 -0
- package/src/__tests__/compiler/string-advanced.test.ts +310 -0
- package/src/__tests__/compiler/struct-extends.test.ts +109 -0
- package/src/__tests__/compiler/throttle-retry.test.ts +191 -0
- package/src/__tests__/compiler/tuple-type.test.ts +263 -0
- package/src/__tests__/compiler/watch-decorator.test.ts +72 -0
- package/src/__tests__/config/project-config.test.ts +181 -0
- package/src/__tests__/config-decorator.test.ts +157 -0
- package/src/__tests__/diagnostics-extra.test.ts +155 -0
- package/src/__tests__/emit/compile-branches.test.ts +135 -0
- package/src/__tests__/emit/compile-coverage.test.ts +696 -0
- package/src/__tests__/emit/compile-extra-branches.test.ts +228 -0
- package/src/__tests__/emit/compile-mocked-branches.test.ts +249 -0
- package/src/__tests__/emit/compile.test.ts +6 -1
- package/src/__tests__/emit/execute-chain.test.ts +114 -0
- package/src/__tests__/emit/index.test.ts +2 -1
- package/src/__tests__/emit/modules-branches.test.ts +90 -0
- package/src/__tests__/emit/modules-coverage.test.ts +241 -0
- package/src/__tests__/emit/modules-errors.test.ts +192 -0
- package/src/__tests__/emit/modules-rewrite.test.ts +232 -0
- package/src/__tests__/emit/source-map.test.ts +152 -0
- package/src/__tests__/enum.test.ts +9 -4
- package/src/__tests__/error-recovery.test.ts +226 -0
- package/src/__tests__/events-types-extra.test.ts +110 -0
- package/src/__tests__/events-types.test.ts +66 -0
- package/src/__tests__/formatter.test.ts +15 -5
- package/src/__tests__/generics.test.ts +16 -9
- package/src/__tests__/hir/lower-extra.test.ts +151 -0
- package/src/__tests__/hir/monomorphize-coverage.test.ts +432 -0
- package/src/__tests__/hir/monomorphize-extra.test.ts +220 -0
- package/src/__tests__/hir/monomorphize-extra2.test.ts +350 -0
- package/src/__tests__/impl.test.ts +12 -8
- package/src/__tests__/incremental.test.ts +10 -2
- package/src/__tests__/index-extra.test.ts +79 -0
- package/src/__tests__/lexer.test.ts +2 -2
- package/src/__tests__/lint/hir-coverage.test.ts +1716 -0
- package/src/__tests__/lint/rules-coverage.test.ts +598 -0
- package/src/__tests__/lint/rules.test.ts +230 -0
- package/src/__tests__/lir/lower.test.ts +33 -0
- package/src/__tests__/lir/verify.test.ts +33 -0
- package/src/__tests__/lsp/completion.test.ts +687 -0
- package/src/__tests__/lsp/definition.test.ts +499 -0
- package/src/__tests__/lsp/diagnostics.test.ts +108 -0
- package/src/__tests__/lsp/hover-docs.test.ts +222 -0
- package/src/__tests__/lsp.test.ts +4 -1
- package/src/__tests__/mc-integration/item-entity-events.test.ts +5 -0
- package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +5 -0
- package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1105 -0
- package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1366 -0
- package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1245 -0
- package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +755 -0
- package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +771 -0
- package/src/__tests__/mc-integration/stdlib-coverage.test.ts +5 -0
- package/src/__tests__/mc-integration/syntax-coverage.test.ts +5 -0
- package/src/__tests__/mc-validator-coverage.test.ts +325 -0
- package/src/__tests__/mc-validator-extra.test.ts +252 -0
- package/src/__tests__/mir/lower-extra.test.ts +402 -0
- package/src/__tests__/mir/lower-extra2.test.ts +348 -0
- package/src/__tests__/mir/lower-extra3.test.ts +277 -0
- package/src/__tests__/mir/lower-extra4.test.ts +636 -0
- package/src/__tests__/mir/lower-extra5.test.ts +612 -0
- package/src/__tests__/mir/lower-extra6.test.ts +520 -0
- package/src/__tests__/mir/lower-extra7.test.ts +1045 -0
- package/src/__tests__/mir/lower-extra8.test.ts +704 -0
- package/src/__tests__/mir/lower-extra9.test.ts +821 -0
- package/src/__tests__/optimizer/auto-inline.test.ts +206 -0
- package/src/__tests__/optimizer/cse.test.ts +195 -0
- package/src/__tests__/optimizer/inline_fn.test.ts +263 -0
- package/src/__tests__/optimizer/licm.test.ts +358 -0
- package/src/__tests__/optimizer/nbt-coalesce.test.ts +147 -0
- package/src/__tests__/optimizer/optimizer-extended.test.ts +1081 -0
- package/src/__tests__/optimizer/scoreboard-batch.test.ts +141 -0
- package/src/__tests__/optimizer/strength-reduction.test.ts +111 -0
- package/src/__tests__/optimizer/tco-coverage.test.ts +309 -0
- package/src/__tests__/optimizer/tco.test.ts +238 -0
- package/src/__tests__/option.test.ts +14 -7
- package/src/__tests__/parser-coverage.test.ts +576 -0
- package/src/__tests__/parser-extra.test.ts +531 -0
- package/src/__tests__/parser.test.ts +14 -0
- package/src/__tests__/repl-extra.test.ts +195 -0
- package/src/__tests__/repl-server-extra.test.ts +150 -0
- package/src/__tests__/repl-server.test.ts +122 -0
- package/src/__tests__/runtime-extra.test.ts +862 -0
- package/src/__tests__/singleton-decorator.test.ts +285 -0
- package/src/__tests__/sourcemap.test.ts +1 -1
- package/src/__tests__/stdlib/advanced.test.ts +312 -0
- package/src/__tests__/stdlib/bigint.test.ts +57 -0
- package/src/__tests__/stdlib/bits.test.ts +75 -0
- package/src/__tests__/stdlib/bossbar.test.ts +45 -0
- package/src/__tests__/stdlib/color.test.ts +60 -0
- package/src/__tests__/stdlib/combat.test.ts +35 -0
- package/src/__tests__/stdlib/cooldown.test.ts +35 -0
- package/src/__tests__/stdlib/dialog.test.ts +14 -6
- package/src/__tests__/stdlib/ecs.test.ts +54 -0
- package/src/__tests__/stdlib/effects.test.ts +45 -0
- package/src/__tests__/stdlib/events.test.ts +23 -0
- package/src/__tests__/stdlib/expr.test.ts +48 -0
- package/src/__tests__/stdlib/fft.test.ts +54 -0
- package/src/__tests__/stdlib/graph.test.ts +77 -0
- package/src/__tests__/stdlib/interactions.test.ts +30 -0
- package/src/__tests__/stdlib/inventory.test.ts +40 -0
- package/src/__tests__/stdlib/linalg.test.ts +52 -0
- package/src/__tests__/stdlib/map.test.ts +55 -0
- package/src/__tests__/stdlib/math.test.ts +19 -5
- package/src/__tests__/stdlib/math_hp.test.ts +55 -0
- package/src/__tests__/stdlib/mobs.test.ts +40 -0
- package/src/__tests__/stdlib/noise.test.ts +46 -0
- package/src/__tests__/stdlib/ode.test.ts +40 -0
- package/src/__tests__/stdlib/parabola.test.ts +51 -0
- package/src/__tests__/stdlib/particles.test.ts +40 -0
- package/src/__tests__/stdlib/physics.test.ts +50 -0
- package/src/__tests__/stdlib/player.test.ts +35 -0
- package/src/__tests__/stdlib/quaternion.test.ts +46 -0
- package/src/__tests__/stdlib/queue.test.ts +73 -0
- package/src/__tests__/stdlib/random.test.ts +50 -0
- package/src/__tests__/stdlib/result.test.ts +326 -0
- package/src/__tests__/stdlib/scheduler.test.ts +18 -7
- package/src/__tests__/stdlib/set_int.test.ts +62 -0
- package/src/__tests__/stdlib/sets.test.ts +28 -0
- package/src/__tests__/stdlib/signal.test.ts +60 -0
- package/src/__tests__/stdlib/spawn.test.ts +40 -0
- package/src/__tests__/stdlib/string.test.ts +224 -0
- package/src/__tests__/stdlib/strings.test.ts +55 -0
- package/src/__tests__/stdlib/tags.test.ts +32 -0
- package/src/__tests__/stdlib/teams.test.ts +45 -0
- package/src/__tests__/stdlib/timer.test.ts +53 -0
- package/src/__tests__/stdlib/vec.test.ts +72 -0
- package/src/__tests__/stdlib/world.test.ts +45 -0
- package/src/__tests__/struct-display.test.ts +69 -0
- package/src/__tests__/test-framework/runner.test.ts +208 -0
- package/src/__tests__/tuner/adapters.test.ts +232 -0
- package/src/__tests__/tuner/simulator-extra.test.ts +222 -0
- package/src/__tests__/tuple.test.ts +11 -4
- package/src/__tests__/typechecker-coverage.test.ts +671 -0
- package/src/__tests__/typechecker.test.ts +4 -3
- package/src/__tests__/watch-decorator.test.ts +59 -0
- package/src/ast/types.ts +65 -3
- package/src/cache/incremental.ts +128 -99
- package/src/cache/index.ts +35 -8
- package/src/cli.ts +538 -29
- package/src/config/project-config.ts +176 -0
- package/src/diagnostics/index.ts +22 -0
- package/src/docs.ts +98 -0
- package/src/emit/compile.ts +408 -51
- package/src/emit/index.ts +366 -18
- package/src/emit/modules.ts +19 -3
- package/src/emit/sourcemap.ts +64 -43
- package/src/formatter/index.ts +35 -8
- package/src/hir/deprecated.ts +212 -0
- package/src/hir/lower.ts +128 -8
- package/src/hir/monomorphize.ts +24 -2
- package/src/hir/types.ts +26 -1
- package/src/index.ts +23 -3
- package/src/lexer/index.ts +45 -6
- package/src/lint/index.ts +922 -0
- package/src/lir/lower.ts +30 -2
- package/src/lir/types.ts +4 -0
- package/src/lsp/server.ts +100 -1
- package/src/mir/lower.ts +785 -40
- package/src/mir/macro.ts +30 -2
- package/src/mir/types.ts +13 -0
- package/src/mir/verify.ts +10 -2
- package/src/optimizer/auto-inline.ts +86 -0
- package/src/optimizer/copy_prop.ts +2 -2
- package/src/optimizer/coroutine.ts +3 -3
- package/src/optimizer/cse.ts +205 -0
- package/src/optimizer/dce.ts +2 -2
- package/src/optimizer/inline.ts +335 -0
- package/src/optimizer/interprocedural.ts +5 -1
- package/src/optimizer/licm.ts +454 -0
- package/src/optimizer/nbt-coalesce.ts +109 -0
- package/src/optimizer/pipeline.ts +16 -2
- package/src/optimizer/scoreboard-batch.ts +52 -0
- package/src/optimizer/strength_reduction.ts +95 -0
- package/src/optimizer/tco.ts +267 -0
- package/src/optimizer/unroll.ts +2 -2
- package/src/parser/index.ts +426 -53
- package/src/repl-server.ts +102 -0
- package/src/stdlib/advanced.mcrs +271 -101
- package/src/stdlib/bigint.mcrs +97 -11
- package/src/stdlib/bits.mcrs +75 -12
- package/src/stdlib/bossbar.mcrs +37 -8
- package/src/stdlib/calculus.mcrs +82 -26
- package/src/stdlib/color.mcrs +98 -16
- package/src/stdlib/combat.mcrs +23 -5
- package/src/stdlib/cooldown.mcrs +19 -0
- package/src/stdlib/dialog.mcrs +45 -7
- package/src/stdlib/easing.mcrs +132 -12
- package/src/stdlib/ecs.mcrs +142 -25
- package/src/stdlib/effects.mcrs +88 -12
- package/src/stdlib/events.mcrs +21 -2
- package/src/stdlib/expr.mcrs +18 -3
- package/src/stdlib/fft.mcrs +66 -56
- package/src/stdlib/geometry.mcrs +137 -39
- package/src/stdlib/graph.mcrs +73 -0
- package/src/stdlib/heap.mcrs +49 -8
- package/src/stdlib/i18n/zh.yaml +2891 -0
- package/src/stdlib/interactions.mcrs +43 -20
- package/src/stdlib/inventory.mcrs +14 -3
- package/src/stdlib/linalg.mcrs +185 -30
- package/src/stdlib/list.mcrs +168 -18
- package/src/stdlib/map.mcrs +112 -0
- package/src/stdlib/math.mcrs +68 -18
- package/src/stdlib/math_hp.mcrs +124 -33
- package/src/stdlib/matrix.mcrs +133 -20
- package/src/stdlib/mobs.mcrs +87 -0
- package/src/stdlib/noise.mcrs +65 -21
- package/src/stdlib/ode.mcrs +96 -0
- package/src/stdlib/parabola.mcrs +104 -29
- package/src/stdlib/particles.mcrs +78 -21
- package/src/stdlib/pathfind.mcrs +89 -35
- package/src/stdlib/physics.mcrs +134 -26
- package/src/stdlib/player.mcrs +18 -0
- package/src/stdlib/quaternion.mcrs +213 -9
- package/src/stdlib/queue.mcrs +123 -0
- package/src/stdlib/random.mcrs +63 -18
- package/src/stdlib/result.mcrs +111 -0
- package/src/stdlib/scheduler.mcrs +59 -10
- package/src/stdlib/set_int.mcrs +240 -0
- package/src/stdlib/sets.mcrs +49 -19
- package/src/stdlib/signal.mcrs +151 -79
- package/src/stdlib/sort.mcrs +44 -24
- package/src/stdlib/spawn.mcrs +30 -7
- package/src/stdlib/state.mcrs +40 -5
- package/src/stdlib/strings.mcrs +131 -3
- package/src/stdlib/tags.mcrs +2 -2
- package/src/stdlib/teams.mcrs +22 -10
- package/src/stdlib/timer.mcrs +36 -6
- package/src/stdlib/vec.mcrs +44 -9
- package/src/stdlib/world.mcrs +57 -25
- package/src/structs/expand.ts +64 -0
- package/src/testing/runner.ts +271 -0
- package/src/typechecker/index.ts +273 -9
|
@@ -0,0 +1,993 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Extended optimizer coverage tests.
|
|
4
|
+
*
|
|
5
|
+
* Targets uncovered branches in:
|
|
6
|
+
* - coroutine.ts (fnContainsMacroCalls paths, macro-skip warning)
|
|
7
|
+
* - unroll.ts (two-step latch pattern, initializesTo0 copy path, copy-init path)
|
|
8
|
+
* - interprocedural.ts (call_macro isSelfRecursive, param mismatch guard, rewriteCallSites guards)
|
|
9
|
+
* - lir/const_imm.ts (score_swap / store_nbt_to_score / macro_line read slots, extractSlotsFromRaw)
|
|
10
|
+
* - block_merge.ts (branch terminator — no merge, target === fn.entry guard)
|
|
11
|
+
* - constant_fold.ts (evalCmp default branch)
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Imports
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const coroutine_1 = require("../../optimizer/coroutine");
|
|
18
|
+
const unroll_1 = require("../../optimizer/unroll");
|
|
19
|
+
const interprocedural_1 = require("../../optimizer/interprocedural");
|
|
20
|
+
const const_imm_1 = require("../../optimizer/lir/const_imm");
|
|
21
|
+
const block_merge_1 = require("../../optimizer/block_merge");
|
|
22
|
+
const constant_fold_1 = require("../../optimizer/constant_fold");
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// MIR helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const c = (v) => ({ kind: 'const', value: v });
|
|
27
|
+
const t = (n) => ({ kind: 'temp', name: n });
|
|
28
|
+
function mirBlock(id, instrs, term, preds = []) {
|
|
29
|
+
return { id, instrs, term, preds };
|
|
30
|
+
}
|
|
31
|
+
function mirFn(name, params, blocks, isMacro = false) {
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
params: params.map(p => ({ name: p, isMacroParam: false })),
|
|
35
|
+
blocks,
|
|
36
|
+
entry: 'entry',
|
|
37
|
+
isMacro,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function mirMod(functions) {
|
|
41
|
+
return { functions, namespace: 'test', objective: '__test' };
|
|
42
|
+
}
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// LIR helpers
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
const obj = '__test';
|
|
47
|
+
function slot(player) { return { player, obj }; }
|
|
48
|
+
function lirFn(instructions) {
|
|
49
|
+
return { name: 'test', instructions, isMacro: false, macroParams: [] };
|
|
50
|
+
}
|
|
51
|
+
// ===========================================================================
|
|
52
|
+
// 1. coroutine.ts — uncovered branches
|
|
53
|
+
// ===========================================================================
|
|
54
|
+
describe('coroutine — fnContainsMacroCalls skip path', () => {
|
|
55
|
+
function mkBlock(id, instrs, term) {
|
|
56
|
+
return { id, instrs, term, preds: [] };
|
|
57
|
+
}
|
|
58
|
+
test('skips transform and emits warning when function has call_macro', () => {
|
|
59
|
+
const fn = {
|
|
60
|
+
name: 'test:worker',
|
|
61
|
+
params: [],
|
|
62
|
+
blocks: [
|
|
63
|
+
mkBlock('entry', [
|
|
64
|
+
// call_macro instruction — should trigger the skip path
|
|
65
|
+
{ kind: 'call_macro', dst: null, fn: 'test:mfn', args: [] },
|
|
66
|
+
], { kind: 'return', value: null }),
|
|
67
|
+
],
|
|
68
|
+
entry: 'entry',
|
|
69
|
+
isMacro: false,
|
|
70
|
+
};
|
|
71
|
+
const mod = mirMod([fn]);
|
|
72
|
+
const info = { fnName: 'test:worker', batch: 10 };
|
|
73
|
+
const result = (0, coroutine_1.coroutineTransform)(mod, [info]);
|
|
74
|
+
// Function should be kept unchanged (not transformed)
|
|
75
|
+
expect(result.module.functions).toHaveLength(1);
|
|
76
|
+
expect(result.module.functions[0].name).toBe('test:worker');
|
|
77
|
+
// No tick functions generated
|
|
78
|
+
expect(result.generatedTickFunctions).toHaveLength(0);
|
|
79
|
+
// Warning should be emitted
|
|
80
|
+
expect(result.warnings).toHaveLength(1);
|
|
81
|
+
expect(result.warnings[0]).toContain('test:worker');
|
|
82
|
+
expect(result.warnings[0]).toContain('@coroutine');
|
|
83
|
+
});
|
|
84
|
+
test('skips transform when function has __raw: call with ${interpolation}', () => {
|
|
85
|
+
const fn = {
|
|
86
|
+
name: 'test:interpolated',
|
|
87
|
+
params: [],
|
|
88
|
+
blocks: [
|
|
89
|
+
mkBlock('entry', [
|
|
90
|
+
// A raw call with variable interpolation
|
|
91
|
+
{ kind: 'call', dst: null, fn: '__raw:say ${name}', args: [] },
|
|
92
|
+
], { kind: 'return', value: null }),
|
|
93
|
+
],
|
|
94
|
+
entry: 'entry',
|
|
95
|
+
isMacro: false,
|
|
96
|
+
};
|
|
97
|
+
const mod = mirMod([fn]);
|
|
98
|
+
const info = { fnName: 'test:interpolated', batch: 10 };
|
|
99
|
+
const result = (0, coroutine_1.coroutineTransform)(mod, [info]);
|
|
100
|
+
expect(result.module.functions).toHaveLength(1);
|
|
101
|
+
expect(result.generatedTickFunctions).toHaveLength(0);
|
|
102
|
+
expect(result.warnings).toHaveLength(1);
|
|
103
|
+
});
|
|
104
|
+
test('skips transform when function has __raw:\\x01 (builtin with macro)', () => {
|
|
105
|
+
const fn = {
|
|
106
|
+
name: 'test:builtin_macro',
|
|
107
|
+
params: [],
|
|
108
|
+
blocks: [
|
|
109
|
+
mkBlock('entry', [
|
|
110
|
+
{ kind: 'call', dst: null, fn: '__raw:\x01particle dust', args: [] },
|
|
111
|
+
], { kind: 'return', value: null }),
|
|
112
|
+
],
|
|
113
|
+
entry: 'entry',
|
|
114
|
+
isMacro: false,
|
|
115
|
+
};
|
|
116
|
+
const mod = mirMod([fn]);
|
|
117
|
+
const info = { fnName: 'test:builtin_macro', batch: 5 };
|
|
118
|
+
const result = (0, coroutine_1.coroutineTransform)(mod, [info]);
|
|
119
|
+
expect(result.generatedTickFunctions).toHaveLength(0);
|
|
120
|
+
expect(result.warnings).toHaveLength(1);
|
|
121
|
+
});
|
|
122
|
+
test('transforms when __raw: call has no interpolation', () => {
|
|
123
|
+
// __raw: without ${ or \x01 should NOT block the transform
|
|
124
|
+
const fn = {
|
|
125
|
+
name: 'test:plain_raw',
|
|
126
|
+
params: [],
|
|
127
|
+
blocks: [
|
|
128
|
+
mkBlock('entry', [
|
|
129
|
+
{ kind: 'call', dst: null, fn: '__raw:say hello', args: [] },
|
|
130
|
+
], { kind: 'return', value: null }),
|
|
131
|
+
],
|
|
132
|
+
entry: 'entry',
|
|
133
|
+
isMacro: false,
|
|
134
|
+
};
|
|
135
|
+
const mod = mirMod([fn]);
|
|
136
|
+
const info = { fnName: 'test:plain_raw', batch: 5 };
|
|
137
|
+
const result = (0, coroutine_1.coroutineTransform)(mod, [info]);
|
|
138
|
+
// Should be transformed (no warning)
|
|
139
|
+
expect(result.warnings).toHaveLength(0);
|
|
140
|
+
expect(result.generatedTickFunctions).toHaveLength(1);
|
|
141
|
+
});
|
|
142
|
+
test('call_macro in terminator position also triggers skip', () => {
|
|
143
|
+
// The loop in fnContainsMacroCalls iterates [...block.instrs, block.term]
|
|
144
|
+
// so a call_macro as the terminator should also trigger the skip
|
|
145
|
+
const fn = {
|
|
146
|
+
name: 'test:macro_term',
|
|
147
|
+
params: [],
|
|
148
|
+
blocks: [
|
|
149
|
+
{
|
|
150
|
+
id: 'entry',
|
|
151
|
+
instrs: [],
|
|
152
|
+
// call_macro as terminator
|
|
153
|
+
term: { kind: 'call_macro', dst: null, fn: 'test:m', args: [] },
|
|
154
|
+
preds: [],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
entry: 'entry',
|
|
158
|
+
isMacro: false,
|
|
159
|
+
};
|
|
160
|
+
const mod = mirMod([fn]);
|
|
161
|
+
const info = { fnName: 'test:macro_term', batch: 5 };
|
|
162
|
+
const result = (0, coroutine_1.coroutineTransform)(mod, [info]);
|
|
163
|
+
expect(result.warnings).toHaveLength(1);
|
|
164
|
+
expect(result.generatedTickFunctions).toHaveLength(0);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
// ===========================================================================
|
|
168
|
+
// 2. unroll.ts — uncovered branches
|
|
169
|
+
// ===========================================================================
|
|
170
|
+
describe('loopUnroll — uncovered branches', () => {
|
|
171
|
+
function buildLoopWithBlocks(blocks) {
|
|
172
|
+
return { name: 'test', params: [], blocks, entry: 'entry', isMacro: false };
|
|
173
|
+
}
|
|
174
|
+
test('two-step latch pattern: add t_tmp i 1; copy i t_tmp', () => {
|
|
175
|
+
// Pattern 2 in latchIncrementsBy1
|
|
176
|
+
const fn = buildLoopWithBlocks([
|
|
177
|
+
mirBlock('entry', [
|
|
178
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
179
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
180
|
+
mirBlock('loop_header_0', [
|
|
181
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
|
|
182
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
183
|
+
mirBlock('loop_body_0', [
|
|
184
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
|
|
185
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
186
|
+
mirBlock('loop_latch_0', [
|
|
187
|
+
// Two-step increment: t_tmp = i + 1; i = t_tmp
|
|
188
|
+
{ kind: 'add', dst: 't_tmp', a: t('i'), b: c(1) },
|
|
189
|
+
{ kind: 'copy', dst: 'i', src: t('t_tmp') },
|
|
190
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
191
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
192
|
+
]);
|
|
193
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
194
|
+
// Should have unrolled — loop_header gone
|
|
195
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
196
|
+
const entry = result.blocks.find(b => b.id === 'entry');
|
|
197
|
+
const calls = entry.instrs.filter(i => i.kind === 'call');
|
|
198
|
+
expect(calls).toHaveLength(3);
|
|
199
|
+
// i substituted as 0, 1, 2
|
|
200
|
+
const callInstrs = calls;
|
|
201
|
+
expect(callInstrs[0].args[0]).toEqual(c(0));
|
|
202
|
+
expect(callInstrs[1].args[0]).toEqual(c(1));
|
|
203
|
+
expect(callInstrs[2].args[0]).toEqual(c(2));
|
|
204
|
+
});
|
|
205
|
+
test('initializesTo0 via copy instruction (copy i, const 0)', () => {
|
|
206
|
+
// Tests the `copy` branch in initializesTo0
|
|
207
|
+
const fn = buildLoopWithBlocks([
|
|
208
|
+
mirBlock('entry', [
|
|
209
|
+
// Initialize via copy of const 0 instead of direct const
|
|
210
|
+
{ kind: 'copy', dst: 'i', src: c(0) },
|
|
211
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
212
|
+
mirBlock('loop_header_0', [
|
|
213
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
|
|
214
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
215
|
+
mirBlock('loop_body_0', [
|
|
216
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
|
|
217
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
218
|
+
mirBlock('loop_latch_0', [
|
|
219
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
220
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
221
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
222
|
+
]);
|
|
223
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
224
|
+
// Should unroll since copy i, 0 is recognized as init-to-0
|
|
225
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
226
|
+
const entry = result.blocks.find(b => b.id === 'entry');
|
|
227
|
+
const calls = entry.instrs.filter(i => i.kind === 'call');
|
|
228
|
+
expect(calls).toHaveLength(2);
|
|
229
|
+
});
|
|
230
|
+
test('initializesTo0 copy with non-zero const does not unroll', () => {
|
|
231
|
+
// copy i, const 5 — not init to 0
|
|
232
|
+
const fn = buildLoopWithBlocks([
|
|
233
|
+
mirBlock('entry', [
|
|
234
|
+
{ kind: 'copy', dst: 'i', src: c(5) },
|
|
235
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
236
|
+
mirBlock('loop_header_0', [
|
|
237
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(8) },
|
|
238
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
239
|
+
mirBlock('loop_body_0', [
|
|
240
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
|
|
241
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
242
|
+
mirBlock('loop_latch_0', [
|
|
243
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
244
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
245
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
246
|
+
]);
|
|
247
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
248
|
+
expect(result).toBe(fn); // unchanged
|
|
249
|
+
});
|
|
250
|
+
test('latch with wrong temp in two-step does not unroll', () => {
|
|
251
|
+
// Pattern 2 but copy is from a different temp than the add destination
|
|
252
|
+
const fn = buildLoopWithBlocks([
|
|
253
|
+
mirBlock('entry', [
|
|
254
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
255
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
256
|
+
mirBlock('loop_header_0', [
|
|
257
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
|
|
258
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
259
|
+
mirBlock('loop_body_0', [
|
|
260
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
|
|
261
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
262
|
+
mirBlock('loop_latch_0', [
|
|
263
|
+
// Two-step but the copy sources from a different unrelated temp
|
|
264
|
+
{ kind: 'add', dst: 't_tmp', a: t('i'), b: c(1) },
|
|
265
|
+
{ kind: 'copy', dst: 'i', src: t('other_tmp') }, // wrong source
|
|
266
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
267
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
268
|
+
]);
|
|
269
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
270
|
+
// Should NOT unroll — two-step mismatch
|
|
271
|
+
expect(result).toBe(fn);
|
|
272
|
+
});
|
|
273
|
+
test('multiple loops in function: unrolls both iteratively', () => {
|
|
274
|
+
// Two sequential loops, both N=2 — fixpoint iteration handles both
|
|
275
|
+
const fn = buildLoopWithBlocks([
|
|
276
|
+
mirBlock('entry', [
|
|
277
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
278
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
279
|
+
mirBlock('loop_header_0', [
|
|
280
|
+
{ kind: 'cmp', dst: 't0', op: 'lt', a: t('i'), b: c(2) },
|
|
281
|
+
], { kind: 'branch', cond: t('t0'), then: 'loop_body_0', else: 'mid' }),
|
|
282
|
+
mirBlock('loop_body_0', [
|
|
283
|
+
{ kind: 'call', dst: null, fn: 'test:a', args: [t('i')] },
|
|
284
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
285
|
+
mirBlock('loop_latch_0', [
|
|
286
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
287
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
288
|
+
mirBlock('mid', [
|
|
289
|
+
{ kind: 'const', dst: 'j', value: 0 },
|
|
290
|
+
], { kind: 'jump', target: 'loop_header_1' }),
|
|
291
|
+
mirBlock('loop_header_1', [
|
|
292
|
+
{ kind: 'cmp', dst: 't1', op: 'lt', a: t('j'), b: c(2) },
|
|
293
|
+
], { kind: 'branch', cond: t('t1'), then: 'loop_body_1', else: 'loop_exit_1' }),
|
|
294
|
+
mirBlock('loop_body_1', [
|
|
295
|
+
{ kind: 'call', dst: null, fn: 'test:b', args: [t('j')] },
|
|
296
|
+
], { kind: 'jump', target: 'loop_latch_1' }),
|
|
297
|
+
mirBlock('loop_latch_1', [
|
|
298
|
+
{ kind: 'add', dst: 'j', a: t('j'), b: c(1) },
|
|
299
|
+
], { kind: 'jump', target: 'loop_header_1' }),
|
|
300
|
+
mirBlock('loop_exit_1', [], { kind: 'return', value: null }),
|
|
301
|
+
]);
|
|
302
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
303
|
+
// Both loops should be unrolled
|
|
304
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
// ===========================================================================
|
|
308
|
+
// 3. interprocedural.ts — uncovered branches
|
|
309
|
+
// ===========================================================================
|
|
310
|
+
describe('interproceduralConstProp — uncovered branches', () => {
|
|
311
|
+
test('isSelfRecursive: function calling itself via call_macro is not specialized', () => {
|
|
312
|
+
// A function that is self-recursive via call_macro should be skipped
|
|
313
|
+
const selfRecursiveFn = mirFn('test:recur', ['n'], [
|
|
314
|
+
mirBlock('entry', [
|
|
315
|
+
// call_macro to itself
|
|
316
|
+
{ kind: 'call_macro', dst: null, fn: 'test:recur', args: [] },
|
|
317
|
+
], { kind: 'return', value: null }),
|
|
318
|
+
]);
|
|
319
|
+
const callerFn = mirFn('test:main', [], [
|
|
320
|
+
mirBlock('entry', [
|
|
321
|
+
{ kind: 'call', dst: null, fn: 'test:recur', args: [c(5)] },
|
|
322
|
+
], { kind: 'return', value: null }),
|
|
323
|
+
]);
|
|
324
|
+
const mod = mirMod([callerFn, selfRecursiveFn]);
|
|
325
|
+
const result = (0, interprocedural_1.interproceduralConstProp)(mod);
|
|
326
|
+
// Should NOT create specialized version — self-recursive via call_macro
|
|
327
|
+
expect(result.functions.some(f => f.name.includes('__const_'))).toBe(false);
|
|
328
|
+
});
|
|
329
|
+
test('param count mismatch: callee.params.length !== instr.args.length skips', () => {
|
|
330
|
+
// Callee expects 2 params but call site provides 1 — should not specialize
|
|
331
|
+
const addFn = mirFn('test:add', ['a', 'b'], [
|
|
332
|
+
mirBlock('entry', [
|
|
333
|
+
{ kind: 'add', dst: 'r', a: t('a'), b: t('b') },
|
|
334
|
+
], { kind: 'return', value: t('r') }),
|
|
335
|
+
]);
|
|
336
|
+
const callerFn = mirFn('test:main', [], [
|
|
337
|
+
mirBlock('entry', [
|
|
338
|
+
// Only 1 arg but callee expects 2
|
|
339
|
+
{ kind: 'call', dst: null, fn: 'test:add', args: [c(3)] },
|
|
340
|
+
], { kind: 'return', value: null }),
|
|
341
|
+
]);
|
|
342
|
+
const mod = mirMod([callerFn, addFn]);
|
|
343
|
+
const result = (0, interprocedural_1.interproceduralConstProp)(mod);
|
|
344
|
+
expect(result.functions.some(f => f.name.includes('__const_'))).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
test('rewriteCallSites skips call_macro instructions (not rewritten)', () => {
|
|
347
|
+
// rewriteCallSites only rewrites 'call' not 'call_macro'
|
|
348
|
+
const helperFn = mirFn('test:helper', ['x'], [
|
|
349
|
+
mirBlock('entry', [], { kind: 'return', value: null }),
|
|
350
|
+
]);
|
|
351
|
+
const callerFn = mirFn('test:main', [], [
|
|
352
|
+
mirBlock('entry', [
|
|
353
|
+
// call_macro — should not be rewritten even if specialized fn exists
|
|
354
|
+
{ kind: 'call_macro', dst: null, fn: 'test:helper', args: [] },
|
|
355
|
+
], { kind: 'return', value: null }),
|
|
356
|
+
]);
|
|
357
|
+
const mod = mirMod([callerFn, helperFn]);
|
|
358
|
+
const result = (0, interprocedural_1.interproceduralConstProp)(mod);
|
|
359
|
+
const main = result.functions.find(f => f.name === 'test:main');
|
|
360
|
+
const macroCall = main.blocks[0].instrs.find(i => i.kind === 'call_macro');
|
|
361
|
+
// Should remain unchanged
|
|
362
|
+
expect(macroCall).toBeDefined();
|
|
363
|
+
});
|
|
364
|
+
test('rewriteCallSites: call with self-same name is not rewritten', () => {
|
|
365
|
+
// When callee.name === fn.name (self-call) in rewriteCallSites, skip
|
|
366
|
+
const selfCallFn = mirFn('test:fib', ['n'], [
|
|
367
|
+
mirBlock('entry', [
|
|
368
|
+
// direct self-call with constant arg — rewriteCallSites should skip
|
|
369
|
+
{ kind: 'call', dst: null, fn: 'test:fib', args: [c(5)] },
|
|
370
|
+
], { kind: 'return', value: null }),
|
|
371
|
+
]);
|
|
372
|
+
const mod = mirMod([selfCallFn]);
|
|
373
|
+
const result = (0, interprocedural_1.interproceduralConstProp)(mod);
|
|
374
|
+
const fib = result.functions.find(f => f.name === 'test:fib');
|
|
375
|
+
const call = fib.blocks[0].instrs.find(i => i.kind === 'call');
|
|
376
|
+
// Self call should remain unchanged
|
|
377
|
+
expect(call.fn).toBe('test:fib');
|
|
378
|
+
});
|
|
379
|
+
test('rewriteCallSites: call with non-all-const args is not rewritten', () => {
|
|
380
|
+
const addFn = mirFn('test:add', ['a', 'b'], [
|
|
381
|
+
mirBlock('entry', [
|
|
382
|
+
{ kind: 'add', dst: 'r', a: t('a'), b: t('b') },
|
|
383
|
+
], { kind: 'return', value: t('r') }),
|
|
384
|
+
]);
|
|
385
|
+
const callerFn = mirFn('test:main', [], [
|
|
386
|
+
mirBlock('entry', [
|
|
387
|
+
// Mixed args — not all const
|
|
388
|
+
{ kind: 'call', dst: null, fn: 'test:add', args: [t('x'), c(4)] },
|
|
389
|
+
], { kind: 'return', value: null }),
|
|
390
|
+
]);
|
|
391
|
+
const mod = mirMod([callerFn, addFn]);
|
|
392
|
+
const result = (0, interprocedural_1.interproceduralConstProp)(mod);
|
|
393
|
+
const main = result.functions.find(f => f.name === 'test:main');
|
|
394
|
+
const call = main.blocks[0].instrs.find(i => i.kind === 'call');
|
|
395
|
+
// Should remain as 'test:add' (not rewritten)
|
|
396
|
+
expect(call.fn).toBe('test:add');
|
|
397
|
+
});
|
|
398
|
+
test('callee with multiple blocks is not specialized', () => {
|
|
399
|
+
// Only single-block callees are specialized
|
|
400
|
+
const multiFn = mirFn('test:multi', ['x'], [
|
|
401
|
+
mirBlock('entry', [
|
|
402
|
+
{ kind: 'cmp', dst: 'cond', op: 'gt', a: t('x'), b: c(0) },
|
|
403
|
+
], { kind: 'branch', cond: t('cond'), then: 'pos', else: 'neg' }),
|
|
404
|
+
mirBlock('pos', [], { kind: 'return', value: c(1) }),
|
|
405
|
+
mirBlock('neg', [], { kind: 'return', value: c(0) }),
|
|
406
|
+
]);
|
|
407
|
+
const callerFn = mirFn('test:main', [], [
|
|
408
|
+
mirBlock('entry', [
|
|
409
|
+
{ kind: 'call', dst: null, fn: 'test:multi', args: [c(5)] },
|
|
410
|
+
], { kind: 'return', value: null }),
|
|
411
|
+
]);
|
|
412
|
+
const mod = mirMod([callerFn, multiFn]);
|
|
413
|
+
const result = (0, interprocedural_1.interproceduralConstProp)(mod);
|
|
414
|
+
expect(result.functions.some(f => f.name.includes('__const_'))).toBe(false);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
// ===========================================================================
|
|
418
|
+
// 4. lir/const_imm.ts — uncovered branches
|
|
419
|
+
// ===========================================================================
|
|
420
|
+
describe('constImmFold — getReadSlots uncovered branches', () => {
|
|
421
|
+
test('score_swap: both slots a and b are read slots', () => {
|
|
422
|
+
// score_swap uses both a and b — getReadSlots returns [a, b]
|
|
423
|
+
// If one of them is the $__const_ slot, use count is > 1 → no fold
|
|
424
|
+
const constSlot = slot('$__const_5');
|
|
425
|
+
const fn = lirFn([
|
|
426
|
+
{ kind: 'score_set', dst: constSlot, value: 5 },
|
|
427
|
+
{ kind: 'score_swap', a: constSlot, b: slot('$x') },
|
|
428
|
+
{ kind: 'score_add', dst: slot('$y'), src: constSlot },
|
|
429
|
+
]);
|
|
430
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
431
|
+
// $__const_5 is used twice (swap + add) → no fold
|
|
432
|
+
expect(result.instructions).toHaveLength(3);
|
|
433
|
+
expect(result).toBe(fn);
|
|
434
|
+
});
|
|
435
|
+
test('store_nbt_to_score: does not count as reading a slot', () => {
|
|
436
|
+
// store_nbt_to_score reads from NBT, not a score slot — getReadSlots returns []
|
|
437
|
+
// So $__const_5 with only score_add as user → folds
|
|
438
|
+
const constSlot = slot('$__const_5');
|
|
439
|
+
const fn = lirFn([
|
|
440
|
+
{ kind: 'score_set', dst: constSlot, value: 5 },
|
|
441
|
+
// store_nbt_to_score does NOT read constSlot
|
|
442
|
+
{ kind: 'store_nbt_to_score', dst: slot('$z'), ns: 'test', path: 'data', scale: 1 },
|
|
443
|
+
{ kind: 'score_add', dst: slot('$x'), src: constSlot },
|
|
444
|
+
]);
|
|
445
|
+
// Not adjacent (store_nbt_to_score is between) — no fold
|
|
446
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
447
|
+
expect(result.instructions).toHaveLength(3);
|
|
448
|
+
});
|
|
449
|
+
test('macro_line: slots referenced in template are counted', () => {
|
|
450
|
+
// macro_line uses extractSlotsFromRaw on the template field
|
|
451
|
+
const constSlot = slot('$__const_5');
|
|
452
|
+
const fn = lirFn([
|
|
453
|
+
{ kind: 'score_set', dst: constSlot, value: 5 },
|
|
454
|
+
// macro_line that references constSlot in template
|
|
455
|
+
{ kind: 'macro_line', template: `$__const_5 ${obj}` },
|
|
456
|
+
{ kind: 'score_add', dst: slot('$x'), src: constSlot },
|
|
457
|
+
]);
|
|
458
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
459
|
+
// $__const_5 is used in macro_line template + score_add → 2 uses → no fold
|
|
460
|
+
expect(result).toBe(fn);
|
|
461
|
+
expect(result.instructions).toHaveLength(3);
|
|
462
|
+
});
|
|
463
|
+
test('extractSlotsFromRaw: raw cmd with player-obj pattern extracts slot', () => {
|
|
464
|
+
// raw instruction — slots extracted via regex /(\$[\w.:]+)\s+(\S+)/g
|
|
465
|
+
const constSlot = slot('$__const_7');
|
|
466
|
+
const fn = lirFn([
|
|
467
|
+
{ kind: 'score_set', dst: constSlot, value: 7 },
|
|
468
|
+
// raw command that references constSlot
|
|
469
|
+
{ kind: 'raw', cmd: `scoreboard players get $__const_7 ${obj}` },
|
|
470
|
+
{ kind: 'score_add', dst: slot('$a'), src: constSlot },
|
|
471
|
+
]);
|
|
472
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
473
|
+
// constSlot used in raw + score_add → 2 uses → no fold
|
|
474
|
+
expect(result).toBe(fn);
|
|
475
|
+
});
|
|
476
|
+
test('raw cmd with no matching slots: slot is not counted', () => {
|
|
477
|
+
// A raw cmd with no $-prefixed player references
|
|
478
|
+
const constSlot = slot('$__const_3');
|
|
479
|
+
const fn = lirFn([
|
|
480
|
+
{ kind: 'score_set', dst: constSlot, value: 3 },
|
|
481
|
+
{ kind: 'raw', cmd: 'say hello world' }, // no slot references
|
|
482
|
+
{ kind: 'score_add', dst: slot('$x'), src: constSlot },
|
|
483
|
+
]);
|
|
484
|
+
// constSlot used only once (score_add), but not adjacent to score_set
|
|
485
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
486
|
+
expect(result.instructions).toHaveLength(3); // not adjacent → no fold
|
|
487
|
+
});
|
|
488
|
+
test('call_if_score: both a and b slots are counted as reads', () => {
|
|
489
|
+
// call_if_score reads both a and b — if constSlot appears as either, use count++
|
|
490
|
+
const constSlot = slot('$__const_4');
|
|
491
|
+
const fn = lirFn([
|
|
492
|
+
{ kind: 'score_set', dst: constSlot, value: 4 },
|
|
493
|
+
{ kind: 'call_if_score', fn: 'test:f', a: constSlot, op: 'gt', b: slot('$x') },
|
|
494
|
+
{ kind: 'score_add', dst: slot('$y'), src: constSlot },
|
|
495
|
+
]);
|
|
496
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
497
|
+
// constSlot used in call_if_score (a) + score_add → 2 uses → no fold
|
|
498
|
+
expect(result).toBe(fn);
|
|
499
|
+
});
|
|
500
|
+
test('return_value: slot is counted as a read', () => {
|
|
501
|
+
// return_value reads the slot — if constSlot is returned, use count++
|
|
502
|
+
const constSlot = slot('$__const_9');
|
|
503
|
+
const fn = lirFn([
|
|
504
|
+
{ kind: 'score_set', dst: constSlot, value: 9 },
|
|
505
|
+
{ kind: 'score_add', dst: slot('$x'), src: constSlot },
|
|
506
|
+
{ kind: 'return_value', slot: constSlot },
|
|
507
|
+
]);
|
|
508
|
+
// Two uses of constSlot (score_add + return_value) — no fold for the score_add pair
|
|
509
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
510
|
+
expect(result).toBe(fn);
|
|
511
|
+
});
|
|
512
|
+
test('instructions length < 2: returns fn unchanged', () => {
|
|
513
|
+
const fn = lirFn([
|
|
514
|
+
{ kind: 'score_set', dst: slot('$x'), value: 1 },
|
|
515
|
+
]);
|
|
516
|
+
const result = (0, const_imm_1.constImmFold)(fn);
|
|
517
|
+
expect(result).toBe(fn);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
// ===========================================================================
|
|
521
|
+
// 5. block_merge.ts — uncovered branch
|
|
522
|
+
// ===========================================================================
|
|
523
|
+
describe('blockMerge — uncovered branches', () => {
|
|
524
|
+
test('branch terminator: block with branch does not trigger merge', () => {
|
|
525
|
+
// The merge condition requires term.kind === 'jump'; a branch should not merge
|
|
526
|
+
const fn = {
|
|
527
|
+
name: 'test',
|
|
528
|
+
params: [],
|
|
529
|
+
blocks: [
|
|
530
|
+
mirBlock('entry', [], {
|
|
531
|
+
kind: 'branch', cond: t('c'), then: 'b1', else: 'b2',
|
|
532
|
+
}),
|
|
533
|
+
mirBlock('b1', [], { kind: 'return', value: null }, ['entry']),
|
|
534
|
+
mirBlock('b2', [], { kind: 'return', value: null }, ['entry']),
|
|
535
|
+
],
|
|
536
|
+
entry: 'entry',
|
|
537
|
+
isMacro: false,
|
|
538
|
+
};
|
|
539
|
+
const result = (0, block_merge_1.blockMerge)(fn);
|
|
540
|
+
// Should have all 3 blocks — no merge possible
|
|
541
|
+
expect(result.blocks).toHaveLength(3);
|
|
542
|
+
});
|
|
543
|
+
test('jump to entry block: not merged (entry guard)', () => {
|
|
544
|
+
// A block jumping to 'entry' should not cause entry to be merged away
|
|
545
|
+
// (the `targetId !== fn.entry` guard)
|
|
546
|
+
const fn = {
|
|
547
|
+
name: 'test',
|
|
548
|
+
params: [],
|
|
549
|
+
blocks: [
|
|
550
|
+
mirBlock('entry', [
|
|
551
|
+
{ kind: 'const', dst: 'x', value: 1 },
|
|
552
|
+
], { kind: 'return', value: t('x') }),
|
|
553
|
+
// A block that jumps to entry (unusual but valid for testing)
|
|
554
|
+
mirBlock('before', [], { kind: 'jump', target: 'entry' }),
|
|
555
|
+
],
|
|
556
|
+
entry: 'before',
|
|
557
|
+
isMacro: false,
|
|
558
|
+
};
|
|
559
|
+
const result = (0, block_merge_1.blockMerge)(fn);
|
|
560
|
+
// 'entry' has 1 pred (before) but is the fn.entry — guard prevents merge?
|
|
561
|
+
// Actually fn.entry = 'before', so 'entry' is NOT fn.entry here — it should merge
|
|
562
|
+
// Let's verify: before → entry (single pred), entry is not fn.entry ('before' is)
|
|
563
|
+
// So entry gets merged into before
|
|
564
|
+
expect(result.blocks.some(b => b.id === 'before')).toBe(true);
|
|
565
|
+
});
|
|
566
|
+
test('target not in blockMap: merge is skipped gracefully', () => {
|
|
567
|
+
// If target block doesn't exist in blockMap, blockMap.get returns undefined
|
|
568
|
+
// The `if (target && ...)` guard handles this
|
|
569
|
+
const fn = {
|
|
570
|
+
name: 'test',
|
|
571
|
+
params: [],
|
|
572
|
+
blocks: [
|
|
573
|
+
mirBlock('entry', [], { kind: 'jump', target: 'nonexistent' }),
|
|
574
|
+
],
|
|
575
|
+
entry: 'entry',
|
|
576
|
+
isMacro: false,
|
|
577
|
+
};
|
|
578
|
+
// Should not throw — target not found means no merge
|
|
579
|
+
const result = (0, block_merge_1.blockMerge)(fn);
|
|
580
|
+
expect(result.blocks).toHaveLength(1);
|
|
581
|
+
});
|
|
582
|
+
test('already-removed block is skipped in iteration', () => {
|
|
583
|
+
// When A merges B, B is added to `removed`. If blocks array processes B again, it skips.
|
|
584
|
+
// A → B → C (all single-pred). B merges into A in first iteration;
|
|
585
|
+
// then A+B → C merges in second iteration.
|
|
586
|
+
const fn = {
|
|
587
|
+
name: 'test',
|
|
588
|
+
params: [],
|
|
589
|
+
blocks: [
|
|
590
|
+
mirBlock('entry', [{ kind: 'const', dst: 'a', value: 1 }], { kind: 'jump', target: 'b1' }),
|
|
591
|
+
mirBlock('b1', [{ kind: 'const', dst: 'b', value: 2 }], { kind: 'jump', target: 'b2' }, ['entry']),
|
|
592
|
+
mirBlock('b2', [{ kind: 'const', dst: 'c', value: 3 }], { kind: 'return', value: null }, ['b1']),
|
|
593
|
+
],
|
|
594
|
+
entry: 'entry',
|
|
595
|
+
isMacro: false,
|
|
596
|
+
};
|
|
597
|
+
const result = (0, block_merge_1.blockMerge)(fn);
|
|
598
|
+
expect(result.blocks).toHaveLength(1);
|
|
599
|
+
expect(result.blocks[0].instrs).toHaveLength(3);
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
// ===========================================================================
|
|
603
|
+
// 2b. unroll.ts — additional uncovered branches
|
|
604
|
+
// ===========================================================================
|
|
605
|
+
describe('loopUnroll — additional edge cases', () => {
|
|
606
|
+
test('initializesTo0: another instr defines loopVar before const → returns false', () => {
|
|
607
|
+
// In initializesTo0, if getInstrDst returns loopVar for a non-const, non-copy instr → false
|
|
608
|
+
const fn = {
|
|
609
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
610
|
+
blocks: [
|
|
611
|
+
mirBlock('entry', [
|
|
612
|
+
// Another instr defines 'i' — then const i 0 never reached in reverse scan
|
|
613
|
+
{ kind: 'add', dst: 'i', a: c(1), b: c(1) }, // defines i = 2
|
|
614
|
+
{ kind: 'const', dst: 'i', value: 0 }, // defines i = 0 (last def wins in reverse)
|
|
615
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
616
|
+
mirBlock('loop_header_0', [
|
|
617
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
|
|
618
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
619
|
+
mirBlock('loop_body_0', [
|
|
620
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
|
|
621
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
622
|
+
mirBlock('loop_latch_0', [
|
|
623
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
624
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
625
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
626
|
+
],
|
|
627
|
+
};
|
|
628
|
+
// Reverse scan hits const i 0 first → should unroll
|
|
629
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
630
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
631
|
+
});
|
|
632
|
+
test('initializesTo0: loopVar defined by non-zero non-copy instr → false', () => {
|
|
633
|
+
// In reverse scan: first hit is add dst=i → getInstrDst returns 'i' → return false
|
|
634
|
+
const fn = {
|
|
635
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
636
|
+
blocks: [
|
|
637
|
+
mirBlock('entry', [
|
|
638
|
+
// Only a non-zero add defines i — no const 0 before it in reverse scan
|
|
639
|
+
{ kind: 'add', dst: 'i', a: c(5), b: c(3) }, // defines i (not 0)
|
|
640
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
641
|
+
mirBlock('loop_header_0', [
|
|
642
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
|
|
643
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
644
|
+
mirBlock('loop_body_0', [
|
|
645
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
|
|
646
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
647
|
+
mirBlock('loop_latch_0', [
|
|
648
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
649
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
650
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
651
|
+
],
|
|
652
|
+
};
|
|
653
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
654
|
+
expect(result).toBe(fn); // not unrolled — not initialized to 0
|
|
655
|
+
});
|
|
656
|
+
test('findPreHeader: multiple predecessors to header → no unroll', () => {
|
|
657
|
+
// If loop_header has more than one non-latch predecessor, findPreHeader returns null
|
|
658
|
+
const fn = {
|
|
659
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
660
|
+
blocks: [
|
|
661
|
+
mirBlock('entry', [
|
|
662
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
663
|
+
], { kind: 'branch', cond: t('some_cond'), then: 'loop_header_0', else: 'alt' }),
|
|
664
|
+
mirBlock('alt', [
|
|
665
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
666
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
667
|
+
// loop_header has two non-latch preds: entry and alt
|
|
668
|
+
mirBlock('loop_header_0', [
|
|
669
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
|
|
670
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
671
|
+
mirBlock('loop_body_0', [
|
|
672
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
|
|
673
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
674
|
+
mirBlock('loop_latch_0', [
|
|
675
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
676
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
677
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
678
|
+
],
|
|
679
|
+
};
|
|
680
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
681
|
+
expect(result).toBe(fn); // not unrolled — ambiguous pre-header
|
|
682
|
+
});
|
|
683
|
+
test('body with nbt_write_dynamic substitutes both indexSrc and valueSrc', () => {
|
|
684
|
+
// Covers the nbt_write_dynamic case in substituteInstr
|
|
685
|
+
const fn = {
|
|
686
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
687
|
+
blocks: [
|
|
688
|
+
mirBlock('entry', [
|
|
689
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
690
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
691
|
+
mirBlock('loop_header_0', [
|
|
692
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
|
|
693
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
694
|
+
mirBlock('loop_body_0', [
|
|
695
|
+
// nbt_write_dynamic with i as both indexSrc and valueSrc
|
|
696
|
+
{
|
|
697
|
+
kind: 'nbt_write_dynamic',
|
|
698
|
+
ns: 'test', pathPrefix: 'arr',
|
|
699
|
+
indexSrc: t('i'),
|
|
700
|
+
valueSrc: t('i'),
|
|
701
|
+
},
|
|
702
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
703
|
+
mirBlock('loop_latch_0', [
|
|
704
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
705
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
706
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
707
|
+
],
|
|
708
|
+
};
|
|
709
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
710
|
+
// Should unroll — 2 copies
|
|
711
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
712
|
+
const entry = result.blocks.find(b => b.id === 'entry');
|
|
713
|
+
const writes = entry.instrs.filter(i => i.kind === 'nbt_write_dynamic');
|
|
714
|
+
expect(writes).toHaveLength(2);
|
|
715
|
+
// Both indexSrc and valueSrc should be substituted
|
|
716
|
+
const w0 = writes[0];
|
|
717
|
+
expect(w0.indexSrc).toEqual(c(0));
|
|
718
|
+
expect(w0.valueSrc).toEqual(c(0));
|
|
719
|
+
});
|
|
720
|
+
test('body with nbt_write substitutes src', () => {
|
|
721
|
+
const fn = {
|
|
722
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
723
|
+
blocks: [
|
|
724
|
+
mirBlock('entry', [
|
|
725
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
726
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
727
|
+
mirBlock('loop_header_0', [
|
|
728
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
|
|
729
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
730
|
+
mirBlock('loop_body_0', [
|
|
731
|
+
{ kind: 'nbt_write', ns: 'test', path: 'val', type: 'int', scale: 1, src: t('i') },
|
|
732
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
733
|
+
mirBlock('loop_latch_0', [
|
|
734
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
735
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
736
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
737
|
+
],
|
|
738
|
+
};
|
|
739
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
740
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
741
|
+
const entry = result.blocks.find(b => b.id === 'entry');
|
|
742
|
+
const writes = entry.instrs.filter(i => i.kind === 'nbt_write');
|
|
743
|
+
expect(writes).toHaveLength(2);
|
|
744
|
+
expect(writes[0].src).toEqual(c(0));
|
|
745
|
+
expect(writes[1].src).toEqual(c(1));
|
|
746
|
+
});
|
|
747
|
+
test('body with call_macro substitutes arg values', () => {
|
|
748
|
+
// Covers the call_macro case in substituteInstr
|
|
749
|
+
const fn = {
|
|
750
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
751
|
+
blocks: [
|
|
752
|
+
mirBlock('entry', [
|
|
753
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
754
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
755
|
+
mirBlock('loop_header_0', [
|
|
756
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
|
|
757
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
758
|
+
mirBlock('loop_body_0', [
|
|
759
|
+
{
|
|
760
|
+
kind: 'call_macro',
|
|
761
|
+
dst: null,
|
|
762
|
+
fn: 'test:macro_fn',
|
|
763
|
+
args: [{ name: 'x', value: t('i'), type: 'int', scale: 1 }],
|
|
764
|
+
},
|
|
765
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
766
|
+
mirBlock('loop_latch_0', [
|
|
767
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
768
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
769
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
770
|
+
],
|
|
771
|
+
};
|
|
772
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
773
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
774
|
+
const entry = result.blocks.find(b => b.id === 'entry');
|
|
775
|
+
const macroCalls = entry.instrs.filter(i => i.kind === 'call_macro');
|
|
776
|
+
expect(macroCalls).toHaveLength(2);
|
|
777
|
+
expect(macroCalls[0].args[0].value).toEqual(c(0));
|
|
778
|
+
expect(macroCalls[1].args[0].value).toEqual(c(1));
|
|
779
|
+
});
|
|
780
|
+
test('body with return instr: substitutes value', () => {
|
|
781
|
+
// return with value — substituteInstr covers the return case
|
|
782
|
+
const fn = {
|
|
783
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
784
|
+
blocks: [
|
|
785
|
+
mirBlock('entry', [
|
|
786
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
787
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
788
|
+
mirBlock('loop_header_0', [
|
|
789
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
|
|
790
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
791
|
+
// Body ending with a terminator substitution test via branch (cond=i)
|
|
792
|
+
mirBlock('loop_body_0', [
|
|
793
|
+
{ kind: 'call', dst: null, fn: 'test:f', args: [] },
|
|
794
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
795
|
+
mirBlock('loop_latch_0', [
|
|
796
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
797
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
798
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: t('i') }),
|
|
799
|
+
],
|
|
800
|
+
};
|
|
801
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
802
|
+
// Unrolled — exit is kept with return value
|
|
803
|
+
expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false);
|
|
804
|
+
});
|
|
805
|
+
test('loop with N=0: does not unroll (N <= 0 guard)', () => {
|
|
806
|
+
const fn = {
|
|
807
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
808
|
+
blocks: [
|
|
809
|
+
mirBlock('entry', [
|
|
810
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
811
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
812
|
+
mirBlock('loop_header_0', [
|
|
813
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(0) },
|
|
814
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
815
|
+
mirBlock('loop_body_0', [
|
|
816
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [] },
|
|
817
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
818
|
+
mirBlock('loop_latch_0', [
|
|
819
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
820
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
821
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
822
|
+
],
|
|
823
|
+
};
|
|
824
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
825
|
+
expect(result).toBe(fn); // N=0 is rejected
|
|
826
|
+
});
|
|
827
|
+
test('loop_header with no cmp instr: not unrolled', () => {
|
|
828
|
+
// header has branch but no cmp instruction for condName
|
|
829
|
+
const fn = {
|
|
830
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
831
|
+
blocks: [
|
|
832
|
+
mirBlock('entry', [
|
|
833
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
834
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
835
|
+
mirBlock('loop_header_0', [
|
|
836
|
+
// No cmp here — branch uses t_cmp but nothing defines it
|
|
837
|
+
], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
838
|
+
mirBlock('loop_body_0', [
|
|
839
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [] },
|
|
840
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
841
|
+
mirBlock('loop_latch_0', [
|
|
842
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
843
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
844
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
845
|
+
],
|
|
846
|
+
};
|
|
847
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
848
|
+
expect(result).toBe(fn);
|
|
849
|
+
});
|
|
850
|
+
test('loop_header with const cond (not temp): not unrolled', () => {
|
|
851
|
+
// branch.cond must be temp — if it's const, skip
|
|
852
|
+
const fn = {
|
|
853
|
+
name: 'test', params: [], entry: 'entry', isMacro: false,
|
|
854
|
+
blocks: [
|
|
855
|
+
mirBlock('entry', [
|
|
856
|
+
{ kind: 'const', dst: 'i', value: 0 },
|
|
857
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
858
|
+
mirBlock('loop_header_0', [
|
|
859
|
+
{ kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
|
|
860
|
+
], { kind: 'branch', cond: c(1), then: 'loop_body_0', else: 'loop_exit_0' }),
|
|
861
|
+
mirBlock('loop_body_0', [
|
|
862
|
+
{ kind: 'call', dst: null, fn: 'test:body', args: [] },
|
|
863
|
+
], { kind: 'jump', target: 'loop_latch_0' }),
|
|
864
|
+
mirBlock('loop_latch_0', [
|
|
865
|
+
{ kind: 'add', dst: 'i', a: t('i'), b: c(1) },
|
|
866
|
+
], { kind: 'jump', target: 'loop_header_0' }),
|
|
867
|
+
mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
|
|
868
|
+
],
|
|
869
|
+
};
|
|
870
|
+
const result = (0, unroll_1.loopUnroll)(fn);
|
|
871
|
+
expect(result).toBe(fn);
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
// ===========================================================================
|
|
875
|
+
// 6. constant_fold.ts — uncovered branch (evalCmp default)
|
|
876
|
+
// ===========================================================================
|
|
877
|
+
describe('constantFold — evalCmp default branch', () => {
|
|
878
|
+
test('unknown cmp op falls through to default (returns 0)', () => {
|
|
879
|
+
// The default case in evalCmp — an op that doesn't match any known op
|
|
880
|
+
// We test through constantFold by constructing a cmp with unknown op
|
|
881
|
+
const fn = {
|
|
882
|
+
name: 'test',
|
|
883
|
+
params: [],
|
|
884
|
+
blocks: [
|
|
885
|
+
mirBlock('entry', [
|
|
886
|
+
// Use an unknown cmp op — TypeScript won't normally allow this
|
|
887
|
+
// but we cast to test the runtime default branch
|
|
888
|
+
{
|
|
889
|
+
kind: 'cmp',
|
|
890
|
+
dst: 'result',
|
|
891
|
+
op: 'unknown_op',
|
|
892
|
+
a: c(5),
|
|
893
|
+
b: c(3),
|
|
894
|
+
},
|
|
895
|
+
], { kind: 'return', value: t('result') }),
|
|
896
|
+
],
|
|
897
|
+
entry: 'entry',
|
|
898
|
+
isMacro: false,
|
|
899
|
+
};
|
|
900
|
+
const result = (0, constant_fold_1.constantFold)(fn);
|
|
901
|
+
// Should have folded cmp(unknown_op, 5, 3) → const 0 (default case)
|
|
902
|
+
const instr = result.blocks[0].instrs[0];
|
|
903
|
+
expect(instr.kind).toBe('const');
|
|
904
|
+
if (instr.kind === 'const') {
|
|
905
|
+
expect(instr.value).toBe(0);
|
|
906
|
+
expect(instr.dst).toBe('result');
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
test('all six cmp ops fold correctly', () => {
|
|
910
|
+
// Regression: verify all known ops work (these are covered but good to have)
|
|
911
|
+
const ops = [
|
|
912
|
+
{ op: 'eq', a: 5, b: 5, expected: 1 },
|
|
913
|
+
{ op: 'eq', a: 5, b: 3, expected: 0 },
|
|
914
|
+
{ op: 'ne', a: 5, b: 3, expected: 1 },
|
|
915
|
+
{ op: 'ne', a: 5, b: 5, expected: 0 },
|
|
916
|
+
{ op: 'lt', a: 3, b: 5, expected: 1 },
|
|
917
|
+
{ op: 'lt', a: 5, b: 3, expected: 0 },
|
|
918
|
+
{ op: 'le', a: 5, b: 5, expected: 1 },
|
|
919
|
+
{ op: 'le', a: 6, b: 5, expected: 0 },
|
|
920
|
+
{ op: 'gt', a: 5, b: 3, expected: 1 },
|
|
921
|
+
{ op: 'gt', a: 3, b: 5, expected: 0 },
|
|
922
|
+
{ op: 'ge', a: 5, b: 5, expected: 1 },
|
|
923
|
+
{ op: 'ge', a: 4, b: 5, expected: 0 },
|
|
924
|
+
];
|
|
925
|
+
for (const { op, a: av, b: bv, expected } of ops) {
|
|
926
|
+
const fn = {
|
|
927
|
+
name: 'test',
|
|
928
|
+
params: [],
|
|
929
|
+
blocks: [
|
|
930
|
+
mirBlock('entry', [
|
|
931
|
+
{ kind: 'cmp', dst: 'r', op, a: c(av), b: c(bv) },
|
|
932
|
+
], { kind: 'return', value: t('r') }),
|
|
933
|
+
],
|
|
934
|
+
entry: 'entry',
|
|
935
|
+
isMacro: false,
|
|
936
|
+
};
|
|
937
|
+
const result = (0, constant_fold_1.constantFold)(fn);
|
|
938
|
+
const instr = result.blocks[0].instrs[0];
|
|
939
|
+
expect(instr.kind).toBe('const');
|
|
940
|
+
if (instr.kind === 'const') {
|
|
941
|
+
expect(instr.value).toBe(expected);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
test('cmp with non-const operands is not folded', () => {
|
|
946
|
+
const fn = {
|
|
947
|
+
name: 'test',
|
|
948
|
+
params: [],
|
|
949
|
+
blocks: [
|
|
950
|
+
mirBlock('entry', [
|
|
951
|
+
{ kind: 'cmp', dst: 'r', op: 'lt', a: t('x'), b: c(5) },
|
|
952
|
+
], { kind: 'return', value: t('r') }),
|
|
953
|
+
],
|
|
954
|
+
entry: 'entry',
|
|
955
|
+
isMacro: false,
|
|
956
|
+
};
|
|
957
|
+
const result = (0, constant_fold_1.constantFold)(fn);
|
|
958
|
+
// Not folded — a is temp
|
|
959
|
+
expect(result.blocks[0].instrs[0].kind).toBe('cmp');
|
|
960
|
+
});
|
|
961
|
+
test('div by zero is not folded', () => {
|
|
962
|
+
const fn = {
|
|
963
|
+
name: 'test',
|
|
964
|
+
params: [],
|
|
965
|
+
blocks: [
|
|
966
|
+
mirBlock('entry', [
|
|
967
|
+
{ kind: 'div', dst: 'r', a: c(10), b: c(0) },
|
|
968
|
+
], { kind: 'return', value: t('r') }),
|
|
969
|
+
],
|
|
970
|
+
entry: 'entry',
|
|
971
|
+
isMacro: false,
|
|
972
|
+
};
|
|
973
|
+
const result = (0, constant_fold_1.constantFold)(fn);
|
|
974
|
+
// div by 0 should not be folded
|
|
975
|
+
expect(result.blocks[0].instrs[0].kind).toBe('div');
|
|
976
|
+
});
|
|
977
|
+
test('mod by zero is not folded', () => {
|
|
978
|
+
const fn = {
|
|
979
|
+
name: 'test',
|
|
980
|
+
params: [],
|
|
981
|
+
blocks: [
|
|
982
|
+
mirBlock('entry', [
|
|
983
|
+
{ kind: 'mod', dst: 'r', a: c(10), b: c(0) },
|
|
984
|
+
], { kind: 'return', value: t('r') }),
|
|
985
|
+
],
|
|
986
|
+
entry: 'entry',
|
|
987
|
+
isMacro: false,
|
|
988
|
+
};
|
|
989
|
+
const result = (0, constant_fold_1.constantFold)(fn);
|
|
990
|
+
expect(result.blocks[0].instrs[0].kind).toBe('mod');
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
//# sourceMappingURL=optimizer-extended.test.js.map
|