Skip to content

unroll: guard short-preamble force-loop on unmapped jump arg#556

Merged
youknowone merged 2 commits into
mainfrom
box-pool
Jul 15, 2026
Merged

unroll: guard short-preamble force-loop on unmapped jump arg#556
youknowone merged 2 commits into
mainfrom
box-pool

Conversation

@youknowone

@youknowone youknowone commented Jul 14, 2026

Copy link
Copy Markdown
Owner

One commit porting the upstream inline_short_preamble inner fix-point guard.

The bug

inline_short_preamble's inner force-box loop re-forces short_jump_args until the list stops growing. When force_box grows the list via add_preamble_op with a producer short-op not yet replayed, the newly-added jump arg is absent from mapping; on the immediate re-loop the top-of-loop _map_args panicked (expect("mapping missing jump_arg")), aborting the JIT process instead of deferring.

The fix

Port unroll.py:425-434 (pypy fix 983edc22e589 / gh-5212): after the list grows, break out of the inner loop when any new non-Const entry is not yet in mapping, deferring to the outer replay loop to register it; only re-force when every new entry is a Const or already mapped. The stale comment citing the pre-guard range (417-423) is updated to 417-434.

Verification

  • majit-metainterp lib: dynasm 1396 / cranelift 1394, 0 failed
  • check.py: dynasm 181/181, cranelift 181/181
  • accumulator hot-loop repro: PYRE_JIT=0 oracle == JIT on both backends

commented by Claude

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of overflow-checked operations during optimized loop execution.
    • Fixed short preamble replay behavior when new values are discovered, improving correctness and stability.
  • Performance

    • Reduced repeated computation inside loops by enabling eligible operations to be calculated once and reused.
    • Improved optimization of unrolled loops and peeled preambles for more efficient runtime execution.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Short-preamble optimization now hoists postponed overflow-checked operations and tightens replay fixpoint handling so unmapped non-constant jump arguments are deferred until their producers are registered.

Changes

Short-preamble optimization fixes

Layer / File(s) Summary
Postponed OVF short-preamble registration
majit/majit-metainterp/src/optimizeopt/pure.rs
GUARD_NO_OVERFLOW handling adds postponed overflow operations to the short-preamble pure-operation set alongside the main pure cache.
Short-preamble replay fixpoint guard
majit/majit-metainterp/src/optimizeopt/unroll.rs
Replay rechecks newly added jump arguments and stops when non-constant arguments are not yet present in the mapping. The related comment is updated.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Poem

I’m a bunny with a preamble bright,
Hoisting overflow out of sight.
Mapped hops wait till producers appear,
Replay stays tidy, crisp, and clear.
Nibble, compile, and leap with cheer!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main unroll fix: guarding the short-preamble force-loop when a jump arg is unmapped.
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 box-pool

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.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 367d038).

Files in the reviewed diff
majit/majit-metainterp/src/optimizeopt/pure.rs
majit/majit-metainterp/src/optimizeopt/unroll.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

  • majit/majit-metainterp/src/optimizeopt/pure.rs:989 ↔ rpython/jit/metainterp/optimizeopt/pure.py:316-322: Rust queues the postponed pre-downstream INT_*_OVF immediately; PyPy scans finalized _newoperations. If intbounds rewrites it to non-overflowing INT_* and removes GUARD_NO_OVERFLOW, PyPy exports the final always-pure INT_*, while Rust exports INT_*_OVF (and its short-preamble machinery adds an unnecessary overflow guard). The rewrite/removal path is rpython/jit/metainterp/optimizeopt/intbounds.py:209-220.

3. Pre-existing mismatches (already present before this patch)

  • majit/majit-metainterp/src/optimizeopt/unroll.rs:3837-3843 ↔ rpython/jit/metainterp/optimizeopt/unroll.py:398-400: Rust imposes a 20-iteration fixpoint cap and proceeds after logging; PyPy uses while 1 until convergence. A valid preamble requiring more iterations can therefore be incompletely replayed in Rust.

4. Structural adaptations

  • majit/majit-metainterp/src/optimizeopt/unroll.rs:3755-3794 ↔ rpython/jit/metainterp/optimizeopt/unroll.py:393-396: Rust maps both serialized Phase-1 and renamed short-input OpRef domains. PyPy needs only one mapping because its Box identities survive the boundary. This is a Rust serialized-IR identity adaptation.
  • majit/majit-metainterp/src/optimizeopt/unroll.rs:3874-3896 ↔ rpython/jit/metainterp/optimizeopt/unroll.py:364-370: Rust converts PyPy’s missing-mapping KeyError into a deferred InvalidLoop and fallback. This is an exception-free Rust control-flow adaptation.

inline_short_preamble's inner force-box loop re-forces short_jump_args
until the list stops growing. When force_box grows it via add_preamble_op
with a producer short-op not yet replayed, the newly-added jump arg is
absent from `mapping`; the re-loop's top-of-loop _map_args then panicked
on `expect("mapping missing jump_arg")`, aborting the process.

Port the unroll.py:425-434 guard: after the list grows, break out of the
inner loop when any new non-Const entry is not yet in `mapping`, deferring
to the outer replay loop to register it, and only re-force when every new
entry is a Const or already mapped.

Assisted-by: Claude
OptPure records is_always_pure ops into short_preamble_pure_ops so
produce_potential_short_preamble_ops can replay them into the peeled
short preamble. The OVF-op emit branch (an is_ovf op followed by
GUARD_NO_OVERFLOW) only inserted into the CSE cache and never pushed to
short_preamble_pure_ops, so a loop-invariant overflow-checked op
(INT_ADD_OVF/INT_SUB_OVF/INT_MUL_OVF) was recomputed each iteration
instead of hoisted once into the preamble.

Push the emitted OVF op onto short_preamble_pure_ops, matching
pure.py:321-322 which calls sb.add_pure_op(op) for both is_always_pure
and is_ovf+GUARD_NO_OVERFLOW ops.

Assisted-by: Claude

@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 `@majit/majit-metainterp/src/optimizeopt/unroll.rs`:
- Around line 3980-3993: Update the length guard in the short-jump argument
handling around current_short_jump_args so it exits when grown.len() is less
than or equal to num_short_jump_args, before slicing
grown[num_short_jump_args..]. Preserve the existing growth and remapping
behavior for longer argument lists.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c720a139-7ce7-4bd7-8fd0-813fc94b8e9a

📥 Commits

Reviewing files that changed from the base of the PR and between c08bc70 and 7be7346.

📒 Files selected for processing (2)
  • majit/majit-metainterp/src/optimizeopt/pure.rs
  • majit/majit-metainterp/src/optimizeopt/unroll.rs

Comment on lines +3980 to +3993
// unroll.py:425-434: if short_jump_args did not grow we are
// done. If force_box grew it via add_preamble_op, only re-force
// when every new entry is a Const or already in `mapping`;
// otherwise break and let the outer replay loop register the
// producer first, since the top-of-loop _map_args above would
// otherwise read an unmapped arg.
let grown = current_short_jump_args(short_preamble, ctx);
if grown.len() == num_short_jump_args {
break;
}
let all_new_mapped = grown[num_short_jump_args..]
.iter()
.all(|arg| arg.is_constant() || mapping.contains_key(arg));
if !all_new_mapped {

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 | 🔵 Trivial | 💤 Low value

Use a defensive length check to prevent potential slice panics.

While short_jump_args logically only grows in this context, using <= instead of == is a good defensive practice. It prevents a potential out-of-bounds slice panic at grown[num_short_jump_args..] if the underlying builder logic is ever refactored in the future to allow shrinking.

🛠️ Proposed defensive fix
-                if grown.len() == num_short_jump_args {
+                if grown.len() <= num_short_jump_args {
                     break;
                 }
📝 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
// unroll.py:425-434: if short_jump_args did not grow we are
// done. If force_box grew it via add_preamble_op, only re-force
// when every new entry is a Const or already in `mapping`;
// otherwise break and let the outer replay loop register the
// producer first, since the top-of-loop _map_args above would
// otherwise read an unmapped arg.
let grown = current_short_jump_args(short_preamble, ctx);
if grown.len() == num_short_jump_args {
break;
}
let all_new_mapped = grown[num_short_jump_args..]
.iter()
.all(|arg| arg.is_constant() || mapping.contains_key(arg));
if !all_new_mapped {
// unroll.py:425-434: if short_jump_args did not grow we are
// done. If force_box grew it via add_preamble_op, only re-force
// when every new entry is a Const or already in `mapping`;
// otherwise break and let the outer replay loop register the
// producer first, since the top-of-loop _map_args above would
// otherwise read an unmapped arg.
let grown = current_short_jump_args(short_preamble, ctx);
if grown.len() <= num_short_jump_args {
break;
}
let all_new_mapped = grown[num_short_jump_args..]
.iter()
.all(|arg| arg.is_constant() || mapping.contains_key(arg));
if !all_new_mapped {
🤖 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 `@majit/majit-metainterp/src/optimizeopt/unroll.rs` around lines 3980 - 3993,
Update the length guard in the short-jump argument handling around
current_short_jump_args so it exits when grown.len() is less than or equal to
num_short_jump_args, before slicing grown[num_short_jump_args..]. Preserve the
existing growth and remapping behavior for longer argument lists.

@youknowone youknowone merged commit 3e67246 into main Jul 15, 2026
29 of 30 checks passed
@youknowone youknowone deleted the box-pool branch July 15, 2026 01:12
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