Skip to content
Merged
4 changes: 2 additions & 2 deletions majit/examples/tiny2/src/jit_interp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! JIT-enabled tiny2 interpreter via `#[jit_interp]` proc macro with `state_fields`.
//!
//! TODO: `rpython/jit/tl/tiny2_hotpath.py:90` models the
//! operand stack as a linked-list `Stack(value, next)`; each push allocates
//! Representation difference: `tiny2_hotpath.Stack` represents the operand
//! stack as a linked list of `Stack(value, next)` nodes. Each push allocates
//! one cons cell that RPython's JIT peels as a chain of virtuals. pyre's
//! `state_fields = { stackpos, stack: [int; virt] }` does not express
//! linked-list stacks — it requires a contiguous virtualizable array. The
Expand Down
12 changes: 7 additions & 5 deletions majit/examples/tiny3/src/jit_interp.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! JIT-enabled tiny3 interpreter via `#[jit_interp]` proc macro with `state_fields`.
//!
//! TODO: `rpython/jit/tl/tiny3_hotpath.py:96` models the
//! operand stack as a linked-list `Stack(value, next)`, identical shape to
//! tiny2_hotpath.py. pyre's `state_fields = { stackpos, stack: [int; virt] }`
//! does not express linked-list stacks — see the same adaptation note on
//! `majit/examples/tiny2/src/jit_interp.rs`.
//! Representation difference: `tiny3_hotpath.Stack` represents
//! the operand stack as a linked list of `Stack(value, next)` nodes. Each push
//! allocates one cons cell that RPython's JIT peels as a chain of virtuals.
//! pyre's `state_fields = { stackpos, stack: [int; virt] }` requires a
//! contiguous virtualizable array and cannot express that linked-list shape.
//! The array backing is a source-shape deviation, although its optimized trace
//! is equivalent for the shallow, constant-height stacks used by tiny3.
//!
//! Greens: [pc]
//! Reds: [stackpos, stack] (args at bottom, computation stack on top)
Expand Down
18 changes: 9 additions & 9 deletions majit/majit-backend-dynasm/src/aarch64/assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ const CC_GE: u8 = 11; // signed >=
const CC_LE: u8 = 12; // signed <=
const CC_G: u8 = 13; // signed >

/// Invert a condition code.
/// Widest value `cmp Xn, #imm` encodes — `codebuilder.py:389 CMP_ri`.
/// Widest value accepted by `codebuilder.py`'s `CMP_ri` encoding of
/// `cmp Xn, #imm`.
const MAX_CMP_IMM12: u32 = 4095;

/// Forward reach of `b.cond`: a signed 19-bit displacement in 4-byte words.
Expand Down Expand Up @@ -1077,8 +1077,8 @@ impl<'a> AssemblerARM64<'a> {
let l = self.load_loc_to_reg(lhs, 17);
let d = dst_reg;
// Rd/Rn take the SP-form register family for the `#imm12`
// encoding; `XSP(r)` encodes identically to `X(r)` for the
// non-SP registers the allocator hands out here.
// encoding; `XSP(r)` encodes identically to `X(r)` for every
// non-SP register allocated to this instruction.
match opcode {
OpCode::IntSub => {
dynasm!(self.mc ; .arch aarch64 ; sub XSP(d), XSP(l), im as u32)
Expand Down Expand Up @@ -1178,16 +1178,16 @@ impl<'a> AssemblerARM64<'a> {
}
_ => return,
};
// `opassembler.py:129 emit_int_comp_op` takes `CMP_ri` when the
// right-hand side is an immediate; `codebuilder.py:389 CMP_ri` holds
// `opassembler.py`'s `emit_int_comp_op` takes `CMP_ri` when the
// right-hand side is an immediate; `codebuilder.py`'s `CMP_ri` holds
// a 12-bit unsigned field, so anything wider still needs a register.
if let Loc::Immed(i) = loc1
&& let Ok(imm) = u32::try_from(i.value)
&& imm <= MAX_CMP_IMM12
{
// dynasm's `cmp Xn|SP, #uimm` form cannot take a dynamic
// register operand, so encode it the way
// `codebuilder.py:389 CMP_ri` does: SUBS with Rd = xzr.
// dynasm's `cmp Xn|SP, #uimm` form cannot take a dynamic register
// operand. `codebuilder.py`'s `CMP_ri` encodes the equivalent
// instruction as SUBS with Rd = xzr.
let word: u32 = (0b1111000100u32 << 22) | (imm << 10) | ((r0 as u32) << 5) | 0b11111;
dynasm!(self.mc ; .arch aarch64 ; .u32 word);
return;
Expand Down
23 changes: 11 additions & 12 deletions majit/majit-backend-dynasm/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2023,32 +2023,31 @@ impl DynasmBackend {
}
}

/// llsupport/regalloc.py:861-871 `_set_initial_bindings` parity:
/// Parity with `BaseRegalloc._set_initial_bindings`:
/// `_ll_initial_locs` stores `loc.value - base_ofs`, measured in bytes
/// from `FIRST_ITEM_OFFSET`, not input-order slot numbers.
fn input_initial_loc(position: usize) -> i32 {
(Self::input_slot(position) * crate::jitframe::SIZEOFSIGNED) as i32
}

/// llmodel.py:412 get_latest_descr parity: resolve a raw jf_descr
/// pointer to its `DescrRef`. Searches root loop fail_descrs
/// Resolve a raw `jf_descr` pointer to its `DescrRef`, as
/// `LLGraphCPU.get_latest_descr` does. Searches root loop fail descriptors
/// first, then all bridge fail_descrs stored in asmmemmgr_blocks.
/// RPython does this via AbstractDescr.show() which works for any
/// descr from any loop/bridge.
///
/// `compile.py:618-671` parity: the four `DoneWithThisFrame*`
/// `compile.py`'s four `DoneWithThisFrame*` descriptors
/// + `ExitFrameWithExceptionDescrRef` singletons attached to
/// `self.cpu` are compared by pointer identity against the raw
/// `jf_descr` value — same as RPython
/// `llgraph/runner.py:1478-1484` (`faildescr == self.cpu.done_with_this_frame_descr_*`).
/// `llgraph/runner.py`'s `LLGraphCPU.execute_token`
/// (`faildescr == self.cpu.done_with_this_frame_descr_*`).
///
/// Panics if not found — RPython uses object identity, so lookup
/// failure is impossible in well-formed execution.
///
/// `frame_ptr` is required so the `propagate_exception_descr` arm
/// can run the equivalent of `compile.py:1092-1098`'s
/// `cpu.grab_exc_value(deadframe)` — read `jf_guard_exc` (the grab is
/// read-only, `llmodel.py:240-242`; the clear alongside it is pyre's)
/// `frame_ptr` lets the `propagate_exception_descr` arm implement
/// `PropagateExceptionDescr.handle_fail`: read `jf_guard_exc`, clear it,
/// and stage the value into `jf_frame[0]` before synthesizing the
/// exit-frame-with-exception descr the toplevel consumer expects.
fn find_descr_by_ptr(
Expand All @@ -2058,7 +2057,7 @@ impl DynasmBackend {
frame_ptr: *mut JitFrame,
) -> majit_ir::DescrRef {
let attached = self.attached_descr_ptrs();
// compile.py:618-669 done_with_this_frame_descr — check all 4 variants.
// Check all four `DoneWithThisFrameDescr` variants.
// Forward through `meta_descr` so the metainterp class hierarchy
// (DoneWithThisFrameDescr{Void,Int,Ref,Float}) answers
// `is_finish` / `fail_arg_types` etc. via `compile.py:624 final_descr=True`.
Expand All @@ -2069,9 +2068,9 @@ impl DynasmBackend {
|| ptr == attached.done_with_this_frame_descr_float)
{
// Return the metainterp `DoneWithThisFrameDescr*` Arc directly.
// `compile.py:618-672` class hierarchy answers
// The `DoneWithThisFrameDescr` class hierarchy answers
// `is_finish`/`fail_arg_types` via its own FailDescr impl —
// no backend wrapper needed (Phase C-1 cascade endpoint).
// no backend wrapper is needed.
let att = self.descr_attachments.read().unwrap();
let meta = if ptr == attached.done_with_this_frame_descr_void {
att.done_with_this_frame_descr_void.clone()
Expand Down
69 changes: 25 additions & 44 deletions majit/majit-backend-wasm/tests/codegen_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ fn workspace_root() -> PathBuf {
.to_path_buf()
}

/// The wasm-host module these runtime tests measure.
///
/// Only the snapshot path, never the raw `pyre_wasm.wasm` cargo output: the
/// `web` and `wasm-host` features of `pyre-wasm` build to that one filename and
/// overwrite each other, so a tree that last built `web` leaves a module there
/// which loads and runs but is not the one whose counters these tests pin.
/// `check.py` copies the wasm-host build here for exactly that reason.
fn wasm_host_module(root: &Path) -> PathBuf {
root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm")
}

fn run_runtime_program(
binary: &Path,
script: &Path,
Expand Down Expand Up @@ -56,13 +67,7 @@ fn global_reassign_retraces_non_last_label_backedge_at_runtime() {
let root = workspace_root();
let dynasm = root.join("target/release/pyre-dynasm");
let wasm_runner = root.join("target/release/pyre-wasm-runner");
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
let wasm_module = if host_module.exists() {
host_module
} else {
plain_module
};
let wasm_module = wasm_host_module(&root);

for artifact in [&dynasm, &wasm_runner, &wasm_module] {
assert!(
Expand Down Expand Up @@ -118,13 +123,7 @@ fn raise_catch_clear_root_does_not_cross_the_host_per_exception() {
let root = workspace_root();
let dynasm = root.join("target/release/pyre-dynasm");
let wasm_runner = root.join("target/release/pyre-wasm-runner");
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
let wasm_module = if host_module.exists() {
host_module
} else {
plain_module
};
let wasm_module = wasm_host_module(&root);
let script = root.join("pyre/bench/raise_catch_loop.py");

for artifact in [&dynasm, &wasm_runner, &wasm_module] {
Expand Down Expand Up @@ -170,13 +169,7 @@ fn recursive_call_assembler_does_not_refill_zeroed_nursery_frames() {
let root = workspace_root();
let dynasm = root.join("target/release/pyre-dynasm");
let wasm_runner = root.join("target/release/pyre-wasm-runner");
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
let wasm_module = if host_module.exists() {
host_module
} else {
plain_module
};
let wasm_module = wasm_host_module(&root);
let script = root.join("pyre/bench/fib_recursive.py");

for artifact in [&dynasm, &wasm_runner, &wasm_module] {
Expand Down Expand Up @@ -209,8 +202,14 @@ fn recursive_call_assembler_does_not_refill_zeroed_nursery_frames() {
wasm_run.stdout, dynasm_run.stdout,
"wasm recursive fib output diverged from dynasm:\n{stderr}"
);
assert_eq!(stat_value(&stderr, "compiles"), 4);
assert_eq!(stat_value(&stderr, "BRIDGE_OK"), 3);
// `compiles` is the host's module-compile tally, one per loop and one per
// bridge, and `BRIDGE_OK` counts the bridges the backend accepted — the
// same event `bridges_compiled` counts, since both are bumped only on the
// `Ok` side of `compile_bridge`. So both follow from the committed
// `pyre/bench/fib_recursive.wasm.jitstats`: `loops_compiled=1` +
// `bridges_compiled=8`. Re-record these two alongside that baseline.
assert_eq!(stat_value(&stderr, "compiles"), 9);
assert_eq!(stat_value(&stderr, "BRIDGE_OK"), 8);
assert!(
!stderr.contains("memory.fill"),
"recursive CA still refills a nursery that is already zeroed:\n{stderr}"
Expand All @@ -224,13 +223,7 @@ fn fannkuch_blackhole_helpers_do_not_reflect_through_the_host() {
let root = workspace_root();
let dynasm = root.join("target/release/pyre-dynasm");
let wasm_runner = root.join("target/release/pyre-wasm-runner");
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
let wasm_module = if host_module.exists() {
host_module
} else {
plain_module
};
let wasm_module = wasm_host_module(&root);
let script = root.join("pyre/bench/fannkuch.py");
for artifact in [&dynasm, &wasm_runner, &wasm_module] {
assert!(
Expand Down Expand Up @@ -272,13 +265,7 @@ fn terminal_declined_call_assembler_matches_dynasm_at_runtime() {
let root = workspace_root();
let dynasm = root.join("target/release/pyre-dynasm");
let wasm_runner = root.join("target/release/pyre-wasm-runner");
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
let wasm_module = if host_module.exists() {
host_module
} else {
plain_module
};
let wasm_module = wasm_host_module(&root);
let script = root.join("pyre/bench/ca_terminal_decline.py");

for artifact in [&dynasm, &wasm_runner, &wasm_module] {
Expand Down Expand Up @@ -329,13 +316,7 @@ fn wasm_outlier_bridges_stay_compiled_at_runtime() {
let root = workspace_root();
let dynasm = root.join("target/release/pyre-dynasm");
let wasm_runner = root.join("target/release/pyre-wasm-runner");
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
let wasm_module = if host_module.exists() {
host_module
} else {
plain_module
};
let wasm_module = wasm_host_module(&root);

for artifact in [&dynasm, &wasm_runner, &wasm_module] {
assert!(
Expand Down
20 changes: 10 additions & 10 deletions majit/majit-metainterp/src/blackhole.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5029,29 +5029,29 @@ fn bhimpl_int_add(a: i64, b: i64) -> i64 {
a.wrapping_add(b)
}

/// blackhole.py:462-464 `bhimpl_int_sub(a, b): return intmask(a - b)`.
/// Port of `blackhole.bhimpl_int_sub`: `return intmask(a - b)`.
fn bhimpl_int_sub(a: i64, b: i64) -> i64 {
a.wrapping_sub(b)
}

/// blackhole.py:466-468 `bhimpl_int_mul(a, b): return intmask(a * b)`.
/// Port of `blackhole.bhimpl_int_mul`: `return intmask(a * b)`.
fn bhimpl_int_mul(a: i64, b: i64) -> i64 {
a.wrapping_mul(b)
}

/// RPython `rint.py:399-408 ll_int_py_div` (oopspec `int.py_div`).
/// Port of `rint.ll_int_py_div` (oopspec `int.py_div`).
/// The OS_INT_PY_DIV residual call lands here at runtime. The JIT
/// trace contains two explicit guards upstream of this call,
/// produced by the inlined `_ovf_zer` wrapper (`rint.py:429
/// ll_int_py_div_ovf_zer`):
/// produced by the inlined `rint.ll_int_py_div_ovf_zer` wrapper:
/// * `int_eq(rhs, 0) -> guard_false` (zero divisor),
/// * `int_and(int_eq(lhs, INT_MIN), int_eq(rhs, -1)) ->
/// guard_false` (overflow corner — `INT_MIN // -1` would
/// overflow to `INT_MIN` in two's-complement; PyPy
/// `intobject.py:316/491/804` routes this case through
/// `intobject.py`'s `_floordiv`, `descr_floordiv`, and `descr_rfloordiv`
/// route this case through
/// `ovf2long` to long arithmetic).
/// Other negative operand combinations are valid: PyPy
/// `intobject.py:316 _floordiv` only handles `ZeroDivisionError`,
/// `intobject.py`'s `_floordiv` only handles `ZeroDivisionError`,
/// and the no-branch floor correction
/// (`(a ^ b) < 0 && d * b != a -> d - 1`) yields Python-floor
/// semantics for every legal sign combination of `(a, b)`.
Expand All @@ -5063,8 +5063,8 @@ fn bhimpl_int_mul(a: i64, b: i64) -> i64 {
/// path: the `_ovf_zer` wrapper's `int_eq(rhs, 0) -> guard_false` and
/// `(lhs == INT_MIN) & (rhs == -1) -> guard_false` runtime guards
/// (emitted at `codegen.rs::generated_binary_int_value`) bail out the
/// trace before this helper is invoked, matching RPython's
/// `rint.py:429 ll_int_py_div_ovf_zer` shape. Direct (non-traced)
/// trace before this helper is invoked, matching
/// `rint.ll_int_py_div_ovf_zer`. Direct non-traced
/// callers must respect the same precondition.
///
/// `extern "C"`: the residual-call path
Expand All @@ -5084,7 +5084,7 @@ pub extern "C" fn ll_int_py_div(a: i64, b: i64) -> i64 {
}
}

/// RPython `rint.py:496-500 ll_int_py_mod` (oopspec `int.py_mod`).
/// Port of `rint.ll_int_py_mod` (oopspec `int.py_mod`).
/// See [`ll_int_py_div`] for the JIT-side runtime guard
/// rationale (`int_eq(rhs, 0)` + `(lhs == INT_MIN) & (rhs == -1)`).
/// Uses `wrapping_rem` for the C-style remainder step, then applies
Expand Down
29 changes: 8 additions & 21 deletions majit/majit-metainterp/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2891,22 +2891,10 @@ mod tests {
);
}
}
/// `compile.py:855` ResumeGuardDescr `_attrs_ = ('rd_numb', 'rd_consts',
/// 'rd_virtuals', 'rd_pendingfields', 'status')` — the per-guard
/// resume payload shared by every concrete `AbstractResumeGuardDescr`
/// subclass. Pyre stores them in `UnsafeCell` so the optimizer can
/// mutate the descr in place via `FailDescr::set_rd_*` without
/// breaking the `Arc<dyn FailDescr>` identity stamped on the op.
///
/// Each slot wraps `Arc<[T]>` so `copy_all_attributes_from`
/// (compile.py:861-867) — `self.rd_consts = other.rd_consts` etc. —
/// can mirror RPython's reference-share semantics with a single
/// `Arc::clone()` rather than a `Vec::clone()` that would deep-copy
/// the bytes. External setters still accept `Option<Vec<T>>`; the
/// conversion to `Arc<[T]>` is one move per (rare) write.
// RdPayload moved to majit-backend::rd_payload (Phase C-1
// preparatory step toward backend struct deletion). Re-export from
// here so existing `compile::RdPayload` references stay resolvable.
/// Re-export the backend-owned resume payload under its historical
/// `compile::RdPayload` path. [`copy_all_attributes_from`] shares the payload's
/// `Arc`-backed sections, matching `ResumeGuardDescr.copy_all_attributes_from`
/// without changing the descriptor identity stored on a guard operation.
pub use majit_backend::RdPayload;

fn push_vector_info(head: &mut Option<Box<AccumInfo>>, mut info: AccumInfo) {
Expand Down Expand Up @@ -5172,11 +5160,10 @@ pub fn make_compile_loop_version_descr_from(source_op: &majit_ir::Op) -> DescrRe
make_compile_loop_version_descr_with_payload(types, payload)
}

// Resume data for a guard now lives on `StoredExitLayout.resume_layout`
// (per-guard `ResumeLayoutSummary`) rather than a separate trace-side
// `HashMap<u32, ResumeData>`. See `pyjitpl.rs CompiledTrace` and the
// producers/readers below. This mirrors RPython's single guard-owned
// `ResumeGuardDescr` container (`compile.py:855`).
// `StoredExitLayout.resume_layout` is the canonical per-guard
// `ResumeLayoutSummary`: `build_guard_metadata` derives it from the guard
// descriptor, and backend exit recovery consumes it. This preserves RPython's
// single guard-owned `compile.ResumeGuardDescr` container.

//
// These are the **compile role** of `TraceCtx`, mirroring RPython's
Expand Down
Loading
Loading