From cc96ee8c70ac24b165dfe536d8b2f1b327011cfe Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:59:06 +1000 Subject: [PATCH 1/4] Fix `-0` being turned into `0` in the type matrix test --- src/compiler/intermediate.js | 43 ++++++++++++++++++++- test/integration/tw_operator_type_matrix.js | 6 +-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/compiler/intermediate.js b/src/compiler/intermediate.js index 23c82cc8fe8..2a663145651 100644 --- a/src/compiler/intermediate.js +++ b/src/compiler/intermediate.js @@ -209,6 +209,46 @@ class IntermediateInput { } } + +/** + * @param {InputType} type + * @returns {string} + */ +const stringifyType = type => { + let formatFlags = []; + + for (const enumValue in InputType) { + const testFormat = InputType[enumValue]; + + if ((testFormat & type) === testFormat) { + for (const existingFormat of formatFlags) { + if ((testFormat & InputType[existingFormat]) === testFormat) { + continue; + } + } + + formatFlags = formatFlags.filter(value => (InputType[value] & testFormat) !== InputType[value]); + formatFlags.push(enumValue); + } + } + + let str = null; + + for (const formatFlag of formatFlags) { + if (str === null) { + str = formatFlag; + } else { + str = `${str} | ${formatFlag}`; + } + } + + if (str === null) { + return 'INVALID'; + } + + return str; +}; + /** * A 'stack' of blocks, like the contents of a script or the inside * of a C block. @@ -350,5 +390,6 @@ module.exports = { IntermediateInput, IntermediateStack, IntermediateScript, - IntermediateRepresentation + IntermediateRepresentation, + stringifyType }; diff --git a/test/integration/tw_operator_type_matrix.js b/test/integration/tw_operator_type_matrix.js index 2402067302c..035102fe037 100644 --- a/test/integration/tw_operator_type_matrix.js +++ b/test/integration/tw_operator_type_matrix.js @@ -3,7 +3,7 @@ const VM = require('../../src/virtual-machine'); const {BlockType, ArgumentType} = require('../../src/extension-support/tw-extension-api-common'); const {IRGenerator} = require('../../src/compiler/irgen'); const {IROptimizer} = require('../../src/compiler/iroptimizer'); -const {IntermediateInput} = require('../../src/compiler/intermediate'); +const {IntermediateInput, stringifyType} = require('../../src/compiler/intermediate'); const nanolog = require('@turbowarp/nanolog'); const VALUES = [ @@ -113,7 +113,7 @@ test('operator type matrix', async t => { 1, [ 4, - `${inputs[i]}` + `${Object.is(inputs[i], -0) ? '-0' : inputs[i]}` ] ]; } @@ -180,7 +180,7 @@ test('operator type matrix', async t => { t.ok( irOperator.isSometimesType(expectedType), `${operator.opcode}${JSON.stringify(operator.fields)}[${inputs.map(str)}] ` + - `outputted value ${str(reportedValue)} is of the expected type ${irOperator.type}.` + `outputted value ${str(reportedValue)} is of the expected type ${stringifyType(irOperator.type)}.` ); }; From c16be2517896a0f825f494fd3b171b6de33eb7e8 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:59:56 +1000 Subject: [PATCH 2/4] Fix #273 --- src/compiler/iroptimizer.js | 2 +- .../__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index f44dd730ce8..fe162349062 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -644,8 +644,8 @@ class IROptimizer { const newState = state.clone(); modified = this.analyzeStack(stack, newState) || modified; + modified = this.analyzeInputs(block.inputs, newState) || modified; modified = (keepLooping = state.or(newState)) || modified; - modified = this.analyzeInputs(block.inputs, state) || modified; } while (keepLooping); block.entryState = state.clone(); return modified; diff --git a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot index 224f800f45e..d2efe972b71 100644 --- a/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-gh-249-quicksort.sb3.tw-snapshot @@ -67,10 +67,10 @@ b0.value = (b1.value[(((((+p1 || 0) + (+p0 || 0)) || 0) / 2) | 0) - 1] ?? ""); b2.value = p0; b3.value = p1; while (true) { -while (compareLessThan(listGet(b1.value, b2.value), b0.value)) { +while (compareLessThan((b1.value[(b2.value | 0) - 1] ?? ""), b0.value)) { b2.value = ((+b2.value || 0) + 1); } -while (compareGreaterThan(listGet(b1.value, b3.value), b0.value)) { +while (compareGreaterThan((b1.value[(b3.value | 0) - 1] ?? ""), b0.value)) { b3.value = ((+b3.value || 0) + -1); } if (compareGreaterThan(b2.value, b3.value)) { From 749e9233787baf51d6e0ce48e1766973cb0ee774 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:10:50 +1000 Subject: [PATCH 3/4] Fix #272 (thanks @Geotale) --- src/compiler/iroptimizer.js | 34 ++++++++++--------- .../__snapshots__/tw-NaN.sb3.tw-snapshot | 6 ++-- ...s-515-non-finite-direction.sb3.tw-snapshot | 2 +- .../warp-timer/tw-NaN.sb3.tw-snapshot | 6 ++-- ...s-515-non-finite-direction.sb3.tw-snapshot | 2 +- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index fe162349062..f20a47ea4f1 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -377,8 +377,8 @@ class IROptimizer { let resultType = 0; const canBeNaN = function () { - // REAL / 0 = NaN - if ((leftType & InputType.NUMBER_REAL) && (rightType & InputType.NUMBER_ZERO)) return true; + // (-)0 / (-)0 = NaN + if ((leftType & InputType.NUMBER_ANY_ZERO) && (rightType & InputType.NUMBER_ANY_ZERO)) return true; // (-)Infinity / (-)Infinity = NaN if ((leftType & InputType.NUMBER_INF) && (rightType & InputType.NUMBER_INF)) return true; // (-)0 / NaN = NaN @@ -395,24 +395,26 @@ class IROptimizer { if (canBePos()) resultType |= InputType.NUMBER_POS; const canBeNegInfinity = function () { - // -Infinity / 0 = -Infinity - if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_ZERO)) return true; - // Infinity / -0 = -Infinity - if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; - // NEG_REAL / NaN = -Infinity - if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NAN)) return true; - // NEG_REAL / NUMBER_OR_NAN ~= -Infinity - if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; + // NEG / 0 = -Infinity + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_ZERO)) return true; + // POS / -0 = -Infinity + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // NEG_REAL / POS_REAL ~= -Infinity + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // POS_REAL / NEG_REAL ~= -Infinity + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; }; if (canBeNegInfinity()) resultType |= InputType.NUMBER_NEG_INF; const canBeInfinity = function () { - // Infinity / 0 = Infinity - if ((leftType & InputType.NUMBER_POS_INF) && (rightType & InputType.NUMBER_ZERO)) return true; - // -Infinity / -0 = Infinity - if ((leftType & InputType.NUMBER_NEG_INF) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; - // POS_REAL / NUMBER_OR_NAN ~= Infinity - if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_OR_NAN)) return true; + // POS / 0 = Infinity + if ((leftType & InputType.NUMBER_POS) && (rightType & InputType.NUMBER_ZERO)) return true; + // NEG / -0 = Infinity + if ((leftType & InputType.NUMBER_NEG) && (rightType & InputType.NUMBER_NEG_ZERO)) return true; + // POS_REAL / POS_REAL ~= Infinity + if ((leftType & InputType.NUMBER_POS_REAL) && (rightType & InputType.NUMBER_POS_REAL)) return true; + // NEG_REAL / NEG_REAL ~= Infinity + if ((leftType & InputType.NUMBER_NEG_REAL) && (rightType & InputType.NUMBER_NEG_REAL)) return true; }; if (canBeInfinity()) resultType |= InputType.NUMBER_POS_INF; diff --git a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot index 537fd7102ad..655a04a4e77 100644 --- a/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot @@ -57,13 +57,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ao", if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aq", null); } -if (((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "as", null); } -if (((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "au", null); } -if ((((tan(((1 / 0) || 0)) || 0) * 1) === 0)) { +if ((((tan((1 / 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aw", null); } if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) { diff --git a/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot index 59991893cea..00bb8c4d3e3 100644 --- a/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot @@ -11,7 +11,7 @@ target.setDirection(95); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 1",}, b0, false, false, "o", null); } -target.setDirection(((1 / 0) || 0)); +target.setDirection((1 / 0)); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 2",}, b0, false, false, "r", null); } diff --git a/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot index 537fd7102ad..655a04a4e77 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-NaN.sb3.tw-snapshot @@ -57,13 +57,13 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ao", if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aq", null); } -if (((((Math.round(Math.sin((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "as", null); } -if (((((Math.round(Math.cos((Math.PI * ((1 / 0) || 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { +if (((((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "au", null); } -if ((((tan(((1 / 0) || 0)) || 0) * 1) === 0)) { +if ((((tan((1 / 0)) || 0) * 1) === 0)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aw", null); } if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) { diff --git a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot index 59991893cea..00bb8c4d3e3 100644 --- a/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot +++ b/test/snapshot/__snapshots__/warp-timer/tw-forkphorus-515-non-finite-direction.sb3.tw-snapshot @@ -11,7 +11,7 @@ target.setDirection(95); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 1",}, b0, false, false, "o", null); } -target.setDirection(((1 / 0) || 0)); +target.setDirection((1 / 0)); if ((target.direction === 95)) { yield* executeInCompatibilityLayer({"MESSAGE":"pass 2",}, b0, false, false, "r", null); } From d17ceccd830188887ac2746b2c1fc720ffc91a52 Mon Sep 17 00:00:00 2001 From: Tacodiva <27910867+Tacodiva@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:13:11 +1000 Subject: [PATCH 4/4] Fix #269 --- src/compiler/irgen.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js index 2a22749b345..8bc9897b97e 100644 --- a/src/compiler/irgen.js +++ b/src/compiler/irgen.js @@ -299,14 +299,14 @@ class ScriptTreeGenerator { } return new IntermediateInput(InputOpcode.LOOKS_COSTUME_NAME, InputType.STRING); case 'looks_size': - return new IntermediateInput(InputOpcode.LOOKS_SIZE_GET, InputType.NUMBER_POS_REAL); + return new IntermediateInput(InputOpcode.LOOKS_SIZE_GET, InputType.NUMBER_POS); case 'motion_direction': return new IntermediateInput(InputOpcode.MOTION_DIRECTION_GET, InputType.NUMBER_REAL); case 'motion_xposition': - return new IntermediateInput(InputOpcode.MOTION_X_GET, InputType.NUMBER_REAL); + return new IntermediateInput(InputOpcode.MOTION_X_GET, InputType.NUMBER); case 'motion_yposition': - return new IntermediateInput(InputOpcode.MOTION_Y_GET, InputType.NUMBER_REAL); + return new IntermediateInput(InputOpcode.MOTION_Y_GET, InputType.NUMBER); case 'operator_add': return new IntermediateInput(InputOpcode.OP_ADD, InputType.NUMBER_OR_NAN, { @@ -494,7 +494,7 @@ class ScriptTreeGenerator { case 'sensing_dayssince2000': return new IntermediateInput(InputOpcode.SENSING_TIME_DAYS_SINCE_2000, InputType.NUMBER); case 'sensing_distanceto': - return new IntermediateInput(InputOpcode.SENSING_DISTANCE, InputType.NUMBER_POS_REAL | InputType.NUMBER_ZERO, { + return new IntermediateInput(InputOpcode.SENSING_DISTANCE, InputType.NUMBER_POS | InputType.NUMBER_ZERO, { target: this.descendInputOfBlock(block, 'DISTANCETOMENU').toType(InputType.STRING) }); case 'sensing_keypressed':