Skip to content

fix(codegen): round JS arithmetic to f64 at every i32-chain step (#7232) - #7237

Merged
proggeramlug merged 6 commits into
mainfrom
fix/7232-i32-mul-precision
Aug 2, 2026
Merged

fix(codegen): round JS arithmetic to f64 at every i32-chain step (#7232)#7237
proggeramlug merged 6 commits into
mainfrom
fix/7232-i32-mul-precision

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #7232.

The bug

let s0 = 12345;
let s1 = (s0 * 1103515245 + 12345) & 0x7fffffff;
let s2 = (s1 * 1103515245 + 12345) & 0x7fffffff;   // node 654583808, perry 654583775

An LCG step. Wrong straight-line and loop-carried; correct through a function
boundary. No throw, no warning — just different numbers, in PRNG seeds, hash
mixing and checksum accumulators.

Root cause

crates/perry-codegen/src/expr/i32_fast_path.rs. The i32-native fast path
evaluated the whole chain in exact two's-complement mul/add i32:

%r4 = mul i32 %r3, 1103515245
%r5 = add i32 %r4, 12345
%r6 = and i32 %r5, 2147483647

ECMAScript numbers are IEEE-754 doubles, so * and + round their result to
the nearest double before the next operator runs. 1406932606 * 1103515245 is ~2^61 — past 2^53, where the double's ulp is 256 — so Node's
mask reads a rounded product while the exact mul i32 reads the low bits the
double had already discarded. It only reproduced through a local because a
function boundary NaN-boxes the intermediate, which rounds it.

The old admission rule (can_lower_expr_as_i32) required only that every
integer literal fit in i32. That is neither necessary — Math.imul is
defined as an exact low-32 multiply and needs no such rule — nor sufficient:
1103515245 fits in i32, and its product with an i32-range local does not fit
in a double.

The invariant

An i32-native chain computes the exact two's-complement low 32 bits of the
integer result. JS evaluates the same chain in doubles, rounding at every
operator. The two agree only while every intermediate is exactly
representable as a double
|v| <= 2^53. Below that ceiling the JS double
is the exact integer, so low32(exact) == ToInt32(double); above it, they
are different numbers.

So the predicate now carries a magnitude bound through the whole chain
(i32_chain_magnitude_bits), capped at 53. Add/Sub grow the bound by one
bit and Mul sums it — the same composition known_finite_magnitude_bits
already used for the toint32_fast poison proof, with 53 in place of 63
because this bound gates exact integer arithmetic, not one fptosi. The cap
applies to Add/Sub too, not only Mul: two ceiling-width products sum to
2^54.

Both emitters of the chain consult the same function, so the gate and the
last-resort arithmetic arm in lower_expr_native_i32 cannot drift apart.

Keeping the fast path fast

A naive 53-bit cap deoptimizes a lot of correct code, so the bound is measured
rather than assumed:

  • an integer literal contributes its own bit width (h * 31 is 37 bits, not 64);
  • x & m with a non-negative literal mask lands in [0, m];
  • x >> k / x >>> k by a literal k drop k bits;
  • a const bound to a numeric literal contributes its width — this is what
    keeps the dominant strided-index shape buf[y * WIDTH + x] exact;
  • Math.imul is exempt entirely.

Measured over all 30 programs in benchmarks/suite/, comparing emitted LLVM IR
byte-for-byte against main: 29 are unchanged. The one that moves is
11_prime_sieve, whose for (let j = i * i; ...) preheader is genuinely
unbounded and now rounds — one mul i32 becomes fmul + fjcvtzs, executed
once per outer-loop iteration.

Two of those 29 took a detour worth recording, because both are cases where the
first attempt at this fix did regress them:

  • bench_int_arithmetic lost half its mul i32 (108 → 54) until the const
    literal's own width was measured — const SIZE = 100 was reading as the
    32-bit default, so the convolution index (y - 1) * SIZE + (x - 1) came out
    at 65 bits.
  • bench_typed_array_untyped_access (the Perf: untyped typed-array element access ~6x slower (per-access thread-local kind lookup) — bcryptjs cost-12 ~28s vs ~250ms #5525 Blowfish guard) turned a native
    lshr into a js_dynamic_ushr call, because the exactness proof had been
    applied to the last-resort bitwise arm as well. Bitwise operators are
    ToInt32-wrapped by definition and cannot leave the exact range; that arm is
    where bcryptjs's S[l >>> 24] lands with an untyped operand, and it must
    stay proof-free.

Red then green

test-files/test_gap_7232_i32_chain_double_rounding.ts — the issue's three
shapes (straight-line, loop-carried, through-a-call), every ToInt32-shaped
consumer, compound assignment, the unmasked product, a full LCG run, the
2^53 boundary from both sides, and the chains that must stay exact
(h * 31 + c, FNV-1a via Math.imul, 16×16 masked mixing, i * cols + j
indices, i * i).

  • Unfixed main: 4 lines diverge from node --experimental-strip-types 26.5.1.
  • Fixed: byte-identical.

Fourteen unit tests in crates/perry-codegen/src/expr/i32_fast_path/bits_tests.rs
pin the bound itself, and are sabotage-checked both ways: widening
F64_EXACT_INTEGER_BITS past 53 reds the four divergence tests; deleting the
BitAnd/shift/const tightening reds the four exactness tests.

Gates

  • cargo test -p perry-codegen --lib — 540 pass.
  • Gap suite, oracle-exact against the pinned Node.
  • compiler_output_regression.py census --gate — green, no floor touched, no
    --update.
  • cargo fmt --all; file-size cap offender list unchanged (16), and no file in
    this diff is on it.
  • No GC-adjacent codegen touched, so the GC matrix does not apply — the diff is
    confined to expr/i32_fast_path.rs plus the mechanical argument threading at
    its call sites.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed incorrect results caused by double rounding in chained 32-bit integer arithmetic.
    • Improved accuracy near the 2^53 precision boundary across arithmetic, loops, assignments, indexing, and function calls.
    • Preserved exact behavior for bitwise operations and Math.imul.
  • Performance

    • Expanded optimized integer handling for constant numeric values in arrays, buffers, typed arrays, collections, and related operations.
  • Tests

    • Added comprehensive regression and boundary coverage for arithmetic precision and integer fast paths.

Ralph Küpper added 4 commits August 2, 2026 08:06
`(x * 1103515245 + 12345) & 0x7fffffff` — an LCG step — printed 654583775
under Perry and 654583808 under Node. The i32-native fast path evaluated the
whole chain in exact two's-complement `mul/add i32`; ECMAScript evaluates it
in doubles, rounding at every operator. The ~2^61 product is past 2^53, so the
double had already discarded the low bits the exact chain still carried, and
the mask read them straight back.

The old admission rule required only that every integer *literal* fit in i32.
That is neither necessary (`Math.imul` is defined as an exact low-32 multiply)
nor sufficient: 1103515245 fits, and its product with an i32-range local does
not. Replace it with a magnitude bound carried through the whole chain —
`i32_chain_magnitude_bits` — capped at 2^53, the largest integer a double
represents exactly. Below the cap the JS double IS the exact integer and
`low32(exact) == ToInt32(double)`; above it the two models diverge, so the
chain falls onto the f64 path whose `fmul`/`fadd` round where the spec says to.

Both emitters of the chain carry the proof, so the gate and the last-resort
arithmetic arm in `lower_expr_native_i32` cannot drift apart.

Claude-Session: https://claude.ai/code/session_018ZFER8EEg8K7ez2n6oDrT9
Both directions: widening F64_EXACT_INTEGER_BITS past 53 reds the four
divergence tests, and deleting the BitAnd/shift tightening reds the four
exactness tests. Named in the module doc so the sabotage is reproducible.

Claude-Session: https://claude.ai/code/session_018ZFER8EEg8K7ez2n6oDrT9
…und (#7232)

The 2^53 cap costs `buf[y * WIDTH + x]` its exact path when WIDTH is a
`const` binding: the local reads as the 32-bit default, so 33 + 32 = 65 bits
and the whole strided index falls to f64. Measured on
benchmarks/suite/bench_int_arithmetic.ts, where the convolution index halved
its `mul i32` count (108 -> 54).

A `const` bound to a numeric literal has an exactly-known magnitude, so use
it: 33 + 7 = 40 keeps the index exact. The tightening never widens past 32 —
the chain reads the local's i32 slot, so what it computes with is
ToInt32-shaped whatever the literal was.

Claude-Session: https://claude.ai/code/session_018ZFER8EEg8K7ez2n6oDrT9
#7232)

The proof added to `lower_expr_native_i32`'s last-resort arm covered every
i32-chain operator, including the bitwise ones. That arm is not dead code: it
is where bcryptjs's `S[l >>> 24]` lands, with an untyped-but-ToInt32-consumed
operand and no i32-chain proof to be had. Routing it through `lower_expr`
turned a native `lshr` into a `js_dynamic_ushr` call — measured as an IR
regression on benchmarks/suite/bench_typed_array_untyped_access.ts, the #5525
Blowfish guard.

Bitwise operators are ToInt32-wrapped by definition and cannot leave the
double's exact range, so they need no proof. Scope the guard to Add/Sub/Mul,
which is exactly the class that can. With this, the only benchmark whose IR
still moves is 11_prime_sieve, whose `j = i * i` loop preheader is genuinely
unbounded and must round.

Claude-Session: https://claude.ai/code/session_018ZFER8EEg8K7ez2n6oDrT9
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The i32 fast path now tracks expression magnitude through the exact 2^53 boundary. It propagates constant-number locals across lowering paths and uses floating-point arithmetic when exactness is unproven. Tests cover arithmetic chains, loops, calls, masks, shifts, and boundary cases.

Changes

i32 exactness

Layer / File(s) Summary
Magnitude-aware i32 eligibility
crates/perry-codegen/src/expr/i32_fast_path.rs
The analysis tracks magnitude bounds, handles Math.imul, and preserves native bitwise lowering. Unproven Add, Sub, and Mul expressions use floating-point lowering with ToInt32.
Constant-local fact propagation
crates/perry-codegen/src/expr/{arrays_finds,bigint_set,buffer_access,channel,math_simple,proven_view_access}.rs, crates/perry-codegen/src/lower_call/func_ref.rs, crates/perry-codegen/src/stmt/let_stmt.rs
Index, collection, channel, clamp, view, buffer, array, and local-initialization checks now pass constant-number facts to i32 eligibility analysis.
Magnitude and double-rounding regression coverage
crates/perry-codegen/src/expr/i32_fast_path/bits_tests.rs, test-files/test_gap_7232_i32_chain_double_rounding.ts, changelog.d/7237-i32-chain-double-rounding.md
Tests cover exactness limits, masks, shifts, byte loads, constants, Math.imul, unknown locals, arithmetic chains, loops, calls, and boundary inputs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Expression
  participant I32Analysis as i32 fast-path analysis
  participant NativeLowering as native i32 lowering
  participant FloatLowering as floating-point lowering
  Expression->>I32Analysis: provide expression and constant-local facts
  I32Analysis-->>NativeLowering: approve exact i32 chain
  I32Analysis-->>FloatLowering: reject unproven arithmetic chain
  FloatLowering->>FloatLowering: evaluate Add, Sub, or Mul as f64
  FloatLowering->>NativeLowering: apply ToInt32 for the consumer
Loading

Possibly related issues

  • PerryTS/perry#7238: Both issues address double-rounding behavior in integer-specialized arithmetic, but this change targets i32 lowering while #7238 targets i64 function specialization.

Possibly related PRs

  • PerryTS/perry#6844: Both changes modify region-scoped i32 lowering and arithmetic eligibility analysis.
  • PerryTS/perry#6850: Both changes modify i32_fast_path.rs, including arithmetic chains, Math.imul, and typed-array operations.
  • PerryTS/perry#6898: Both changes modify the i32 local-lowering path in let_stmt.rs.

Suggested labels: parity

Suggested reviewers: andrewtdiz, thehypnoo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #7232 and test straight-line, loop-carried, and function-call arithmetic parity cases.
Out of Scope Changes check ✅ Passed All changes support the i32-chain precision fix, including codegen updates, regression tests, benchmarks, and the changelog entry.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title clearly identifies the i32-chain arithmetic rounding fix and matches the primary changes.
Description check ✅ Passed The description thoroughly covers the bug, implementation, related issue, regression tests, benchmarks, and verification results, despite omitting template headings and checkboxes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/7232-i32-mul-precision

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
crates/perry-codegen/src/expr/buffer_access.rs (1)

204-239: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the discarded eligibility checks in lower_index_i32_value and lower_value_i32.

Both functions compute can_lower_expr_as_i32 and then run the same expression in both branches. The result is never used. This PR makes the wasted call more expensive: can_lower_expr_as_i32 now walks the whole expression tree to compute a magnitude bound instead of performing a shape check.

Either drop the checks, or route the false branch to the intended fallback lowering.

♻️ Proposed cleanup if the branches are genuinely identical
 fn lower_index_i32_value(ctx: &mut FnCtx<'_>, index: &Expr) -> Result<LoweredValue> {
-    let value = if can_lower_expr_as_i32(
-        index,
-        &ctx.i32_counter_slots,
-        ctx.flat_const_arrays,
-        &ctx.array_row_aliases,
-        ctx.native_facts.integer_locals(),
-        &ctx.const_number_locals,
-        ctx.clamp3_functions,
-        ctx.clamp_u8_functions,
-        ctx.integer_returning_functions,
-        ctx.i32_identity_functions,
-    ) {
-        lower_expr_native(ctx, index, crate::native_value::ExpectedNativeRep::I32)?.value
-    } else {
-        lower_expr_native(ctx, index, crate::native_value::ExpectedNativeRep::I32)?.value
-    };
+    let value = lower_expr_native(ctx, index, crate::native_value::ExpectedNativeRep::I32)?.value;
     Ok(LoweredValue::i32(value))
 }

 fn lower_value_i32(ctx: &mut FnCtx<'_>, value: &Expr) -> Result<String> {
-    if can_lower_expr_as_i32(
-        value,
-        &ctx.i32_counter_slots,
-        ctx.flat_const_arrays,
-        &ctx.array_row_aliases,
-        ctx.native_facts.integer_locals(),
-        &ctx.const_number_locals,
-        ctx.clamp3_functions,
-        ctx.clamp_u8_functions,
-        ctx.integer_returning_functions,
-        ctx.i32_identity_functions,
-    ) {
-        Ok(lower_expr_native(ctx, value, crate::native_value::ExpectedNativeRep::I32)?.value)
-    } else {
-        Ok(lower_expr_native(ctx, value, crate::native_value::ExpectedNativeRep::I32)?.value)
-    }
+    Ok(lower_expr_native(ctx, value, crate::native_value::ExpectedNativeRep::I32)?.value)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/expr/buffer_access.rs` around lines 204 - 239,
Remove the unused can_lower_expr_as_i32 checks from lower_index_i32_value and
lower_value_i32, since both branches currently call identical lower_expr_native
logic with ExpectedNativeRep::I32. Preserve the existing lowering result while
avoiding the unnecessary expression-tree traversal.
crates/perry-codegen/src/expr/i32_fast_path.rs (2)

856-881: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Consider memoizing the magnitude analysis for deep chains.

region_i32_chain_magnitude_bits runs the full ctx-free analysis on the whole subtree at Line 860 before it recurses into the children at Lines 878-879. Each child then repeats the same ctx-free walk over its own subtree. For a chain of N binary nodes the analysis performs O(N^2) work in the worst case. The new fallback arm in lower_expr_native_i32 (Line 1454) calls this function once per arithmetic node during lowering, which compounds the cost.

This affects compile time only, and typical chains are shallow. If long generated arithmetic chains appear in real inputs, add a per-lowering memo keyed by expression pointer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/expr/i32_fast_path.rs` around lines 856 - 881,
Memoize region_i32_chain_magnitude_bits results per lowering using the
expression pointer as the key, and consult/store the cache across recursive
calls and lower_expr_native_i32 fallback invocations. Thread the memo through
the existing analysis context or lowering state without changing the computed
magnitude results.

489-509: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the call gate; the arity check is currently bypassable.

The two conditions do not enforce arity for a function that is in both clamp3_fns and integer_returning_fns. Such a call with two arguments passes the first check through integer_returning_fns, and passes the second check because clamp3_fns.contains(fid) is true. The result stays correct, because try_lower_expr_native_i32_structural and the Expr::Call arm of lower_expr_native_i32 both re-check the arity and fall back to lower_expr plus fptosi. Only the gate intent is unclear.

Collapse both checks into the single admission rule they describe.

♻️ Proposed simplification
-            if !((env.clamp3_fns.contains(fid) && args.len() == 3)
-                || (env.clamp_u8_fns.contains(fid) && args.len() == 1)
-                || env.integer_returning_fns.contains(fid))
-            {
-                return None;
-            }
-            if env.integer_returning_fns.contains(fid)
-                && !env.clamp3_fns.contains(fid)
-                && !env.clamp_u8_fns.contains(fid)
-                && !env.i32_identity_fns.contains(fid)
-            {
-                return None;
-            }
+            // Admitted callees: a clamp helper at its exact arity, or an
+            // i32-identity helper. A plain integer-returning function is not
+            // enough — its result is not proven ToInt32-shaped here.
+            if !((env.clamp3_fns.contains(fid) && args.len() == 3)
+                || (env.clamp_u8_fns.contains(fid) && args.len() == 1)
+                || env.i32_identity_fns.contains(fid))
+            {
+                return None;
+            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/expr/i32_fast_path.rs` around lines 489 - 509, In
the Expr::Call branch, replace the two-stage function-membership checks with one
admission rule that requires each recognized function category to have its
corresponding arity, while preserving integer-returning identity functions as
valid calls. Ensure a function appearing in multiple sets cannot bypass the
clamp3 or clamp_u8 arity requirements; leave the existing argument magnitude
validation unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@changelog.d/7237-i32-chain-double-rounding.md`:
- Around line 38-42: Correct the test-count reference in the changelog fragment
from twelve to fourteen, leaving the surrounding coverage description unchanged.

In `@crates/perry-codegen/src/expr/i32_fast_path/bits_tests.rs`:
- Around line 276-279: Correct the comment above the idx construction to
describe (x + -1) * K + (y + -1), identify K as the inner-product width, and
explain that the intermediate bound is 40 while the outer addition yields 41. Do
not change the expression or assertion in the bits test.

---

Nitpick comments:
In `@crates/perry-codegen/src/expr/buffer_access.rs`:
- Around line 204-239: Remove the unused can_lower_expr_as_i32 checks from
lower_index_i32_value and lower_value_i32, since both branches currently call
identical lower_expr_native logic with ExpectedNativeRep::I32. Preserve the
existing lowering result while avoiding the unnecessary expression-tree
traversal.

In `@crates/perry-codegen/src/expr/i32_fast_path.rs`:
- Around line 856-881: Memoize region_i32_chain_magnitude_bits results per
lowering using the expression pointer as the key, and consult/store the cache
across recursive calls and lower_expr_native_i32 fallback invocations. Thread
the memo through the existing analysis context or lowering state without
changing the computed magnitude results.
- Around line 489-509: In the Expr::Call branch, replace the two-stage
function-membership checks with one admission rule that requires each recognized
function category to have its corresponding arity, while preserving
integer-returning identity functions as valid calls. Ensure a function appearing
in multiple sets cannot bypass the clamp3 or clamp_u8 arity requirements; leave
the existing argument magnitude validation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 26e6ab1d-878e-423a-a217-02531d027b40

📥 Commits

Reviewing files that changed from the base of the PR and between c9cd73b and a1f2ef6.

📒 Files selected for processing (12)
  • changelog.d/7237-i32-chain-double-rounding.md
  • crates/perry-codegen/src/expr/arrays_finds.rs
  • crates/perry-codegen/src/expr/bigint_set.rs
  • crates/perry-codegen/src/expr/buffer_access.rs
  • crates/perry-codegen/src/expr/channel.rs
  • crates/perry-codegen/src/expr/i32_fast_path.rs
  • crates/perry-codegen/src/expr/i32_fast_path/bits_tests.rs
  • crates/perry-codegen/src/expr/math_simple.rs
  • crates/perry-codegen/src/expr/proven_view_access.rs
  • crates/perry-codegen/src/lower_call/func_ref.rs
  • crates/perry-codegen/src/stmt/let_stmt.rs
  • test-files/test_gap_7232_i32_chain_double_rounding.ts

Comment thread changelog.d/7237-i32-chain-double-rounding.md
Comment thread crates/perry-codegen/src/expr/i32_fast_path/bits_tests.rs Outdated
… and the fragment's test count (#7237)

CodeRabbit, PR #7237:

* `lower_index_i32_value` / `lower_value_i32` in expr/buffer_access.rs branched
  on `can_lower_expr_as_i32` into two identical arms, so the predicate's answer
  was computed and thrown away. `lower_expr_native` makes the same decision
  internally; the outer call was pure cost, and this PR turned it into a
  whole-subtree walk. Verified IR-neutral across the typed-array/buffer
  benchmarks.
* The const-literal test comment had the operands swapped and quoted the inner
  product's width rather than the asserted one.
* The changelog fragment said twelve unit tests; there are fourteen.

Claude-Session: https://claude.ai/code/session_018ZFER8EEg8K7ez2n6oDrT9
@proggeramlug
proggeramlug merged commit f8f1e71 into main Aug 2, 2026
8 of 9 checks passed
@proggeramlug
proggeramlug deleted the fix/7232-i32-mul-precision branch August 2, 2026 07:01
proggeramlug added a commit that referenced this pull request Aug 2, 2026
…#7242)

* fix(codegen): remove the unproven i64 function specialization (#7238)

`emit_i64_specializations` re-emitted a whole `number`-typed function body in
i64 arithmetic and wrapped it in an f64 shim that `fptosi`d every argument and
`sitofp`d the result. Two independent halves of its contract were unchecked:

  * overflow — i64 add/sub/mul are exact, JS rounds to the nearest double at
    every operator, so the two agree only while every intermediate satisfies
    |v| <= 2^53;
  * argument truncation — a `number` parameter is a double, and the wrapper's
    `fptosi double %arg to i64` truncated a fractional one on entry.

Neither is statically provable for the self-recursive bodies the pass existed
to serve: a parameter fed by its own recursive-call argument has no bound, so
#7237's `i32_chain_magnitude_bits` has no bounded leaf to measure from. Even
the motivating example diverges (`fib(79)` crosses 2^53). The pass also
suppressed the sound replacements — `typed_f64`/`typed_i32`/`typed_i1` clones
and the Phase-2 specialized ABI were all retained minus the i64-specialized
set — so removing it hands those functions to a specializer that proves what
this one assumed.

Claimed 2 of the 30 `benchmarks/suite/` programs (`05_fibonacci`, `14_closure`).

Fixes #7238.

* docs: changelog fragment for #7238

* test(codegen): drop a vacuous disjunct from the no-i64-body assertion

* test(codegen): make the no-entry-truncation checks naming-independent

CodeRabbit review on #7242. Three assertions matched the literal
`fptosi double %arg`, which couples them to how parameters happen to be
named in the emitted IR. Match on the opcode alone, scoped to the public
function body, so a rename cannot turn them vacuous — none of these fixtures
has another reason to narrow a double to an integer. Also renames the
`native_proof_regressions` fixture to `number_add_module`, since it no longer
describes an i64-specialization collision.

---------

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

number arithmetic in a local is evaluated past double precision — (x * 1103515245 + 12345) & 0x7fffffff diverges from Node

1 participant