Skip to content

perf(codegen): #6794 follow-up (a) — region-scoped i32 chains in the ta_i32 masked-window copy#6844

Merged
proggeramlug merged 2 commits into
mainfrom
perf/6750-untyped-param-array-hoist
Jul 26, 2026
Merged

perf(codegen): #6794 follow-up (a) — region-scoped i32 chains in the ta_i32 masked-window copy#6844
proggeramlug merged 2 commits into
mainfrom
perf/6750-untyped-param-array-hoist

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

#6794 follow-up (a): region-scoped i32 chains

PR #6794 made untyped-param masked-window array reads fast (loop + straight-line
region versioner) and left two open follow-ups on the residual ~15× on bcryptjs's
unrolled _encipher. This is (a): region-scoped i32 chains.

Problem

The region versioner (stmt/masked_window_region.rs) refines untyped-init numeric
locals to Type::Number (f64) in every fast copy. bcryptjs inits its Feistel halves
from a dynamic read — l = lr[off] — which is_strictly_i32_bounded_expr rejects, so
l/r never earn a static i32 slot. In the ta_i32 fast copy the masked array
reads are already native i32, but each l >>> 24 / l & 0xff in the round still
loads l as f64 and pays a branchless ToInt32 tower per op.

Fix

In the ta_i32 copy only, bind a local whose every in-region write is
strictly-i32-bounded
(bitwise / | 0 / Math.imul) and that is never un-refined
to a region-scoped i32 shadow slot (i32_counter_slots) at its refinement point. The
existing LocalSet i32 path then keeps the whole >>>/&/^/| 0 chain in native
i32 and maintains the double shadow. The slot is removed at copy end, so the
plain_f64 / slow copies (which share ctx.i32_counter_slots and whose reads are
f64) are unaffected. Soundness: an empty oracle keeps only truly-i32 writes (copies
excluded); the double shadow carries the correct signed value out for post-region reads.

Measured

  • Unoptimized ta_i32 copy for the Blowfish-F shape: 186 ToInt32-tower selects → 0.
  • LLVM -O already folds ~74% of those towers on its own (sitofp(i32)→ToInt32
    round-trips); removing the residual is worth ~11% on bcryptjs.compareSync
    (Int32Array S-boxes → the ta_i32 tier). A real, free win on crypto/codec workloads;
    the larger remaining gap is the per-call region probe + shadow-slot ops (follow-up b).

Correctness

New gap test test_gap_region_masked_window_i32_chain.ts — the round shape,
overflow-wrapping, post-region full-Number reads, an un-refine on a fractional write,
and the plain-Array tier — all byte-identical to Node. Also verified against the
existing i32-overflow / toint32 / typed-array / crypto gap suite and #6794's own
untyped-masked-window matrix.

Summary by CodeRabbit

  • Performance

    • Improved execution of certain i32 bit-mixing workloads, including typed-array-based cryptographic operations, with measured gains of approximately 11% in bcryptjs.compareSync.
    • Preserved existing behavior for plain-array inputs and slower execution paths.
  • Bug Fixes

    • Improved handling of 32-bit overflow, signed/unsigned conversions, and values read after optimized regions.
  • Tests

    • Added coverage for i32 chains, overflow behavior, refinement changes, post-region reads, and plain-array scenarios.

Ralph Küpper added 2 commits July 26, 2026 10:10
…ta_i32 masked-window copy

The straight-line masked-window region versioner (masked_window_region.rs)
refines untyped-init numeric locals to Type::Number (f64) in every fast copy.
bcryptjs _encipher inits its Feistel halves from a dynamic read (l = lr[off]),
which is_strictly_i32_bounded_expr rejects, so l/r never earn a static i32 slot
and every l>>>24 / l&0xff in the round pays a branchless ToInt32 tower.

In the ta_i32 fast copy — where the masked array reads are already native i32 —
bind such locals to a region-scoped i32 shadow slot (i32_counter_slots) at their
refinement point, so the whole >>>/&/^/|0 bit-mixing chain lowers to native i32
(tower-free) and the double slot is maintained by every write via the existing
LocalSet i32 path. The slot is removed at copy end so the plain_f64 / slow copies
(which share ctx.i32_counter_slots and whose reads are f64) are unaffected.

A local qualifies only if EVERY in-region LocalSet to it is strictly-i32-bounded
(bitwise / |0 / Math.imul — a true signed i32; empty oracle, so copies are
conservatively excluded) AND it is never un-refined, so every write maintains the
slot and the value is always an in-range i32. Disabled for the plain_f64 tier,
where an i32 slot would be maintained by no write.

Unoptimized ta_i32 copy for the Blowfish-F shape drops from 186 ToInt32-tower
selects to 0. LLVM -O already folds ~74% of those towers on its own; removing the
residual is worth ~11% on bcryptjs.compareSync (Int32Array S-boxes -> ta_i32 tier).

New gap test covers the round shape, overflow-wrapping, post-region full-Number
reads, an un-refine on a fractional write, and the plain-Array tier — all
byte-identical to Node.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds region-scoped i32 shadow slots for eligible ta_i32 fast-copy locals, preserves cleanup across copy tiers, and adds typed-array and plain-array tests for i32 mixing, overflow, post-region reads, and unrefinement.

Changes

Masked-window i32 specialization

Layer / File(s) Summary
Identify eligible i32 refinements
crates/perry-codegen/src/stmt/masked_window_region.rs, crates/perry-codegen/src/collectors/mod.rs
Region refinement tracks eligible strictly-i32 locals, excluding locals with updates or unrefined writes.
Bind and clean up typed-tier shadows
crates/perry-codegen/src/stmt/masked_window_region.rs, changelog.d/6844-region-i32-chains.md
The ta_i32 copy binds scoped i32 shadow slots and removes them at finalization; plain_f64 and slow copies disable the behavior.
Exercise typed and plain execution
test-files/test_gap_region_masked_window_i32_chain.ts
Tests cover bit-mixing, overflow, signed/unsigned reads, mid-region unrefinement, and plain-array execution over deterministic iterations.

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

Sequence Diagram(s)

sequenceDiagram
  participant RegionMatcher
  participant lower_region_copy
  participant i32_counter_slots
  RegionMatcher->>lower_region_copy: pass eligible as_i32 refinements
  lower_region_copy->>i32_counter_slots: bind scoped i32 shadow slots
  lower_region_copy->>i32_counter_slots: remove bound slots after copy
Loading

Possibly related PRs

  • PerryTS/perry#6794: Introduces the masked-window region fast-copy structure extended here for ta_i32 shadow-slot refinement.

Suggested reviewers: thehypnoo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main masked-window ta_i32 i32-chain optimization.
Description check ✅ Passed The description is detailed and covers the problem, fix, measurements, and correctness, though it omits the template's explicit Summary/Changes/Test plan headings.
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/6750-untyped-param-array-hoist

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: 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/src/stmt/masked_window_region.rs`:
- Around line 255-276: Update the write scan in the masked-window promotion
logic to recursively traverse expression children, matching the traversal used
by the existing strict-write collector. Ensure nested LocalSet and Update
operations are added to written/disqualified using the same strict i32-bounded
validation, so any nested non-strict write prevents as_i32 promotion.
🪄 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: c8556d62-8119-4f7c-a99c-13ff4684a708

📥 Commits

Reviewing files that changed from the base of the PR and between 497f64e and 006b308.

📒 Files selected for processing (4)
  • changelog.d/6844-region-i32-chains.md
  • crates/perry-codegen/src/collectors/mod.rs
  • crates/perry-codegen/src/stmt/masked_window_region.rs
  • test-files/test_gap_region_masked_window_i32_chain.ts

Comment on lines +255 to +276
for stmt in stmts {
match stmt {
Stmt::Expr(Expr::LocalSet(id, value)) => {
written.insert(*id);
let strict = crate::collectors::is_strictly_i32_bounded_expr(
value,
&empty,
&empty,
&empty,
&empty,
&mut |_| {},
);
if !strict {
disqualified.insert(*id);
}
}
Stmt::Expr(Expr::Update { id, .. }) => {
disqualified.insert(*id);
}
_ => {}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Recursively inspect writes before promoting an i32 slot.

This scan only sees top-level LocalSet/Update statements. A nested assignment or update inside an RHS is ignored, so a fractional/full-f64 write can leave as_i32 enabled and later reads can use a truncated i32 shadow. Traverse expression children, as the existing strict-write collector does.

Proposed fix
 fn region_i32_bounded_write_locals(stmts: &[Stmt]) -> std::collections::HashSet<u32> {
     let empty = std::collections::HashSet::new();
     let mut written = std::collections::HashSet::new();
     let mut disqualified = std::collections::HashSet::new();

+    fn visit_expr(
+        expr: &Expr,
+        empty: &std::collections::HashSet<u32>,
+        written: &mut std::collections::HashSet<u32>,
+        disqualified: &mut std::collections::HashSet<u32>,
+    ) {
+        match expr {
+            Expr::LocalSet(id, value) => {
+                written.insert(*id);
+                if !crate::collectors::is_strictly_i32_bounded_expr(
+                    value, empty, empty, empty, empty, &mut |_| {},
+                ) {
+                    disqualified.insert(*id);
+                }
+                visit_expr(value, empty, written, disqualified);
+            }
+            Expr::Update { id, .. } => {
+                disqualified.insert(*id);
+            }
+            _ => perry_hir::walker::walk_expr_children(expr, &mut |child| {
+                visit_expr(child, empty, written, disqualified);
+            }),
+        }
+    }
+
     for stmt in stmts {
-        match stmt {
-            Stmt::Expr(Expr::LocalSet(id, value)) => { /* ... */ }
-            Stmt::Expr(Expr::Update { id, .. }) => { /* ... */ }
-            _ => {}
+        if let Stmt::Expr(expr) = stmt {
+            visit_expr(expr, &empty, &mut written, &mut disqualified);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for stmt in stmts {
match stmt {
Stmt::Expr(Expr::LocalSet(id, value)) => {
written.insert(*id);
let strict = crate::collectors::is_strictly_i32_bounded_expr(
value,
&empty,
&empty,
&empty,
&empty,
&mut |_| {},
);
if !strict {
disqualified.insert(*id);
}
}
Stmt::Expr(Expr::Update { id, .. }) => {
disqualified.insert(*id);
}
_ => {}
}
}
fn visit_expr(
expr: &Expr,
empty: &std::collections::HashSet<u32>,
written: &mut std::collections::HashSet<u32>,
disqualified: &mut std::collections::HashSet<u32>,
) {
match expr {
Expr::LocalSet(id, value) => {
written.insert(*id);
if !crate::collectors::is_strictly_i32_bounded_expr(
value, empty, empty, empty, empty, &mut |_| {},
) {
disqualified.insert(*id);
}
visit_expr(value, empty, written, disqualified);
}
Expr::Update { id, .. } => {
disqualified.insert(*id);
}
_ => perry_hir::walker::walk_expr_children(expr, &mut |child| {
visit_expr(child, empty, written, disqualified);
}),
}
}
for stmt in stmts {
if let Stmt::Expr(expr) = stmt {
visit_expr(expr, &empty, &mut written, &mut disqualified);
}
}
🤖 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/stmt/masked_window_region.rs` around lines 255 -
276, Update the write scan in the masked-window promotion logic to recursively
traverse expression children, matching the traversal used by the existing
strict-write collector. Ensure nested LocalSet and Update operations are added
to written/disqualified using the same strict i32-bounded validation, so any
nested non-strict write prevents as_i32 promotion.

@proggeramlug
proggeramlug merged commit 19b2b11 into main Jul 26, 2026
29 of 30 checks passed
@proggeramlug
proggeramlug deleted the perf/6750-untyped-param-array-hoist branch July 26, 2026 09:10
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.

1 participant