Skip to content

perf: array read-modify-write in dense loops (#10718); native bitwise on unproven locals (#10511) - #11788

Merged
proggeramlug merged 3 commits into
mainfrom
perf-10718-10511-rmw-bitwise
Oct 3, 2026
Merged

proggeramlug merged 3 commits into
mainfrom
perf-10718-10511-rmw-bitwise

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Closes #10718; partial progress on #10511. Two commits.

Instructions per op

Row main this PR node
arrRmw (a[i] = a[i] + 1; s += a[i]) 206 14.6 39.2
#10511 destructure 212 57 41
#10511 annotated 213 57 –
#10511 prop / bigmask 78 / 186 78 / 186 (open) 9.8 / 43

#10718 (array read-modify-write). A two-statement body fit no loop tier, so every element re-checked the array three times. The dense range tier now admits a[i] = … and a[i ± c] = … when the stored value is provably a plain double. One entry guard covers bounds, holes and element kind for both the read and the write. The add is unboxed and never re-boxed, and a[i] += x takes the same path.

#10511 (bitwise on unproven locals).

  • Unary ~ gets the same inline |v| < 2^63 guard as the binary bitwise ops, with js_dynamic_bitnot moved to the cold arm.
  • New tier stmt/number_local_loop.rs. The loop is versioned on one Number test per local at entry. A fixed point covers every write in the loop, including the condition and update clauses, so it doesn't share the region bug fixed in fix(codegen): region Number proof judges loop condition and update writes #11782. Inside the clone those locals live in f64 slots.

Tests

  • IR tests.
  • Gap tests: NaN, ±Infinity, −0, numeric-looking strings, BigInt TypeErrors, holes, out of bounds, element-kind change, frozen arrays, aliasing, and break/return/throw exits.
  • Moving-GC fixtures under seeded collection at every safepoint with from-space protection.
  • Sabotage: removing the plain-double rule, the condition/update walk or the fixed point each turns a named test red.

Verification

Still open for #10511

  • prop and bigmask: the cost is untyped-receiver property reads in the loop, which need loop-invariant hoisting.
  • Keeping destructured ints in i32 slots would close destructure 57 → about 20.
  • An RMW loop bounded by i < a.length is still about 350 per element.

Overlaps the #10741 lane in stmt/loops.rs (dense mode).

Summary by CodeRabbit

  • Performance Improvements

    • Numeric loops and array read-modify-write operations can now use faster optimized paths in more cases, including loops with multiple statements and offset-based array access.
    • Certain bitwise operations can use native numeric handling when values are in range.
  • Bug Fixes

    • Optimized paths check values before use and fall back to standard handling when inputs or array contents do not meet the required conditions, preserving behavior for non-numeric values, special cases, and errors.

Ralph Küpper added 3 commits October 3, 2026 11:13
…e range tier (#10718)

`a[i] = a[i] + 1; s += a[i]` fell off every loop tier. The classic range
mode takes one statement, because a side exit after a store would replay
the store, and the dense mode, which has no side exits, admitted only
masked stores. The loop ran on the generic path at 206 instructions per
element (node 39).

The dense mode now admits counter-offset stores under the masked-store
rule. The entry guard validates the whole counter window (in bounds,
hole-free, raw-f64, plain, integrity-clean), and the stored value must be
a statically genuine double, so the store has no value check and no side
exit. The read and the write share that one guard, and the add is an
unboxed fadd. The #10743 compound-assignment alias fold now runs over
multi-statement bodies, and accumulator verification uses the lowering's
own array set.

arrRmw: 206 -> 14.6 instr/elem. Adds IR tests, including a
sabotage-verified witness for the genuine-RHS rule, and
test_gap_10718_array_rmw_dense.ts.
… loop clone (#10511)

Unary `~` on an operand with no Number proof now takes the binary bitwise
operators' inline guard (`|v| < 2^63`, with js_dynamic_bitnot on the cold
arm) instead of calling the helper on every evaluation.

New tier stmt/number_local_loop.rs, for the noble SHA-2 shape
`let { A, B, C, D } = this; for (...) { ... ~B ... D = C; ... }`. In a
receiver-free loop, a local is Number at every read when:
- it holds a Number at entry (one inline test);
- every write that can execute in the loop, in the body, the condition
  and the update clause, produces a Number given the admitted locals
  (a greatest fixed point); and
- no closure, await, yield, boxed cell or module global can write it.
The loop then runs in a clone whose 5L Number scope holds those locals,
each in a plain F64 alloca that is written back on exit. When the entry
test fails, the ordinary loop runs.

destructure 212 -> 57 and annotated 213 -> 57 instr/iter (node 41).
Adds IR tests; the clause-walk and fixed-point witnesses are
sabotage-verified. Adds test_gap_10511_number_local_loop.ts.
@proggeramlug proggeramlug added the run-extended-tests Opt PR into compile-smoke/parity/doc-tests/drizzle-mysql-smoke label Oct 3, 2026
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 657bb442-ce56-47fa-8e03-e31d54263e64
📥 Commits

Reviewing files that changed from the base of the PR and between 69de7f3 and 5201e16.

📒 Files selected for processing (15)
  • changelog.d/11788-10511-bitwise.md
  • changelog.d/11788-10718-array-rmw.md
  • crates/perry-codegen/src/expr/binary.rs
  • crates/perry-codegen/src/expr/index_get.rs
  • crates/perry-codegen/src/expr/index_set.rs
  • crates/perry-codegen/src/expr/masked_window.rs
  • crates/perry-codegen/src/expr/unary.rs
  • crates/perry-codegen/src/expr/unary_bitnot_tests.rs
  • crates/perry-codegen/src/stmt/loops.rs
  • crates/perry-codegen/src/stmt/mod.rs
  • crates/perry-codegen/src/stmt/number_local_loop.rs
  • crates/perry-codegen/src/stmt/number_local_loop_tests.rs
  • crates/perry-codegen/src/stmt/range_loop_dense_store_tests.rs
  • test-files/test_gap_10511_number_local_loop.ts
  • test-files/test_gap_10718_array_rmw_dense.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The pull request adds guarded unary bitwise-NOT lowering and a Number-local loop clone. It also extends dense range-loop matching to handle counter-offset stores, compound-assignment aliases, and multiple arrays.

Changes

Guarded Bitwise Operations and Number-Local Loops

Layer / File(s) Summary
Guarded unary bitwise NOT
crates/perry-codegen/src/expr/binary.rs, crates/perry-codegen/src/expr/unary.rs, crates/perry-codegen/src/expr/unary_bitnot_tests.rs
Unary bitwise NOT uses an exact-number guard with inline numeric and dynamic-helper paths. The guard is enabled only when guarded arithmetic and inline non-BigInt bitwise lowering are enabled. A regression test checks the emitted guard.
Number-local loop specialization
crates/perry-codegen/src/stmt/number_local_loop.rs, crates/perry-codegen/src/stmt/loops.rs, crates/perry-codegen/src/stmt/mod.rs, crates/perry-codegen/src/stmt/number_local_loop_tests.rs, test-files/test_gap_10511_number_local_loop.ts, changelog.d/11788-10511-bitwise.md
Eligible loops check candidate locals at entry, use F64 slots in a fast clone, and retain the ordinary loop as fallback. Analysis covers writes in the body, condition, and update. Tests cover admitted and rejected loops, and runtime cases include early exits, nested loops, and BigInt operations.

Dense Array Read-Modify-Write Loops

Layer / File(s) Summary
Packed-store proof checks
crates/perry-codegen/src/expr/index_get.rs, crates/perry-codegen/src/expr/index_set.rs, crates/perry-codegen/src/expr/masked_window.rs
Dense facts permit validated nonzero offsets when the fact is not affine. Counter-indexed reads can qualify as genuine f64 values when they match a non-affine packed-f64 fact and use an i32 counter slot. Dense-copy stores require genuine-f64 proof.
Dense loop matching and validation
crates/perry-codegen/src/stmt/loops.rs, crates/perry-codegen/src/stmt/range_loop_dense_store_tests.rs, test-files/test_gap_10718_array_rmw_dense.ts, changelog.d/11788-10718-array-rmw.md
Dense matching folds eligible compound-assignment aliases, accepts qualified counter-offset stores, and verifies accumulator accesses across multiple arrays. Tests cover accepted and rejected stores, alias folding, and runtime array cases.

Priority: ➖ Normal

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

Change: Feature · Severity of issue fixed: Medium

Suggested reviewers: claude

Merge Risk: ⚪ Minimal · up to 5201e

This change adds guarded bitwise lowering, a Number-local loop clone, and wider dense-array loop handling. No concrete defect was found, and the ordinary-loop and dynamic-helper fallbacks are retained. It is ready to merge, with the added IR and runtime tests as protection.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 5201e

The changes retain runtime checks and fallback behavior around the expanded optimizations. No introduced security defect was established, but incomplete coverage prevents a minimal-risk assessment.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Source programs and their runtime values can select the broadened lowering paths. A failed representation or packed-access invariant could affect the generated executable's memory and computation; the inspected scope does not establish tenant, service, credential, or environment-level exposure.

Trust Boundaries and Controls

  • observed — Nonzero-offset packed stores require a validated non-affine window proof. The widened RHS proof accepts counter-indexed reads only with compatible f64 facts, while the inspected dense runtime guard checks array integrity and window bounds before delegating layout validation.

Resilience and Maintainability Implications

  • observed — Dense-loop alias folding is conservative and the slow clone retains the original body. Store-admitting dense loops use the f64 tier, with genuine-double admission limiting replay hazards from a later value-check side exit.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also adds guarded unary bitwise lowering and a Number-versioned loop tier for unproven locals. Those changes address #10511, which the PR description names, but they have no demonstrated connec… Remove the guarded bitwise and number-local loop changes from this PR, or establish and assess them against an active directly linked issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 76 functions across 13 files. (2 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed #10718 targets the high cost of ordinary Array access, especially read-modify-write. The dense range tier now admits proven-double counter-offset stores in multi-statement loops and reuses the validat…
Title check ✅ Passed The title identifies both main changes: array read-modify-write optimization in dense loops and native bitwise operations on unproven locals. It is specific and related to the changeset.
Description check ✅ Passed The description explains the changes, links issue #10718, identifies partial progress on #10511, and reports tests and verification. It includes the required information, although it does not use all …
Full details: Out of Scope Changes check

Explanation

The PR also adds guarded unary bitwise lowering and a Number-versioned loop tier for unproven locals. Those changes address #10511, which the PR description names, but they have no demonstrated connection to #10718's ordinary Array access objective. The supplied linked-issue evidence contains only #10718; it does not establish #10511 as an active directly linked target.

Full details: Docstring Coverage

Explanation

Docstring coverage is 57.89% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 76 functions across 13 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-extended-tests Opt PR into compile-smoke/parity/doc-tests/drizzle-mysql-smoke

Projects

None yet

1 participant