Skip to content

jit(wasm): general CALL_ASSEMBLER + float residual/local perf gaps (nbody/fannkuch/float_loop/spectral)#564

Open
youknowone wants to merge 5 commits into
mainfrom
wasm-jit
Open

jit(wasm): general CALL_ASSEMBLER + float residual/local perf gaps (nbody/fannkuch/float_loop/spectral)#564
youknowone wants to merge 5 commits into
mainfrom
wasm-jit

Conversation

@youknowone

@youknowone youknowone commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Closes the largest wasm-JIT-vs-native(aarch64 dynasm) steady-state perf gaps: float-heavy code (residual crossings + f64 homing) and compiled→compiled loop entry (general CALL_ASSEMBLER). Every lever is the direct wasm analogue of what the native backends already do.

Commits

  1. lower float residual calls through typed direct call_indirect — float residual calls (CallF/CallPureF/CallLoopinvariantF/CallMayForceF, e.g. x ** 0.5 → libm pow) went through the env.jit_call host trampoline (guest→host→guest signature reflection + arg marshalling). They now use a direct in-module call_indirect with a per-descr (f64…)->f64 wasm signature declared after the existing i64/CA type families. On nbody this drops host crossings from 5.28M → 292K.

  2. home Float-typed SSA values in f64 wasm locals — every SSA value was homed in a uniform i64 local, so each float op was bracketed by i64<->f64 reinterprets that Cranelift lowers to real fmov gp<->fp moves. Float-typed values now use f64 locals; a reinterpret is inserted only where a float crosses into the i64 world (frame stores, guards, residual i64-ABI args, loop-seed loads). Int/Ref-only traces coalesce to the prior single i64 local run, so their emitted module is byte-identical.

  3. restrict direct float residual calls to all-float-arg signatures — review follow-up. A float-result residual target whose pointer argument is i32 on wasm32 (e.g. jit_bigint_to_f64_or_inf(&BigInt) -> f64) would trap on a (i64)->f64 call_indirect type mismatch. The direct path now accepts a float residual only when every argument is Float (unambiguous f64); Int/Ref args keep the reflecting trampoline. pow ([Float,Float]->Float) — the dominant win — stays direct.

  4. enter compiled traces via in-guest call_indirect instead of the host jit_execute round-trip — trace entry (glue::execute) now transmutes the trace's shared-table slot to extern "C" fn(u32)->u32 and runs the trace inside the guest instead of crossing to the jit_execute_wasm host import (the same slot the host dispatched by, and the one the CA self-recursion arm already re-enters through). The host import is retained on a dead black_box(false) branch so wasm-ld keeps it in the import table — dropping it would shift import indices and break baked JIT indices. A guest JIT_EXECUTE_COUNT atomic, exported as pyre_jit_execute_count and read by the runner, preserves the executes stat.

  5. lower general CALL_ASSEMBLER to any compiled-loop callee with a terminal-decline back-off — the wasm backend previously declined every trace containing a CALL_ASSEMBLER except the self-recursive-int fib shape (bridge_is_self_recursive_int_ca), so an outer loop that calls an inner-loop-bearing callee never compiled and round-tripped through the interpreter each iteration.

    • general_int_call_assembler_target now admits a loop/bridge whose CALL_ASSEMBLER targets any compiled loop when every arg type and the result are Int|Ref.
    • emit_ca marshals the resolved callee geometry (frame bytes, gcmap, shared-table slot) and call_indirect(0, callee_slot)s it, reading the result or blackhole-resuming the callee on a non-finish exit.
    • try_compile_ca_bridge feeds the CA callee guard through the normal must-compile/bridge path; when the callee bridge terminally declines on wasm, mark_call_assembler_terminal_decline invalidates the callers so the next trace refuses the target and returns to the baseline path (wasm_ca_baseline_call), avoiding a deopt-to-blackhole-per-call regression.
    • Frozen-chain frame geometry reserves 64 value slots / 64 Ref homes so a later CA-callee bridge fits the loop's frame layout.

    nbody 2.44s → 0.46s (timeout gone); ca_int/ca_innerloop 14.8s → ~0.8s; byte-exact vs dynasm within the known cross-ISA pow ULP tolerance.

Measured (wasm-vs-dynasm work-ratio, startup-corrected)

bench before after
float_loop 4.5x 0.9x
spectral_norm 1.8x 1.3x
nbody 17.6x 2.4x

nbody: the float levers (host crossings + fmov) took 17.6x → 12.5x; general CALL_ASSEMBLER then took 12.5x → 2.4x (the module-global dict-call outer loop now compiles instead of round-tripping the interpreter every iteration). fannkuch's remaining 3.16x is an irreducible wasm array-access instruction-density constant factor (wasm has no scaled-index addressing — emit_array_addr ≈ 9 wasm instrs vs one aarch64 ldr [base,idx,lsl#3,#off]), verified not a loop-peeling/optimizer gap: the peel-abort → retry-without-unroll is PyPy-orthodox (virtualstate.py:372-375 pins constant jump-values, intutils.py:1336-42 widen_update doesn't loosen small bounds, pyjitpl.py:3046-50 has the same non-unrolled fallback).

Verification

  • pyre/check.py --backend dynasm,wasm green except the pre-existing synth/list_insert_pop_index BASEFAIL (both backends): dynasm 187/1, wasm 186/1.
  • float_loop/spectral_norm/ca_* byte-exact dynasm-vs-wasm; nbody byte-exact within the known cross-ISA pow ULP tolerance.
  • Bignum→float hot loop no longer traps and is byte-exact dynasm-vs-wasm.
  • 18 majit-backend-wasm unit tests pass; int_loop/fib_loop (non-float) unchanged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved WebAssembly JIT support for floating-point operations and optimized compiled calls.
    • Added more reliable handling for compiled call targets, deoptimization, and fallback execution.
    • Added execution-count diagnostics for tracking JIT activity.
  • Bug Fixes

    • Improved garbage-collection safety and frame handling during compiled calls.
    • Prevented stale compiled-target metadata after recompilation.
    • Enhanced recovery when compiled call bridges cannot be completed.
  • Diagnostics

    • Updated JIT statistics to report accepted compiled calls and guest-side execution counts when available.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds float-aware WASM local and residual-call code generation, restructures CALL_ASSEMBLER target selection and lifecycle handling, wires CA bridge and baseline resume paths, and exposes guest-side JIT execution statistics.

Changes

WASM JIT execution changes

Layer / File(s) Summary
Typed locals and float residual calls
majit/majit-backend-wasm/src/codegen.rs
SSA values now map to WASM local types, float operands use reinterpretation-aware resolution, and eligible residual calls receive direct float-compatible call_indirect signatures.
CALL_ASSEMBLER target lifecycle
majit/majit-backend-wasm/src/failguard.rs, majit/majit-backend-wasm/src/lib.rs
CALL_ASSEMBLER targets are resolved, published, activated, terminally declined, invalidated, and retracted with expanded CA metadata and helper-slot wiring.
CA bridge and baseline resume
majit/majit-metainterp/src/pyjitpl.rs, pyre/pyre-jit/src/call_jit.rs, pyre/pyre-jit/src/eval.rs
CA deoptimization attempts bridge compilation, detects terminal decline, and invokes a configured baseline helper for WASM frames.
Guest execution diagnostics
majit/majit-backend-wasm/src/glue.rs, pyre/pyre-wasm/src/lib.rs, pyre/pyre-wasm-runner/src/main.rs
Guest trace execution is counted and exported, and runner diagnostics prefer the guest count while reporting accepted CA bridges.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WASMTrace
  participant CALL_ASSEMBLER
  participant wasm_ca_resume_deopt
  participant try_compile_ca_bridge
  participant BaselineHelper
  WASMTrace->>CALL_ASSEMBLER: enter compiled target
  CALL_ASSEMBLER->>wasm_ca_resume_deopt: deopt with guard descriptor
  wasm_ca_resume_deopt->>try_compile_ca_bridge: attempt CA bridge compilation
  try_compile_ca_bridge-->>wasm_ca_resume_deopt: return terminal decline status
  wasm_ca_resume_deopt->>BaselineHelper: resume baseline evaluation when needed
Loading

Possibly related issues

  • youknowone/pyre#466 — Covers WASM CALL_ASSEMBLER lowering and related CA frame and target plumbing.
  • youknowone/pyre#205 — Relates to WASM CALL_ASSEMBLER target/deopt handling and residual-call or blackhole-resume behavior.

Possibly related PRs

Poem

A rabbit hopped through WASM bright,
With floaty locals packed just right.
CA bridges rose, then knew when to fall,
Baseline helpers answered the call.
And guest counts twinkled through the night.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: CALL_ASSEMBLER support and float residual/local performance improvements.
✨ 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 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 15, 2026

Copy link
Copy Markdown

🤖 Codex parity review

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

Files in the reviewed diff
majit/majit-backend-wasm/src/codegen.rs
majit/majit-backend-wasm/src/failguard.rs
majit/majit-backend-wasm/src/glue.rs
majit/majit-backend-wasm/src/lib.rs
majit/majit-metainterp/src/pyjitpl.rs
pyre/pyre-jit/src/call_jit.rs
pyre/pyre-jit/src/eval.rs
pyre/pyre-wasm-runner/src/main.rs
pyre/pyre-wasm/src/lib.rs

1. Regressions to PyPy parity introduced by this patch

  • majit/majit-backend-wasm/src/lib.rs:1154 ↔ rpython/jit/backend/x86/assembler.py:2267: the new direct CA admission accepts CallAssemblerI, but its guard-deopt helper reads/returns the result as a Ref: pyre/pyre-jit/src/call_jit.rs:3047 ↔ rpython/jit/backend/x86/assembler.py:2296. PyPy loads deadframe slot 0 according to op.type; the new wasm path calls get_ref_value and handle_blackhole_result, corrupting an integer result on a callee guard failure. main left this operation on the generic trampoline path.

2. Other mismatches introduced by this patch

  • majit/majit-backend-wasm/src/lib.rs:1672 ↔ rpython/jit/backend/llsupport/rewrite.py:665: Box::leak(build_callee_gcmap(...)) permanently leaks one callee GC map for every compiled loop. CompiledWasmLoop::drop removes the target registration at majit/majit-backend-wasm/src/failguard.rs:352, but cannot reclaim that allocation; PyPy keeps the frame metadata with the loop token rather than intentionally leaking it.

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

  • majit/majit-backend-wasm/src/codegen.rs:846 ↔ rpython/jit/backend/x86/assembler.py:2188: the existing direct residual-call path infers a uniform (i64 × n) -> i64 wasm signature merely from IR Int|Ref types. PyPy uses the CallDescr’s actual argument/result ABI. On wasm32, a Ref may be an i32 parameter, so such calls can type-mismatch and trap. This predates the patch; the new float path correctly avoids this assumption.
  • majit/majit-backend-wasm/src/codegen.rs:2542 ↔ rpython/jit/backend/llsupport/rewrite.py:233: existing GETINTERIORFIELD_GC_* lowering uses only base + field_offset, discarding the index and descriptor itemsize; SetinteriorfieldGc has the same issue at majit/majit-backend-wasm/src/codegen.rs:2569 ↔ rpython/jit/backend/llsupport/rewrite.py:239. Nonzero-index interior accesses address the wrong element.

4. Structural adaptations

  • majit/majit-backend-wasm/src/glue.rs:88 ↔ rpython/jit/backend/x86/assembler.py:2272: entering a compiled wasm trace through a guest function-table call rather than a native machine-code call is a Rust/Wasm implementation adaptation; it preserves the trace entry contract.
  • majit/majit-backend-wasm/src/codegen.rs:1106 ↔ rpython/jit/backend/x86/assembler.py:2296: retaining Float SSA values in wasm f64 locals while frame slots remain i64 bit carriers is a required Wasm representation adaptation, not a PyPy semantic divergence.
  • majit/majit-backend-wasm/src/lib.rs:1146 ↔ rpython/jit/backend/x86/assembler.py:2267: restricting direct CALL_ASSEMBLER chaining to one already-compiled Int|Ref target is structural. Unsupported Float/Void or multi-target cases decline to the existing fallback rather than being assigned incorrect semantics.

@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: 8e2a773af7

ℹ️ 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".

value_types[ia.index as usize] = ValType::F64;
}
}
for op in ops {

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 Mark float LABEL arguments as f64 locals

When a LABEL introduces an independent float OpRef that is not an inputarg or an op result, this census leaves that local as I64 because it only marks inputargs/results. Later the local backedge and resume loader choose conversions from value_types[label_arg.raw()], while float users call emit_resolve_f64 expecting the same local to be F64, so float loop-carried label args can generate invalid Wasm local types. The Cranelift backend has an explicit LABEL-arg declaration path for this case (majit-backend-cranelift/src/compiler.rs:9077-9099); this needs the same OpRef::ty()-based marking.

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: 3290979f23

ℹ️ 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 +1154 to +1157
if !matches!(
op.opcode,
majit_ir::OpCode::CallAssemblerI | majit_ir::OpCode::CallAssemblerR
) {

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 Restrict CA fallback to the PyFrame/ref ABI

When a general CALL_ASSEMBLER target is later marked terminally declined, the generated terminal branch calls wasm_ca_baseline_call(op.arg(0), compiled_ptr), whose implementation casts that first word to *mut PyFrame and returns eval_with_jit's PyObjectRef bits. This admission path now accepts CallAssemblerI and any all-Int/Ref descriptor inputs, so a red-only integer-result or non-frame-first CALL_ASSEMBLER will either dereference the wrong value as a PyFrame or store a boxed object pointer into an integer result once the terminal-decline bit flips; the direct path needs to be limited to the ABI that the baseline helper actually implements, or the fallback needs per-descr marshalling/result handling.

Useful? React with 👍 / 👎.

Comment on lines +3113 to +3115
Err(err) => {
set_pending_ca_exception(err);
0

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 Publish baseline CA exceptions to JIT exception slots

For a terminal-declined CA callee that raises, this only stores the error in LAST_CA_EXCEPTION and returns 0, but the compiled caller continues through the recorded GuardNoException/GuardException checks, which read the backend jit_exc_type/jit_exc_value slots rather than this TLS stash. That makes the caller miss the exception and consume 0 as the call result; the helper should set the backend JIT exception state before returning to compiled code.

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/failguard.rs`:
- Around line 236-239: Update the Drop implementation for CompiledWasmLoop to
invalidate every registered self.ca_callers entry before the loop’s storage can
be freed, clearing the corresponding ca.terminal_declined_ptr and
ca.callee_compiled_ptr raw pointers; preserve the existing
CALL_ASSEMBLER_TARGETS removal while ensuring active callers cannot retain
dangling references.
🪄 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: ad648ff3-7069-49d0-abd4-d83c7a9b0516

📥 Commits

Reviewing files that changed from the base of the PR and between 49ec51c and 3290979.

📒 Files selected for processing (9)
  • majit/majit-backend-wasm/src/codegen.rs
  • majit/majit-backend-wasm/src/failguard.rs
  • majit/majit-backend-wasm/src/glue.rs
  • majit/majit-backend-wasm/src/lib.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • pyre/pyre-jit/src/call_jit.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-wasm-runner/src/main.rs
  • pyre/pyre-wasm/src/lib.rs

Comment on lines 236 to +239
pub struct CompiledWasmLoop {
/// Owning `JitCellToken` number, used to retract this loop's
/// CALL_ASSEMBLER target metadata on drop.
pub token_number: u64,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm Drop never walks ca_callers, and check whether eviction elsewhere
# accounts for active CA callers before removing a compiled loop.
rg -n 'ca_callers' majit/majit-backend-wasm/src/*.rs
rg -n 'fn drop' -A 40 majit/majit-backend-wasm/src/failguard.rs | rg -n 'ca_callers|CompiledWasmLoop'
rg -n 'fn try_to_free_some_loops' -A 60 majit/majit-metainterp/src/pyjitpl.rs | rg -n 'ca_callers|CompiledWasmLoop|wasm'

Repository: youknowone/pyre

Length of output: 805


Dropping a CompiledWasmLoop can leave active CA callers with dangling pointers.
Drop removes this loop from CALL_ASSEMBLER_TARGETS, but it never invalidates self.ca_callers. That means ordinary eviction can free a callee while other compiled wasm code still has raw pointers into this struct (ca.terminal_declined_ptr / ca.callee_compiled_ptr). Either walk the callers here and invalidate them, or keep the callee alive while callers remain registered.

🤖 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/failguard.rs` around lines 236 - 239, Update the
Drop implementation for CompiledWasmLoop to invalidate every registered
self.ca_callers entry before the loop’s storage can be freed, clearing the
corresponding ca.terminal_declined_ptr and ca.callee_compiled_ptr raw pointers;
preserve the existing CALL_ASSEMBLER_TARGETS removal while ensuring active
callers cannot retain dangling references.

Route the float residual families (CallF, CallPureF, CallLoopinvariantF,
CallMayForceF) with a Float result through a direct in-module call_indirect
instead of the env.jit_call host trampoline. residual_call_float_sig derives
the callee's real wasm signature from the call descr (Int/Ref -> i64,
Float -> f64, result f64); the distinct signatures are declared as function
types after the existing i64 and CA type families so no prior type index
shifts, and Float SSA values are reinterpreted to/from their i64 bit carrier
around the call. Assembler and release-GIL float calls stay on the trampoline.

On nbody, jit_calls fall from 5.28M to 292K with unchanged energy output.

Assisted-by: Claude
Type each value local by its SSA value's type (f64 for Float, i64 for
Int/Ref) instead of homing every value in a uniform i64 local. Float
producers store the f64 result directly and float consumers read it
directly; a reinterpret is inserted only where a float value crosses into
the i64 world (frame stores, guards, residual i64-ABI args, loop seed
loads). Int/Ref-only traces coalesce to the prior single i64 local run so
their emitted module is byte-identical.

Removes the per-float-op i64<->f64 reinterprets that Cranelift lowered to
gp<->fp fmov moves. float_loop wasm-vs-dynasm work-ratio drops from ~4.5x
to ~0.9x, spectral_norm from ~1.8x to ~1.3x; nbody float arithmetic is
likewise unboxed.

Assisted-by: Claude
…atures

The typed float call_indirect derived its wasm signature from the call
descr, mapping Int/Ref args to i64. That is unsound for a float-result
residual target whose pointer argument is i32 on wasm32 (e.g.
jit_bigint_to_f64_or_inf(&BigInt) -> f64): the emitted (i64)->f64 type
mismatches the (i32)->f64 table entry and call_indirect traps. Unlike the
audited int residual family, float-result targets are not guaranteed to use
a uniform word ABI, so the descr's Int/Ref types are not a reliable oracle
for the callee's wasm parameter widths.

Accept a float residual for the direct path only when every argument is
Float (an unambiguous f64); Int/Ref/other args keep the reflecting jit_call
trampoline. Float ** (pow), the dominant win, is [Float,Float]->Float and
stays direct.

Assisted-by: Claude
…f the host jit_execute round-trip

`glue::execute` (host-import path) transmutes the trace's shared-table slot
(`func_id`) to an `extern "C" fn(u32) -> u32` and calls it, running the trace
inside the guest instead of crossing to the host `jit_execute_wasm` import. The
slot is the same index the host dispatched by (`table.get(func_id)`) and the CA
self-recursion arm already re-enters through (`call_indirect(0, self_slot)`).

The host import is retained on a `black_box(false)` branch so `wasm-ld` keeps it
in the import table (dropping it would shift import indices and break baked JIT
indices). A guest `JIT_EXECUTE_COUNT` atomic, exported as `pyre_jit_execute_count`
and read by the runner, keeps the `executes` stat now that the host no longer
observes each entry.

Assisted-by: Claude
… terminal-decline back-off

The wasm backend previously declined every trace containing a CALL_ASSEMBLER
except the self-recursive-int fib shape (bridge_is_self_recursive_int_ca), so
an outer loop that calls an inner-loop-bearing callee never compiled and
round-tripped through the interpreter each iteration.

- general_int_call_assembler_target admits a loop/bridge whose CALL_ASSEMBLER
  targets any compiled loop when every arg type and the result are Int|Ref.
- emit_ca marshals the resolved callee geometry (frame bytes, gcmap, shared
  table slot) and call_indirect(0, callee_slot)s it, reading the result or
  blackhole-resuming the callee on a non-finish exit.
- try_compile_ca_bridge feeds a CA callee guard through the normal
  must-compile/bridge path; when the callee bridge terminally declines on
  wasm, mark_call_assembler_terminal_decline invalidates the callers so the
  next trace refuses the target and returns to the baseline path
  (wasm_ca_baseline_call), avoiding a deopt-to-blackhole-per-call regression.
- The frozen chain frame geometry reserves 64 value slots / 64 Ref homes so a
  later CA-callee bridge fits the loop's frame layout.

nbody 2.44s -> 0.46s (timeout gone); ca_int/ca_innerloop 14.8s -> ~0.8s;
byte-exact vs dynasm within the known cross-ISA pow ULP tolerance.

Assisted-by: Claude
@youknowone youknowone changed the title jit(wasm): close float residual + float-local perf gaps (nbody/float_loop/spectral) jit(wasm): general CALL_ASSEMBLER + float residual/local perf gaps (nbody/fannkuch/float_loop/spectral) Jul 15, 2026

@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: 6464e1f2eb

ℹ️ 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".

if is_float {
emit_resolve_f64(&mut sink, constants, value_types, arg0);
emit_resolve_f64(&mut sink, constants, value_types, op.arg(1).to_opref());
sink.f64_ne();

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 Use bitwise comparison for float guard values

When GUARD_VALUE specializes a Float, this branch uses numeric f64_ne; for a promoted NaN the value is not equal to itself, so the trace guard will fail every time even when the runtime bits match the recorded value. Guard-value hotness/status records float raw bits and the frame/failarg ABI stores floats as i64 bit carriers, so the guard should compare the reinterpreted bits rather than f64 equality.

Useful? React with 👍 / 👎.

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