From eb0d67806870c77aacaeadef1a3cd7b30c7db12d Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sun, 12 Oct 2025 11:12:05 -0500 Subject: [PATCH 1/2] Only analyze repeat block inputs at the start of the loop --- src/compiler/iroptimizer.js | 13 ++++++-- .../tw-repeat-analysis-doesnt-reanalyze.sb3 | Bin 0 -> 3073 bytes ...-analysis-doesnt-reanalyze.sb3.tw-snapshot | 28 +++++++++++++++++ ...-analysis-doesnt-reanalyze.sb3.tw-snapshot | 29 ++++++++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/execute/tw-repeat-analysis-doesnt-reanalyze.sb3 create mode 100644 test/snapshot/__snapshots__/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index ae7d98e495..146aed1e37 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -559,9 +559,12 @@ class IROptimizer { break; case StackOpcode.CONTROL_WHILE: case StackOpcode.CONTROL_FOR: + modified = this.analyzeInputs(inputs, state) || modified; + modified = this.analyzeLoopedStack(inputs.do, state, stackBlock, true) || modified; + break; case StackOpcode.CONTROL_REPEAT: modified = this.analyzeInputs(inputs, state) || modified; - modified = this.analyzeLoopedStack(inputs.do, state, stackBlock) || modified; + modified = this.analyzeLoopedStack(inputs.do, state, stackBlock, false) || modified; break; case StackOpcode.CONTROL_IF_ELSE: { modified = this.analyzeInputs(inputs, state) || modified; @@ -642,10 +645,11 @@ class IROptimizer { * @param {IntermediateStack} stack * @param {TypeState} state * @param {IntermediateStackBlock} block + * @param {boolean} willReevaluateInputs * @returns {boolean} * @private */ - analyzeLoopedStack (stack, state, block) { + analyzeLoopedStack (stack, state, block, willReevaluateInputs) { if (block.yields && !this.ignoreYields) { let modified = state.clear(); block.entryState = state.clone(); @@ -672,7 +676,10 @@ class IROptimizer { const newState = state.clone(); modified = this.analyzeStack(stack, newState) || modified; modified = (keepLooping = state.or(newState)) || modified; - modified = this.analyzeInputs(block.inputs, state) || modified; + + if (willReevaluateInputs) { + modified = this.analyzeInputs(block.inputs, state) || modified; + } } while (keepLooping); block.entryState = state.clone(); return modified; diff --git a/test/fixtures/execute/tw-repeat-analysis-doesnt-reanalyze.sb3 b/test/fixtures/execute/tw-repeat-analysis-doesnt-reanalyze.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..12d51fb79bc018595d6a1e5fd5572517b0b5a074 GIT binary patch literal 3073 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 bJ4eI6L4uJUHO-%@3+Fw5{sPZ&0s#CM9(G|C literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot new file mode 100644 index 0000000000..020097c50f --- /dev/null +++ b/test/snapshot/__snapshots__/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot @@ -0,0 +1,28 @@ +// TW Snapshot +// Input SHA-256: 38075839898d1ba3d2556f350ff18438a205c5b6b3cb809d07d3f738bd37dad9 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "d", null); +thread.procedures["Wa"](); +if ((("" + b1.value).toLowerCase() === "bwah".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "l", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "k", null); +retire(); return; +}; }) + +// Sprite1 Wa +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b1 = stage.variables["6h7bpiz0`;;x^G1B3(%["]; +return function funXYZ_a () { +b0.value = b1.value.length; +for (var a0 = b0.value; a0 > 0; a0--) { +b0.value = "bwah"; +} +return ""; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot new file mode 100644 index 0000000000..5366b9c958 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot @@ -0,0 +1,29 @@ +// TW Snapshot +// Input SHA-256: 38075839898d1ba3d2556f350ff18438a205c5b6b3cb809d07d3f738bd37dad9 + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "d", null); +yield* thread.procedures["Wa"](); +if ((("" + b1.value).toLowerCase() === "bwah".toLowerCase())) { +yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "l", null); +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "k", null); +retire(); return; +}; }) + +// Sprite1 Wa +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +const b1 = stage.variables["6h7bpiz0`;;x^G1B3(%["]; +return function* genXYZ_a () { +b0.value = b1.value.length; +for (var a0 = (+b0.value || 0); a0 >= 0.5; a0--) { +b0.value = "bwah"; +if (isStuck()) yield; +} +return ""; +}; }) From 8860e2d6027e1d4040a35788c9003b7b6c5a3ad6 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Sun, 12 Oct 2025 11:16:25 -0500 Subject: [PATCH 2/2] Fix repeat block iteration count not being optimizable in yielding loops --- src/compiler/iroptimizer.js | 9 ++++++--- .../tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index 146aed1e37..2693613425 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -650,16 +650,19 @@ class IROptimizer { * @private */ analyzeLoopedStack (stack, state, block, willReevaluateInputs) { + let modified = false; + if (block.yields && !this.ignoreYields) { - let modified = state.clear(); + modified = state.clear(); + if (willReevaluateInputs) { + modified = this.analyzeInputs(block.inputs, state) || modified; + } block.entryState = state.clone(); block.exitState = state.clone(); - modified = this.analyzeInputs(block.inputs, state) || modified; return this.analyzeStack(stack, state) || modified; } let iterations = 0; - let modified = false; let keepLooping; do { // If we are stuck in an apparent infinite loop, give up and assume the worst. diff --git a/test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot index 5366b9c958..35a0f59d7b 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-repeat-analysis-doesnt-reanalyze.sb3.tw-snapshot @@ -21,7 +21,7 @@ const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; const b1 = stage.variables["6h7bpiz0`;;x^G1B3(%["]; return function* genXYZ_a () { b0.value = b1.value.length; -for (var a0 = (+b0.value || 0); a0 >= 0.5; a0--) { +for (var a0 = b0.value; a0 > 0; a0--) { b0.value = "bwah"; if (isStuck()) yield; }