Skip to content

jit(wasm): panic on missing pool-indexed const at emit time instead of emitting 0 - #691

Merged
youknowone merged 9 commits into
mainfrom
wasm-jit
Jul 23, 2026
Merged

jit(wasm): panic on missing pool-indexed const at emit time instead of emitting 0#691
youknowone merged 9 commits into
mainfrom
wasm-jit

Conversation

@youknowone

@youknowone youknowone commented Jul 21, 2026

Copy link
Copy Markdown
Owner

What

emit_resolve, emit_resolve_f64, and const_operand_value in the wasm backend resolved a legacy pool-indexed const that was absent from the constants pool to a silent 0 (a null Ref). This replaces that unwrap_or(0) with a loud panic via a shared resolve_const_bits helper (missing_emit_const).

Why

collect_constants_from_ops already panics on exactly this condition (missing_legacy_const), with the comment "never register a placeholder 0 — that would emit the constant as zero." The emit path silently did the opposite. This is a wasm-specific hazard: on native a null Ref traps on first dereference, but wasm offset 0 is valid linear memory, so a silent 0 is read as garbage and miscompiles quietly instead of crashing. The emit path now matches the collection path — a missing pool const fails loud (naming the raw OpRef) rather than becoming a silent wrong answer.

Verification

  • cargo check -p majit-backend-wasm clean.
  • check.py --backend wasm --synthetic-only: wasm 230/230 ALL PASSED — the new panic never fires on any valid trace (consistent with collect_constants_from_ops guaranteeing every referenced legacy const is seeded), so this is behavior-preserving for correct traces.

Turns any future (or latent) missing-const regression into a loud, debuggable failure instead of a silent wasm miscompile.

Assisted-by: Claude

Summary by CodeRabbit

  • Bug Fixes

    • Improved WebAssembly codegen by enforcing strict constant and layout resolution, removing silent placeholder behavior for missing metadata.
    • Corrected allocation, bounds, and addressing to require required size/array descriptors.
    • Fixed overflow handling: umullhi/binops now track overflow explicitly, and overflow guards now branch/deopt based on the flag.
    • Updated guard comparisons to use consistent integer equality semantics and corrected finish fail-index default to an “unknown” sentinel.
    • Marked unsupported interior-field, string/unicode, and GC load/store operations as clear failures.
  • Benchmarks

    • Added a Pyre benchmark for overflow-sensitive int multiply promotion.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@youknowone, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ff26e76-34fb-4b58-ace4-2c00742d6318

📥 Commits

Reviewing files that changed from the base of the PR and between 27bad16 and ae979ee.

📒 Files selected for processing (10)
  • majit/majit-backend-wasm/src/codegen.rs
  • majit/majit-backend-wasm/src/lib.rs
  • pyre/bench/synth/comprehension_param_range_call_flush.py
  • pyre/bench/synth/int_mul_ovf_bignum_promote.py
  • pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
  • pyre/pyre-jit-trace/src/trace.rs
  • pyre/pyre-wasm-runner/src/main.rs

Walkthrough

WASM codegen now strictly resolves constants and layout descriptors, rejects unsupported lowering paths, corrects guard addressing and finish metadata, and implements overflow tracking for integer operations. A synthetic benchmark exercises overflow-sensitive multiplication and big-integer promotion.

Changes

WASM codegen correctness

Layer / File(s) Summary
Strict metadata and lowering contracts
majit/majit-backend-wasm/src/codegen.rs
Constant, field, array, and allocation paths now fail explicitly when required values or descriptors are missing.
Unsupported operations and addressing
majit/majit-backend-wasm/src/codegen.rs, majit/majit-backend-wasm/src/lib.rs
Unsupported opcode paths are rejected, subclass loads use usize width, and missing finish indices use the unknown sentinel.
Integer overflow lowering
majit/majit-backend-wasm/src/codegen.rs
Scratch-local bookkeeping, unsigned multiplication-high computation, overflow flags, and overflow guard branching are implemented.
Benchmark coverage
pyre/bench/synth/int_mul_ovf_bignum_promote.py
Adds warmup and overflow-case loops that print results for machine-integer and big-integer multiplication scenarios.

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

Possibly related issues

Possibly related PRs

Poem

A rabbit guards each overflow flag,
While strict layouts leave no lag.
Bits resolve and widths align,
Big ints bloom on the proper sign.
Unknown finishes mark the way. 🐇

🚥 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 change: stricter wasm emit-time constant resolution that panics instead of emitting 0.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wasm-jit

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 21, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit ae979ee).
Updated: 2026-07-22T17:34:16.267Z

Files in the reviewed diff
majit/majit-backend-wasm/src/codegen.rs
majit/majit-backend-wasm/src/lib.rs
pyre/bench/synth/comprehension_param_range_call_flush.py
pyre/bench/synth/int_mul_ovf_bignum_promote.py
pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/resume_snapshot.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs
pyre/pyre-jit-trace/src/trace.rs
pyre/pyre-wasm-runner/src/main.rs

1. Regressions to PyPy parity introduced by this patch

  • majit/majit-backend-wasm/src/codegen.rs:2011 ↔ rpython/jit/backend/x86/assembler.py:1876: GUARD_VALUE now compares float bit patterns with i64_ne; PyPy uses UCOMISD floating comparison. This regresses +0.0 == -0.0 and makes identical-bit NaNs pass, whereas PyPy’s runtime guard treats signed zeros equal and NaNs unequal. upstream/main used f64_ne here.

2. Other mismatches introduced by this patch

  • majit/majit-backend-wasm/src/codegen.rs:2940 ↔ rpython/jit/backend/x86/assembler.py:1961: the new “width-correct” load passes size_of::<usize>(), which is the host Rust width (normally 8) while generating wasm32. It therefore still emits an 8-byte load instead of the required 4-byte wasm pointer/word load; the same error appears at codegen.rs:2966 ↔ assembler.py:1972.

  • pyre/pyre-jit-trace/src/trace.rs:1184 ↔ pypy/module/__builtin__/functional.py:732: the P2 drain abort rolls back store journals but not the newly added range-iterator journal. A failed bridge subwalk leaves W_IntRangeIterator.next()’s eager current/remaining advance in place, so blackhole replay skips an item.

  • pyre/pyre-jit-trace/src/trace.rs:1455 ↔ pypy/module/__builtin__/functional.py:732: the safe-abort reconstruction path calls fbw_store_journal_reset, which neither restores nor clears FBW_BRIDGE_ITER_JOURNAL (pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs:625). Thus an aborted bridge can retain stale cursor entries; a later rollback can restore an unrelated iterator, while a reset-only exit leaves the original iterator advanced.

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

  • majit/majit-backend-wasm/src/codegen.rs:2935 ↔ rpython/jit/backend/x86/assembler.py:1959: the vtable pointer is still read with i64_load before truncation to wasm32. PyPy’s target-word MOV_rm reads one pointer-width word; this was already present in upstream/main.

4. Structural adaptations

  • majit/majit-backend-wasm/src/codegen.rs:2684 ↔ rpython/jit/backend/llsupport/rewrite.py:273: declining direct string/unicode memory operations is a safe wasm-layout adaptation. PyPy lowers these using translated rstr layout tokens; pyre’s Array(Char) representation has no compatible fixed direct-memory layout.

  • majit/majit-backend-wasm/src/codegen.rs:1376 ↔ rpython/jit/metainterp/compile.py:623: WASM_CA_FINISH_FI_UNKNOWN is a wasm dispatch sentinel replacing the accidental valid fail-index 0; it preserves PyPy’s distinct DoneWithThisFrame descriptor semantics within a table-slot-based backend.

  • pyre/pyre-wasm-runner/src/main.rs:561 ↔ rpython/jit/metainterp/warmstate.py:387: skipping Wasmtime destructor teardown after flushed output is a Rust host-runner lifecycle optimization, not a PyPy JIT semantic path.

@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-backend-wasm/src/codegen.rs`:
- Line 4434: Re-extract the corresponding Charon .ullbc files with
scripts/extract-llbc.py after the JIT changes, rebuild the rtyper prepass, and
run all eight benchmarks before committing to verify there are no regressions.
🪄 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: f7f0159a-9fb8-4318-8043-f48a171070ea

📥 Commits

Reviewing files that changed from the base of the PR and between 9da9b5f and e728f9d.

📒 Files selected for processing (1)
  • majit/majit-backend-wasm/src/codegen.rs

Vec::new()
}

/// A legacy pool-indexed const that is absent from the constants pool at emit

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

Re-extract LLBC and run benchmarks after JIT changes.

As per coding guidelines, since this modifies JIT source, please ensure you re-extract the corresponding Charon .ullbc files using scripts/extract-llbc.py before rebuilding the rtyper prepass, and run all eight benchmarks to verify there are no regressions prior to committing.

🤖 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-backend-wasm/src/codegen.rs` at line 4434, Re-extract the
corresponding Charon .ullbc files with scripts/extract-llbc.py after the JIT
changes, rebuild the rtyper prepass, and run all eight benchmarks before
committing to verify there are no regressions.

Source: Coding guidelines

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: edb3625c95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread majit/majit-backend-wasm/src/codegen.rs Outdated
Comment on lines +2628 to +2631
OpCode::Strlen
| OpCode::Unicodelen
| OpCode::Strgetitem
| OpCode::Unicodegetitem => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid declining valid string opcodes

When optimized traces still contain residual string operations, this arm now aborts wasm compilation instead of emitting descriptor-driven loads. These opcodes are not impossible: OptString emits residual Strgetitem/Unicodegetitem when it cannot fold a character (majit/majit-metainterp/src/optimizeopt/vstring.rs:771-779), and the cranelift backend compiles the same string length/getitem opcodes from their ArrayDescr (majit/majit-backend-cranelift/src/compiler.rs:12892-12913, 12948-12975). In those cases hot string/unicode loops on wasm fall back/retry rather than compile; please lower them via the descriptors instead of unconditionally returning Unsupported.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 06554bf6e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2650 to +2654
| OpCode::GcLoadIndexedI
| OpCode::GcLoadIndexedR
| OpCode::GcLoadIndexedF
| OpCode::GcStore
| OpCode::GcStoreIndexed => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return Unsupported for frontend gc_load_indexed ops

These indexed GC ops are not GC-rewrite-only: the translated bytecode table wires gc_load_indexed_i/f and gc_store_indexed_i/f as real blackhole instructions (majit/majit-metainterp/src/blackhole.rs:7840-7843), and another backend lowers GcLoadIndexed* directly (majit/majit-backend-cranelift/src/compiler.rs:12452-12478). When a frontend llop/buffer trace containing one of these opcodes reaches wasm, this new arm now panics during compilation instead of declining the trace with BackendError::Unsupported, preventing the normal interpreter fallback path.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1cd137f80b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2311 to +2315
let num_bytes = const_operand_value(constants, arg1).unwrap_or_else(|| {
panic!(
"wasm int_signext: num_bytes operand (raw={}) is not a \
resolvable compile-time constant",
arg1.raw()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle runtime IntSignext widths without panicking

When IntSignext reaches wasm with arg(1) produced by another op/input instead of a constant, this new panic! aborts compilation rather than lowering the dynamic shift or returning BackendError::Unsupported for interpreter fallback. The IR/blackhole path does not make the width a compile-time-only operand (int_signext/ii>i is wired in majit/majit-metainterp/src/blackhole.rs:7544-7545), and another backend resolves op.arg(1) as a normal operand in majit/majit-backend-cranelift/src/compiler.rs:14082-14085, so such traces are valid inputs to backend codegen.

Useful? React with 👍 / 👎.

@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-backend-wasm/src/codegen.rs`:
- Around line 2106-2135: Centralize the overflow-flag local index used by the
GuardNoOverflow, GuardOverflow, and emit_ovf_binop paths. Add an
ovf_flag_local(num_vars: u32) helper near the existing codegen helpers and
replace each direct num_vars + UMULHI_SCRATCH + 1 calculation with calls to it,
preserving the current overflow guard behavior.
🪄 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: 2324459f-41c9-4217-825a-31817c632bf5

📥 Commits

Reviewing files that changed from the base of the PR and between 06554bf and 27bad16.

📒 Files selected for processing (3)
  • majit/majit-backend-wasm/src/codegen.rs
  • majit/majit-backend-wasm/src/lib.rs
  • pyre/bench/synth/int_mul_ovf_bignum_promote.py

Comment thread majit/majit-backend-wasm/src/codegen.rs Outdated
@youknowone
youknowone force-pushed the wasm-jit branch 2 times, most recently from 2d3ac4e to f1f86fa Compare July 22, 2026 06:12

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f1f86fa382

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2937 to +2941
emit_sized_int_load(
&mut sink,
offset2 as u64,
std::mem::size_of::<usize>(),
true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Load the full subclass range field

On wasm32 this emits a 4-byte load because size_of::<usize>() is 4, but the runtime layouts read by this guard are PyType::subclassrange_min / ClassTypeLayout::subclassrange_min, both 8-byte i64 fields. If a range value ever needs the upper 32 bits, GuardSubclass truncates/sign-extends the object's min and can accept or reject the guard incorrectly; use the actual field width for both this vtable path and the gcremovetypeptr path below.

AGENTS.md reference: AGENTS.md:L15-L18

Useful? React with 👍 / 👎.

…no clean finish

loop_finish_fi is the clean-DoneWithThisFrame fail_index published into the
CALL_ASSEMBLER dispatch entry; the CA arm treats a callee whose returned
fail_index equals it as a clean finish and reads the result Ref from the output
slot, else deopts. When a compiled loop has no clean finish descr the fallback
published 0, but 0 is a valid fail_index (the first descr registered) and the
declared "none" sentinel is WASM_CA_FINISH_FI_UNKNOWN (u32::MAX, the value the
pending dispatch entry is initialized to). Publishing 0 could make a callee's
fail_index-0 guard exit be misread as a clean finish and read a wrong result.
Use the sentinel; no real fail_index equals it, so the case deopts correctly.

check.py --backend wasm: 240/240.

Assisted-by: Claude
…g hardcoded read

strlen/unicodelen/strgetitem/unicodegetitem were lowered with a hardcoded
layout: the length as an 8-byte i64_load of a 4-byte word field (folding
garbage into the high bits), and the item as a 1-byte stride-1 read at a fixed
offset — wrong for UNICODE, whose code units are 4 bytes at stride 4. On wasm
this is a silent wrong value (offset is valid linear memory, no trap). pyre
models strings/unicode as Array(Char) and routes these through the descr-driven
GETARRAYITEM/ARRAYLEN paths, so no producer emits these ops; decline them
(interpreter fallback) like the interior-field / GC-load arms.

check.py --backend wasm: 240/240.

Assisted-by: Claude
…bytes

Both GuardSubclass arms read the word-sized (lltype.Signed) subclassrange_min
field with i64_load (8 bytes), folding the adjacent subclassrange_max into the
high 32 bits. On wasm32 the field is 4 bytes, so the guard's unsigned range
check (loc_tmp - check_min <u check_max - check_min) sees a huge value and
reliably fails, forcing a deopt (correct but unaccelerated). Load it at word
width via emit_sized_int_load (4-byte signed on wasm32), matching the
width-correct ArraylenGc sibling. The vtable-pointer and GC-header i64_loads in
the same arm are unchanged (the former is immediately i32-wrapped; the latter
is a genuine 8-byte header).

check.py --backend wasm: 240/240 (GuardSubclass is latent, not in the corpus).

Assisted-by: Claude
… clean finish

bridge_finish_fi is the CA-emitting trace's own clean DoneWithThisFrame global
fail_index; the CALL_ASSEMBLER arm treats a callee whose returned fail_index
equals it (or the callee's published loop_finish_fi) as a clean finish and reads
the result slot, else routes to wasm_ca_resume_deopt. g.fail_index is already
base-offset into the global fail-index space, so the .unwrap_or(0) fallback for a
trace with no non-exception finish is global index 0 — a valid first-registered
descr. A callee exiting through global fail_index 0 would then be misclassified
as a clean finish, dropping a guard deopt or an exception. Use
WASM_CA_FINISH_FI_UNKNOWN (u32::MAX, bakes as i32 -1, never matches a real
index), mirroring the loop_finish_fi fix.

check.py --backend wasm: 240/240.

Assisted-by: Claude
GUARD_VALUE checks whether a runtime value equals its promoted constant. The
Value/Const equality contract (value.rs Value::eq, history.py same_constant)
compares floats by to_bits() — so 0.0 != -0.0 and NaN == same-bit NaN — and the
dynasm/cranelift backends implement it as an integer bit-compare. The wasm arm's
float branch used IEEE f64.ne, which passes -0.0 == +0.0 (a guard the reference
fails): a runtime -0.0 promoted against a recorded +0.0 keeps running the trace
and const-folds to +0.0, corrupting any sign-of-zero-observable result. It also
fails NaN == same-bit NaN, spuriously deopting. emit_resolve reinterprets an F64
local to its i64 bits, so the int path's i64_ne is the correct compare for both;
remove the float special-case.

check.py --backend wasm: 240/240.

Assisted-by: Claude
…pile diagnostics

On a successful run the wasmtime Store/Module/Engine are dropped at
process exit, munmapping ~40MB of compiled code and guest linear memory
the OS reclaims anyway; on a short run that teardown is ~0.2s, larger
than the cwasm load and far larger than trace compilation. Exit via
process::exit on the success path after stdout/profiler/stats are
flushed; PYRE_WASM_FULL_TEARDOWN=1 restores the drops for leak checks.

Add PYRE_WASM_STARTUP_TRACE (engine_new/load_module/instantiate/
run_python/total laps) and compile_ms (total Module::new time) in
PYRE_WASM_JIT_STATS to attribute the fixed startup cost.

Assisted-by: Claude
Exercises an overflow-crossing int multiply on a JIT-hot path: the loop
is traced while the product fits an i64 (GUARD_NO_OVERFLOW passes), then a
large operand overflows and must promote to a big int. A backend that
drops the overflow check silently wraps instead of promoting.

Assisted-by: Claude
…cording

A bridge/retrace recording walk runs the range FOR_ITER as an authoritative
eager-executor: it advances the real iterator and captures the consumed item
in-flight. The advance is left unjournaled because the main walk recovers the
item via in-flight forward-delivery on abort. The bridge/retrace abort path has
no such delivery, so a recording that did not commit left the iterator advanced
while the item was never appended, and the interpreter resume continued past it
(dropping one comprehension element, or corrupting a returned list).

Journal the pre-advance cursor while is_bridge_trace and restore it via
w_range_iter_set_cursor when the walk does not commit, so the aborted recording
is side-effect neutral and the resume re-consumes the item exactly once. A
committed bridge keeps the advance (the authoritative continuation). Gated to
is_bridge_trace, so the main walk's forward-delivery path is untouched.

Assisted-by: Claude
…ack slot

collect_call_stack_overrides rebuilds the caller operand stack for the
gh#467 nested-inline-decline forward-flush. The live vstack/color sources
emit genuine null-or-self sentinels as explicit null Refs; the shadow
fallback then pushed every remaining slot as an override, including ones
the walk could not resolve (a LOAD_CONST operand whose concrete Ref was
never mirrored) which resolved to a null Ref. The flush override
validation only checked that an override existed, not that it was
non-null, so it committed the null into the caller frame operand stack.
Re-executing the flushed CALL then bound a NULL argument, raising
UnboundLocalError on the callee parameter.

Push only non-null values from the shadow fallback. An unresolved slot is
left absent, so the outer-call flush validation declines and the legacy
replay rebuilds the frame from its start state.

Add pyre/bench/synth/comprehension_param_range_call_flush.py covering the
module-scope hot-loop shape that triggers the flush.

Assisted-by: Claude

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae979eec0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// A bridge/retrace recording walk has no in-flight forward-delivery on
// abort, so journal the pre-advance cursor for restore if the walk does
// not commit (keeps the aborted recording side-effect neutral).
fbw_bridge_iter_journal_push(iter_obj, concrete_current, concrete_remaining);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Roll back bridge iterator journals on subwalk abort

When this specialization runs inside drive_bridge_carrier_subwalk, ctx.trace_ctx.is_bridge_trace is true, so it records the pre-advance range cursor here, but that subwalk does not return through run_perfn_walk's new epilogue; its failure path only calls fbw_store_journal_rollback() at pyre/pyre-jit-trace/src/trace.rs:1184 and other safe-abort paths reset at trace.rs:1455. For a bridge-carrier subwalk that consumes a range item and then declines, the journal is never rolled back, leaving the live iterator advanced while the interpreter/blackhole replays from the guard and drops that iteration.

Useful? React with 👍 / 👎.

Comment on lines +4260 to +4261
static FBW_BRIDGE_ITER_JOURNAL: std::cell::RefCell<Vec<(pyre_object::PyObjectRef, i64, i64)>> =
const { std::cell::RefCell::new(Vec::new()) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Root bridge iterator journal entries

This new TLS stores raw PyObjectRef iterators across the rest of an authoritative bridge walk, but capture_fbw_store_journal_root_area / fbw_store_journal_root_walker_area still visit the existing store/append/sys_exc/foriter journals only, not this one. If a minor collection runs after w_range_iter_next before rollback, the live frame's iterator pointer is forwarded while this journal slot is not, so w_range_iter_set_cursor can write through a stale moved pointer instead of restoring the iterator; add this journal to the root area/walker lifecycle like the other FBW journals.

AGENTS.md reference: AGENTS.md:L153-L155

Useful? React with 👍 / 👎.

@youknowone
youknowone merged commit a1de59b into main Jul 23, 2026
30 of 31 checks passed
@youknowone
youknowone deleted the wasm-jit branch July 23, 2026 01:52
youknowone added a commit that referenced this pull request Jul 25, 2026
…ext/indexed-GC decline, bridge-iter journal root) (#737)

* wasm: correct GuardSubclass field width; decline runtime IntSignext and indexed GC ops

- GuardSubclass read subclassrange_min at size_of::<usize>() (4 bytes on
  wasm32); PyType::subclassrange_min is AtomicI64, so read 8 bytes on both the
  vtable and gcremovetypeptr paths.
- IntSignext with a non-constant num_bytes operand aborted via panic; return
  BackendError::Unsupported so the trace declines to interpreter fallback.
- gc_load_indexed_*/gc_store_indexed_* are frontend blackhole ops that can
  reach the backend; return Unsupported for the indexed forms instead of
  panicking. The bare GcLoad*/GcStore GC-rewrite forms keep the panic.

Assisted-by: Claude

* jit: root the FBW bridge iterator cursor journal

FBW_BRIDGE_ITER_JOURNAL stores range-iterator refs across an authoritative
bridge walk but was visited by no root walker, unlike the five sibling FBW
journals. Add it to FbwStoreJournalRootArea and fbw_store_journal_root_walker_
area so a minor collection forwards the iterator before the non-commit rollback
restores its cursor via w_range_iter_set_cursor.

Assisted-by: Claude

* check.py: report exec times and true ratio in perf-gate FAIL lines

The FAIL line printed raw run times and the gate threshold formatted as if it
were the measured ratio, so the numbers were not self-consistent. Add
_gate_fail_detail to print the startup-subtracted exec times the gate actually
compared, their ratio, and the threshold.

Assisted-by: Claude

* wasm: add unicode str-subscript regression bench; document string-op decline as verified inert

- bench/synth/str_getitem_len_hot.py: hot str/unicode subscript and len over
  ASCII/latin1/BMP/astral strings (item_size 1/2/4), routed through the
  GETARRAYITEM/ARRAYLEN paths; output asserted cpython==pypy.
- codegen.rs: a str-subscript / len / compare / find hot loop traces to
  GETARRAYITEM, never STRGETITEM (verified with PYRE_DUMP_PERFN_JITCODE), so the
  STRGETITEM/UNICODEGETITEM/STRLEN/UNICODELEN decline covers ops no trace emits;
  note this so the decline is not mistaken for a missing descr-driven lowering.

Assisted-by: Claude

* dynasm: materialize wide binop immediates through the scratch register

emit_binop_reg_loc's Loc::Immed arm truncated the value with `as i32`,
encoding an out-of-i32-range immediate as a sign-extended imm32 —
`x & 0xFFFF_FFFF_FFFF` degenerated to `x & -1` in compiled code, the
wrong output of synth/str_getitem_len_hot on dynasm. Follow
regloc.py:456-464: mov the value into X86_64_SCRATCH_REG and retry the
reg-reg form. The IntAdd LEA emitter gets the same fallback for its
immediate arm, which the consider_binop_symm path reaches with an
arbitrary 64-bit constant; its two symmetric arms are merged. Two
backend tests compile and execute AND/ADD with wide immediates.

* jit: expose pypyjit.set_param for runtime JIT-parameter control

Register a pypyjit module whose set_param accepts the positional-string
form ("name=value,…", "off", "default") and keyword arguments, routing
both through the JIT's set_user_param parser. pyre-interpreter cannot
import pyre-jit, so add a SET_JIT_PARAM_STRING_HOOK alongside the existing
per-pair SET_JIT_PARAM_HOOK; pyre-jit registers
set_jit_param_string_via_warmstate at boot and per-eval. The hook is an
in-process function pointer, so a pypyjit.set_param call configures the
warmstate on every backend including the wasm guest, which sees no
environment.

* bench: add threshold-1 JIT-stress twins of the exception recording benches

exception_metadata_jitstress and exception_reraise_tb_depth_jitstress call
pypyjit.set_param("threshold=1,function_threshold=1") so trace recording
fires on the earliest iterations of every section rather than after the
~1600-iteration warmup. Recording then lands on the traceback/context/
exc_info/reraise shapes on every run and every backend, making coverage of
the recording path deterministic instead of dependent on which iteration a
warmup pass happens to hit. The import is guarded so the benches run
unchanged under CPython, which has no pypyjit. Output matches the
natural-threshold twins.

* bench: add bare/named/finally re-raise traceback-depth regression bench

A module-level hot loop executes bare re-raise (depth 2), named re-raise
(depth 3), and finally-passthrough (depth 2) so the recording iteration itself
runs the re-raise chain. Guards the instruction-keyed traceback recording
against spurious nodes at re-raise / handler-cleanup coordinates.
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