diff --git a/packages/compiler/src/backend/llvm/speculative-integers.test.ts b/packages/compiler/src/backend/llvm/speculative-integers.test.ts index 132780937..ae9172298 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.test.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.test.ts @@ -182,6 +182,9 @@ test("invariants may come from bindings no shadow could be written to", () => { test("loops that only count and compare keep one copy", () => { const counter = loop([increment("i")]); expect(planSpeculativeLoop(counter as never, all)).toBeNull(); + // A product in the condition alone is not. + const bounded = loop([increment("i")], bin("<", ref("i"), bin("*", ref("n"), ref("scale")))); + expect(planSpeculativeLoop(bounded as never, all)).toBeNull(); // A variable step (a sieve's `j += i`) is worth a copy. const sieve = loop([assign("i", bin("+", ref("i"), ref("step")))]); expect(planSpeculativeLoop(sieve as never, all)?.locals.has("i")).toBe(true); diff --git a/packages/compiler/src/backend/llvm/speculative-integers.ts b/packages/compiler/src/backend/llvm/speculative-integers.ts index f3b75fb9a..cf1295fa2 100644 --- a/packages/compiler/src/backend/llvm/speculative-integers.ts +++ b/packages/compiler/src/backend/llvm/speculative-integers.ts @@ -259,6 +259,9 @@ export function planSpeculativeLoop( // double forms cost the most. A loop whose integers only count and compare // gains nothing measurable from a second copy, so it keeps one. const multiplied = new Set(); + // A product in the condition alone (`i < Math.floor(n * scale)`) does not + // make the body's work integer. + let inCondition = false; const expr = (e: IrExpr): boolean => { if (rejected) return false; @@ -272,7 +275,7 @@ export function planSpeculativeLoop( else if (e.kind === "bin" && CONSUMING_OPS.has(e.op)) { consume(e.left); consume(e.right); - if (e.op === "*" || e.op === "/" || e.op === "%") + if (!inCondition && (e.op === "*" || e.op === "/" || e.op === "%")) for (const operand of [e.left, e.right]) if (operand.kind === "varRef") multiplied.add(operand.localId); } else if (e.kind === "arrayGet" || e.kind === "arrayHas" || e.kind === "arrayState") @@ -321,7 +324,11 @@ export function planSpeculativeLoop( return everyStmtChild(s, expr, stmt); }; - if (loop.cond) expr(loop.cond); + if (loop.cond) { + inCondition = true; + expr(loop.cond); + inCondition = false; + } if (loop.kind === "for" && loop.update) stmt(loop.update); for (const s of loop.body) stmt(s); if (rejected || nodes > MAX_LOOP_NODES) return null; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/speculative-integer-reads.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/speculative-integer-reads.ts.json new file mode 100644 index 000000000..b05283134 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/speculative-integer-reads.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/speculative-integer-reads.ts" + ], + "diags": [] +}