From ebeec4818d680f09278c51aea8ae5a9126dda032 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 15:58:14 -0500 Subject: [PATCH] Prove 32-bit products exact only below 2^53 The exactness test for a product's 32-bit form multiplied the operands' magnitude bounds in a double and accepted a result of at most 2^53. A bound of 2^53 + 1 (3 * 3002399751580331) rounds to 2^53 there, so a product the double rounds took the integer multiply's low bits: `(3 * 3002399751580331) | 0` printed 1 where Node prints 0. The test is now strict: a bound below 2^53 is exact, and no bound of 2^53 or more rounds below it. --- .../src/backend/llvm/wrapping-sums.ts | 8 +++--- .../8195-wrapping-products-near-2-53.ts.json | 6 +++++ .../8195-wrapping-products-near-2-53.ts | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json create mode 100644 tests/corpus/8195-wrapping-products-near-2-53.ts diff --git a/packages/compiler/src/backend/llvm/wrapping-sums.ts b/packages/compiler/src/backend/llvm/wrapping-sums.ts index e93d66909..5b487d809 100644 --- a/packages/compiler/src/backend/llvm/wrapping-sums.ts +++ b/packages/compiler/src/backend/llvm/wrapping-sums.ts @@ -176,12 +176,14 @@ export function emitWrappingSum( } /** Whether every product of a value in `left` and one in `right` is an - * integer of magnitude at most 2^53, which the double product holds - * exactly. */ + * integer of magnitude below 2^53, which the double product holds exactly. + * The bound's own product is computed in a double: a bound of at least + * 2^53 never rounds below 2^53, while 2^53 + 1 (3 * 3002399751580331) + * rounds to 2^53, so the test is strict. */ function exactProduct(left: IntegerRange, right: IntegerRange): boolean { const magnitude = (range: IntegerRange): number => Math.max(Math.abs(range.min), Math.abs(range.max)); - return magnitude(left) * magnitude(right) <= 2 ** 53; + return magnitude(left) * magnitude(right) < 2 ** 53; } /** The number locals of `fn` that only hold exact integers (never -0) or NaN, with the range of diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json new file mode 100644 index 000000000..234bcebd5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8195-wrapping-products-near-2-53.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8195-wrapping-products-near-2-53.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8195-wrapping-products-near-2-53.ts b/tests/corpus/8195-wrapping-products-near-2-53.ts new file mode 100644 index 000000000..c42104290 --- /dev/null +++ b/tests/corpus/8195-wrapping-products-near-2-53.ts @@ -0,0 +1,25 @@ +// Products that reach bitwise operators with operands whose ranges are +// known, around 2^53: below it the double product is exact, and from it on +// the double product rounds, so the 32-bit result comes from the rounded +// value (3 * 3002399751580331 is 2^53 + 1, which rounds to 2^53). +const pairs: [number, number][] = [ + [3, 3002399751580331], + [-3, 3002399751580331], + [2 ** 26, 2 ** 27], + [94906265, 94906267], + [94906267, 94906267], + [7, 1286742750677284], + [65535, 137443147777], +]; +for (const [a, b] of pairs) console.log(a, b, (a * b) | 0, (a * b) >>> 0, (a * b) ^ 1, (a * b) << 3); + +// The same products with literal operands, which the compiler sees whole. +console.log((3 * 3002399751580331) | 0, (3 * 3002399751580331) >>> 0); +console.log((-3 * 3002399751580331) | 0, (2 ** 26 * 2 ** 27) | 0); +console.log((94906267 * 94906267) | 0, (94906265 * 94906267) >>> 0); + +// A counted loop whose counter multiplies a constant past 2^53. +const step = 1286742750677284; +let h = 0; +for (let k = 5; k < 9; k++) h = (h + ((k * step) | 0)) | 0; +console.log(h);