Skip to content

perf(codegen): reach integer modulo fast path for i32 counters#6388

Merged
proggeramlug merged 6 commits into
mainfrom
perf/value-first-i32-modulo
Jul 14, 2026
Merged

perf(codegen): reach integer modulo fast path for i32 counters#6388
proggeramlug merged 6 commits into
mainfrom
perf/value-first-i32-modulo

Conversation

@TheHypnoo

@TheHypnoo TheHypnoo commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

  • defer signed i32 loop-counter modulo with a strictly positive Expr::Integer divisor to the existing integer remainder lowering
  • preserve floating remainder lowering for integral Expr::Number, unsafe or dynamic divisors, unsigned counters, and non-i32 operands
  • add emitted-IR contracts and Node parity coverage for JavaScript remainder edge cases
  • register the existing ws.readyState dispatch entry in the API manifest

Performance

Paired Perry before/after gate

Five runs per build on the same machine using fresh no-cache binaries for benchmarks/suite/13_factorial.ts:

Perry metric Before After Change
Samples 1579, 1559, 1542, 1543, 1543 ms 99, 134, 97, 108, 115 ms
Median 1543 ms 108 ms -93.0%
After / before 1.000 0.070 14.3× faster
Hot remainder IR frem double srem i64 Integer fast path reached
Hot native remainder fmod vectorized umull / ushr / mls No _fmod reference in the benchmark object
Result sum:49950000000 ×5 sum:49950000000 ×5 Exact parity

Interleaved runtime comparison

To reduce scheduler and thermal noise, each runtime was warmed up three times and then measured in 25 rotating, interleaved rounds on the same machine:

Runtime Median MAD Range Relative to Node Result
Perry after 97 ms 1 ms 96–145 ms 0.98× sum:49950000000 ×25
Node 24.16.0 99 ms 1 ms 98–163 ms 1.00× sum:49950000000 ×25
Bun 1.2.18 99 ms 1 ms 98–138 ms 1.00× sum:49950000000 ×25

This reproduces the public PR #6376 starting point, which reported Perry at 1,555 ms versus 99 ms for Node and Bun. With the integer path reachable, Perry's robust median is now approximately 2% faster than both local references; there is no remaining repeatable factorial gap to chase.

Validation

  • cargo fmt --all -- --check
  • cargo check -p perry-codegen
  • cargo test -p perry-codegen --test native_proof_regressions integer_modulo:: — 3 passed
  • cargo test -p perry-codegen --test manifest_consistency — 5 passed
  • ./run_parity_tests.sh --filter test_gap_numeric_remainder_i32 — 1 passed
  • paired five-run factorial benchmark — 1,543 ms to 108 ms median
  • 25-round interleaved Perry/Node/Bun comparison — 97 / 99 / 99 ms medians
  • ./benchmarks/compare.sh --quick --runs 5 --json-out /tmp/perry-i32-modulo-quick.json --warn-only — all five correctness checks passed

Existing unrelated failures

The full cargo test -p perry-codegen run still encounters existing failures in native_owned_uint8array_get_fallback_uses_uint8array_helper and pod_field_read_after_dynamic_materialization_uses_number_coerce; neither path is modified by this pull request.

Summary by CodeRabbit

  • New Features
    • Added support for reading a WebSocket’s readyState, and updated the supported API reference.
  • Bug Fixes
    • Improved % code generation to route specific signed i32 counter-slot cases with strictly positive i32 literal divisors through the correct integer remainder path, preserving JavaScript remainder behavior (including -0) and avoiding an unwanted floating path.
  • Documentation
    • Updated the auto-generated “Supported API Reference” statistics and ws method listings.
  • Tests
    • Added integer modulo regression coverage, updated existing native proof expectations, and introduced a standalone numeric remainder edge-case test script.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The changes register WebSocket readyState metadata, specialize modulo code generation for signed i32 counters with positive literal divisors, and add regression coverage for supported and unsupported numeric remainder cases.

Changes

Integer modulo lowering

Layer / File(s) Summary
Integer modulo lowering guard
crates/perry-codegen/src/expr/mod.rs
Signed i32_counter_slots modulo positive integer literals now bypasses generic floating modulo lowering.
Modulo regression coverage
crates/perry-codegen/tests/native_proof_regressions.rs, crates/perry-codegen/tests/native_proof_regressions/integer_modulo.rs, test-files/test_gap_numeric_remainder_i32.ts
Tests cover integer remainder with negative-zero repair, floating remainder for unsupported operand shapes, updated dynamic numeric subtraction lowering, and numeric edge cases.

WebSocket manifest metadata

Layer / File(s) Summary
WebSocket.readyState manifest entry
crates/perry-api-manifest/src/entries/part_1.rs, docs/src/api/reference.md
Registers WebSocket.readyState as a receiver-bound method returning Any and adds it to the generated API reference.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: proggeramlug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: enabling the integer modulo fast path for i32 counters.
Description check ✅ Passed The description covers the summary, performance, and validation, though it omits the template's Changes and Related issue sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 perf/value-first-i32-modulo

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.

🧹 Nitpick comments (1)
crates/perry-codegen/tests/native_proof_regressions/integer_modulo.rs (1)

1-145: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider adding unit test coverage for CI enforcement.

Per the coding guideline, acceptance coverage should be in unit tests visible to cargo-test because integration suites under crates/*/tests/*.rs do not run on every PR. These regression tests are in an integration test file and may not run in CI on every PR, leaving the optimization without enforced coverage. Consider adding equivalent #[cfg(test)] unit tests in src/ or migrating these tests to a unit test module.

As per coding guidelines: "Put acceptance coverage in unit tests visible to cargo-test, because integration suites under crates/*/tests/*.rs do not run on every PR."

🤖 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/tests/native_proof_regressions/integer_modulo.rs` around
lines 1 - 145, Move the regression coverage from the integration test module
into a #[cfg(test)] unit-test module under src, or add equivalent unit tests
there, so cargo-test executes it on every PR. Preserve the existing helpers and
assertions from factorial_shaped_ir,
assert_integer_modulo_with_negative_zero_repair, and assert_floating_modulo,
including coverage for eligible literals, unsafe or dynamic divisors, floating
operands, and unsigned operands.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@crates/perry-codegen/tests/native_proof_regressions/integer_modulo.rs`:
- Around line 1-145: Move the regression coverage from the integration test
module into a #[cfg(test)] unit-test module under src, or add equivalent unit
tests there, so cargo-test executes it on every PR. Preserve the existing
helpers and assertions from factorial_shaped_ir,
assert_integer_modulo_with_negative_zero_repair, and assert_floating_modulo,
including coverage for eligible literals, unsafe or dynamic divisors, floating
operands, and unsigned operands.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d8e79bba-bb56-45e9-ae1b-1406e4cfee1f

📥 Commits

Reviewing files that changed from the base of the PR and between cd6091d and 879820b.

📒 Files selected for processing (5)
  • crates/perry-api-manifest/src/entries/part_1.rs
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/tests/native_proof_regressions.rs
  • crates/perry-codegen/tests/native_proof_regressions/integer_modulo.rs
  • test-files/test_gap_numeric_remainder_i32.ts

@proggeramlug

Copy link
Copy Markdown
Contributor

Thanks for this — the captured/parameter asymmetry is a real win. One blocker before it can land, and it's a correctness one rather than a style nit.

What's failing

e2e-scopedperry-codegen --test native_proof_regressions:

pod_field_read_after_dynamic_materialization_uses_number_coerce ... FAILED
  panicked at crates/perry-codegen/tests/native_proof_regressions.rs:1348
  POD field reads after dynamic materialization must not feed boxed JSValue
  fallbacks into raw numeric arithmetic

The test asserts the emitted IR still contains call double @js_number_coerce. On this branch it doesn't.

Why that assertion exists

The shape it guards is local.tag - 1, where local was dynamically materialized — so PropertyGet returns a boxed JSValue, not a proven number. In the test the field is literally assigned the string "x" first. Without the js_number_coerce, the NaN-boxed string pointer is fed straight into raw fsub — the arithmetic operates on the pointer bits and produces garbage. Silently.

So this isn't the fast path being too fast; it's the fast path being taken on a value that hasn't been proven numeric.

Where to look

Your change makes the numeric specialization survive through a capture. The likely cause is that the "is this operand statically numeric?" predicate now answers yes for a value that only looks numeric because it came through the newly-specialized path — the specialization is being treated as a type proof, when it's a representation choice. A dynamically-materialized POD field read has to stay boxed until coerced, regardless of how the array is reached.

Worth checking whether the same over-trust reaches other operand kinds — anywhere a boxed fallback can now skip its coerce.

Note on the gate

e2e-scoped is new (#6338): it runs the integration suites a PR's diff names, which per-PR CI never did before. This is the first regression it has caught — the old path would have let it through to the nightly.

Happy to look further if you want a second pair of eyes on the predicate.

@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: 1

🤖 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 `@crates/perry-codegen/tests/native_proof_regressions.rs`:
- Line 1320: Move
pod_field_read_after_dynamic_materialization_uses_dynamic_numeric_sub and the
newly added integer_modulo coverage from crates/*/tests integration files into a
#[cfg(test)] unit-test module under the relevant src/ directory, preserving
their assertions and setup so cargo test discovers them on every PR.
🪄 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: 553e2788-c264-469f-995f-bb34dfe4741c

📥 Commits

Reviewing files that changed from the base of the PR and between cf5a29b and 2d48b7a.

📒 Files selected for processing (1)
  • crates/perry-codegen/tests/native_proof_regressions.rs


#[test]
fn pod_field_read_after_dynamic_materialization_uses_number_coerce() {
fn pod_field_read_after_dynamic_materialization_uses_dynamic_numeric_sub() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move acceptance coverage to unit tests.

As per coding guidelines, put acceptance coverage in unit tests visible to cargo-test, because integration suites under crates/*/tests/*.rs do not run on every PR. Please move this regression test (and any other newly added coverage, such as the integer_modulo tests) into a #[cfg(test)] unit test module within the src/ directory to ensure they are reliably executed in CI.

🤖 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/tests/native_proof_regressions.rs` at line 1320, Move
pod_field_read_after_dynamic_materialization_uses_dynamic_numeric_sub and the
newly added integer_modulo coverage from crates/*/tests integration files into a
#[cfg(test)] unit-test module under the relevant src/ directory, preserving
their assertions and setup so cargo test discovers them on every PR.

Source: Coding guidelines

@proggeramlug proggeramlug merged commit 6e3ba64 into main Jul 14, 2026
26 checks passed
@proggeramlug proggeramlug deleted the perf/value-first-i32-modulo branch July 14, 2026 21:17
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.

2 participants