Skip to content

Commit 153392c

Browse files
authored
sizeof pre-header and slot count, catch-landing coverage census, and a unicodedata allocation leak (#1195)
* sizeof: count a type's slots as its variable tail `object.__sizeof__` is `tp_basicsize + Py_SIZE(self) * tp_itemsize`, and the `nitems` chain answered 0 for a type object, so every class reported its basicsize alone. A type's variable tail is its `__slots__` member table, so `Py_SIZE` is the slot count. The pre-header half of this change is dropped: `#1174` added `cpython_object_is_gc`, which ports `_PyObject_IS_GC` including the `type_is_gc` refinement that answers with `Py_TPFLAGS_HEAPTYPE`, and so already charges a statically declared type no collector header. Assisted-by: Claude * jit: verify catch-landing coverage end to end under PYRE_CATCH_LIVE_CENSUS `catch_target_extra_ref_colors` exists so that every `catch_exception` finds its landing Ref colors in the marker its owning Python PC resumes at. The existing census counts the anchorless population; nothing checked the property itself once the markers were final. Add a pass that does, gated by the same knob: for each site, resolve the owner PC, read the finished marker, and report any landing color missing from it. Unreachable PCs are skipped -- their markers are cleared wholesale, so an empty set there is correct. Measured over 34 code objects of exception-shaped sources: 193 sites, 193 distinct owner PCs, 0 uncovered sites and 0 uncovered colors. The per-PC anchor table in `derive_after_call_indices_from_sparse` keeps one entry, which would drop a sibling site's colors, but no Python PC owns more than one site -- `catch_exception` is emitted once per canraise block exit and the extra catch links of a multi-exit block lower through `make_exception_link`, which emits none. Recorded on that function. Extract `catch_landing_ref_colors` so the new pass and the existing one read a landing the same way. Assisted-by: Claude * Make implementation comments self-contained * unicodedata: allocate the per-call result strings through the managed path `category`, `bidirectional`, `east_asian_width`, `decomposition`, `name` and `lookup` build a fresh string on every call and returned it through `w_str_new`, whose value buffer comes from `malloc_raw` -- a buffer the collector can never reclaim. Scanning a text one character at a time accumulated one such buffer per call. `w_str_new_managed` allocates a GC storage box when the interpreter collector and the value tid are both live, and falls back to immortal otherwise. The remaining `w_str_new` calls in the module stay: `unidata_version` and its siblings are module constants built once per process, and the others are in tests. Assisted-by: Claude * Remove stale and redundant implementation comments * wasm: pin the recursive-CA counters to the recorded baseline and drop the module fallback `recursive_call_assembler_does_not_refill_zeroed_nursery_frames` asserted `compiles == 4` and `BRIDGE_OK == 3`. The committed `pyre/bench/fib_recursive.wasm.jitstats` records `loops_compiled=1` and `bridges_compiled=8` for the same bench, and `compiles` is the host's module-compile tally over both, so it is 9; `BRIDGE_OK` and `bridges_compiled` count the same event, since `diag_bump(5)` and `self.stats.bridges_compiled += 1` both sit on the `Ok` side of `compile_bridge`, so it is 8. `fannkuch_blackhole_helpers_do_not_reflect_through_the_host` already follows that relation: its `compiles == 28` is `6 + 22` from `fannkuch.wasm.jitstats`. All six runtime tests picked `pyre_wasm.wasm` when `pyre_wasm.wasm-host.wasm` was absent. `pyre-wasm` builds both its `web` and `wasm-host` features to that one filename, so the fallback can load a `web` module while the assertions pin wasm-host counters. They now read the snapshot path only, through one helper. Assisted-by: Claude * cpython_tests: carry each failing case's exception line in the digest `failure_digest` listed unittest's `FAIL:`/`ERROR:` headers, which name the case but not the cause. A case that fails only on the CI host cannot be re-run locally to find out, so the header alone left the run diagnosable only by another CI cycle. Each header now carries the line its traceback ended on, and the FAIL detail cap rises from 300 to 900 to fit four of them. Assisted-by: Claude * parity: cover a module name carrying a lone surrogate `#1182` made `Module.w_name` a `PyObjectRef`, so a unit test round-tripping the name through the pyre-object accessors now only restates what the type already guarantees. The path that can still regress is the interpreter's: `module.__init__` projecting the argument through `w_str_get_value` panics on a lone surrogate, which is what the import machinery hands it whenever a filename was decoded with surrogateescape. Cover it where it lives, as a parity fixture over construction, `__init__` re-seeding, `repr` and dict-key lookup. Verified against CPython and pypy3. Assisted-by: Claude * cpython_tests: report the last link of a chained traceback `traceback_verdict` returned the first unindented line after the header, so a test whose failure chained through `raise ... from` reported the inner cause rather than the exception it actually failed with. Taking the block's last unindented line instead would break the other shape: an assertion failure prints its diff below the `AssertionError`, unindented. Arm the search on each `Traceback` banner and let the next unindented line answer for that link, so a chain's later links overwrite the earlier ones while a diff below the answer is ignored. Assisted-by: Claude
1 parent b0f34c0 commit 153392c

34 files changed

Lines changed: 552 additions & 529 deletions

File tree

‎majit/examples/tiny2/src/jit_interp.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! JIT-enabled tiny2 interpreter via `#[jit_interp]` proc macro with `state_fields`.
22
//!
3-
//! TODO: `rpython/jit/tl/tiny2_hotpath.py:90` models the
4-
//! operand stack as a linked-list `Stack(value, next)`; each push allocates
3+
//! Representation difference: `tiny2_hotpath.Stack` represents the operand
4+
//! stack as a linked list of `Stack(value, next)` nodes. Each push allocates
55
//! one cons cell that RPython's JIT peels as a chain of virtuals. pyre's
66
//! `state_fields = { stackpos, stack: [int; virt] }` does not express
77
//! linked-list stacks — it requires a contiguous virtualizable array. The

‎majit/examples/tiny3/src/jit_interp.rs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//! JIT-enabled tiny3 interpreter via `#[jit_interp]` proc macro with `state_fields`.
22
//!
3-
//! TODO: `rpython/jit/tl/tiny3_hotpath.py:96` models the
4-
//! operand stack as a linked-list `Stack(value, next)`, identical shape to
5-
//! tiny2_hotpath.py. pyre's `state_fields = { stackpos, stack: [int; virt] }`
6-
//! does not express linked-list stacks — see the same adaptation note on
7-
//! `majit/examples/tiny2/src/jit_interp.rs`.
3+
//! Representation difference: `tiny3_hotpath.Stack` represents
4+
//! the operand stack as a linked list of `Stack(value, next)` nodes. Each push
5+
//! allocates one cons cell that RPython's JIT peels as a chain of virtuals.
6+
//! pyre's `state_fields = { stackpos, stack: [int; virt] }` requires a
7+
//! contiguous virtualizable array and cannot express that linked-list shape.
8+
//! The array backing is a source-shape deviation, although its optimized trace
9+
//! is equivalent for the shallow, constant-height stacks used by tiny3.
810
//!
911
//! Greens: [pc]
1012
//! Reds: [stackpos, stack] (args at bottom, computation stack on top)

‎majit/majit-backend-dynasm/src/aarch64/assembler.rs‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ const CC_GE: u8 = 11; // signed >=
210210
const CC_LE: u8 = 12; // signed <=
211211
const CC_G: u8 = 13; // signed >
212212

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

217217
/// Forward reach of `b.cond`: a signed 19-bit displacement in 4-byte words.
@@ -1077,8 +1077,8 @@ impl<'a> AssemblerARM64<'a> {
10771077
let l = self.load_loc_to_reg(lhs, 17);
10781078
let d = dst_reg;
10791079
// Rd/Rn take the SP-form register family for the `#imm12`
1080-
// encoding; `XSP(r)` encodes identically to `X(r)` for the
1081-
// non-SP registers the allocator hands out here.
1080+
// encoding; `XSP(r)` encodes identically to `X(r)` for every
1081+
// non-SP register allocated to this instruction.
10821082
match opcode {
10831083
OpCode::IntSub => {
10841084
dynasm!(self.mc ; .arch aarch64 ; sub XSP(d), XSP(l), im as u32)
@@ -1178,16 +1178,16 @@ impl<'a> AssemblerARM64<'a> {
11781178
}
11791179
_ => return,
11801180
};
1181-
// `opassembler.py:129 emit_int_comp_op` takes `CMP_ri` when the
1182-
// right-hand side is an immediate; `codebuilder.py:389 CMP_ri` holds
1181+
// `opassembler.py`'s `emit_int_comp_op` takes `CMP_ri` when the
1182+
// right-hand side is an immediate; `codebuilder.py`'s `CMP_ri` holds
11831183
// a 12-bit unsigned field, so anything wider still needs a register.
11841184
if let Loc::Immed(i) = loc1
11851185
&& let Ok(imm) = u32::try_from(i.value)
11861186
&& imm <= MAX_CMP_IMM12
11871187
{
1188-
// dynasm's `cmp Xn|SP, #uimm` form cannot take a dynamic
1189-
// register operand, so encode it the way
1190-
// `codebuilder.py:389 CMP_ri` does: SUBS with Rd = xzr.
1188+
// dynasm's `cmp Xn|SP, #uimm` form cannot take a dynamic register
1189+
// operand. `codebuilder.py`'s `CMP_ri` encodes the equivalent
1190+
// instruction as SUBS with Rd = xzr.
11911191
let word: u32 = (0b1111000100u32 << 22) | (imm << 10) | ((r0 as u32) << 5) | 0b11111;
11921192
dynasm!(self.mc ; .arch aarch64 ; .u32 word);
11931193
return;

‎majit/majit-backend-dynasm/src/runner.rs‎

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,32 +2023,31 @@ impl DynasmBackend {
20232023
}
20242024
}
20252025

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

2033-
/// llmodel.py:412 get_latest_descr parity: resolve a raw jf_descr
2034-
/// pointer to its `DescrRef`. Searches root loop fail_descrs
2033+
/// Resolve a raw `jf_descr` pointer to its `DescrRef`, as
2034+
/// `LLGraphCPU.get_latest_descr` does. Searches root loop fail descriptors
20352035
/// first, then all bridge fail_descrs stored in asmmemmgr_blocks.
20362036
/// RPython does this via AbstractDescr.show() which works for any
20372037
/// descr from any loop/bridge.
20382038
///
2039-
/// `compile.py:618-671` parity: the four `DoneWithThisFrame*`
2039+
/// `compile.py`'s four `DoneWithThisFrame*` descriptors
20402040
/// + `ExitFrameWithExceptionDescrRef` singletons attached to
20412041
/// `self.cpu` are compared by pointer identity against the raw
20422042
/// `jf_descr` value — same as RPython
2043-
/// `llgraph/runner.py:1478-1484` (`faildescr == self.cpu.done_with_this_frame_descr_*`).
2043+
/// `llgraph/runner.py`'s `LLGraphCPU.execute_token`
2044+
/// (`faildescr == self.cpu.done_with_this_frame_descr_*`).
20442045
///
20452046
/// Panics if not found — RPython uses object identity, so lookup
20462047
/// failure is impossible in well-formed execution.
20472048
///
2048-
/// `frame_ptr` is required so the `propagate_exception_descr` arm
2049-
/// can run the equivalent of `compile.py:1092-1098`'s
2050-
/// `cpu.grab_exc_value(deadframe)` — read `jf_guard_exc` (the grab is
2051-
/// read-only, `llmodel.py:240-242`; the clear alongside it is pyre's)
2049+
/// `frame_ptr` lets the `propagate_exception_descr` arm implement
2050+
/// `PropagateExceptionDescr.handle_fail`: read `jf_guard_exc`, clear it,
20522051
/// and stage the value into `jf_frame[0]` before synthesizing the
20532052
/// exit-frame-with-exception descr the toplevel consumer expects.
20542053
fn find_descr_by_ptr(
@@ -2058,7 +2057,7 @@ impl DynasmBackend {
20582057
frame_ptr: *mut JitFrame,
20592058
) -> majit_ir::DescrRef {
20602059
let attached = self.attached_descr_ptrs();
2061-
// compile.py:618-669 done_with_this_frame_descr — check all 4 variants.
2060+
// Check all four `DoneWithThisFrameDescr` variants.
20622061
// Forward through `meta_descr` so the metainterp class hierarchy
20632062
// (DoneWithThisFrameDescr{Void,Int,Ref,Float}) answers
20642063
// `is_finish` / `fail_arg_types` etc. via `compile.py:624 final_descr=True`.
@@ -2069,9 +2068,9 @@ impl DynasmBackend {
20692068
|| ptr == attached.done_with_this_frame_descr_float)
20702069
{
20712070
// Return the metainterp `DoneWithThisFrameDescr*` Arc directly.
2072-
// `compile.py:618-672` class hierarchy answers
2071+
// The `DoneWithThisFrameDescr` class hierarchy answers
20732072
// `is_finish`/`fail_arg_types` via its own FailDescr impl —
2074-
// no backend wrapper needed (Phase C-1 cascade endpoint).
2073+
// no backend wrapper is needed.
20752074
let att = self.descr_attachments.read().unwrap();
20762075
let meta = if ptr == attached.done_with_this_frame_descr_void {
20772076
att.done_with_this_frame_descr_void.clone()

‎majit/majit-backend-wasm/tests/codegen_test.rs‎

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ fn workspace_root() -> PathBuf {
2525
.to_path_buf()
2626
}
2727

28+
/// The wasm-host module these runtime tests measure.
29+
///
30+
/// Only the snapshot path, never the raw `pyre_wasm.wasm` cargo output: the
31+
/// `web` and `wasm-host` features of `pyre-wasm` build to that one filename and
32+
/// overwrite each other, so a tree that last built `web` leaves a module there
33+
/// which loads and runs but is not the one whose counters these tests pin.
34+
/// `check.py` copies the wasm-host build here for exactly that reason.
35+
fn wasm_host_module(root: &Path) -> PathBuf {
36+
root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm")
37+
}
38+
2839
fn run_runtime_program(
2940
binary: &Path,
3041
script: &Path,
@@ -56,13 +67,7 @@ fn global_reassign_retraces_non_last_label_backedge_at_runtime() {
5667
let root = workspace_root();
5768
let dynasm = root.join("target/release/pyre-dynasm");
5869
let wasm_runner = root.join("target/release/pyre-wasm-runner");
59-
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
60-
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
61-
let wasm_module = if host_module.exists() {
62-
host_module
63-
} else {
64-
plain_module
65-
};
70+
let wasm_module = wasm_host_module(&root);
6671

6772
for artifact in [&dynasm, &wasm_runner, &wasm_module] {
6873
assert!(
@@ -118,13 +123,7 @@ fn raise_catch_clear_root_does_not_cross_the_host_per_exception() {
118123
let root = workspace_root();
119124
let dynasm = root.join("target/release/pyre-dynasm");
120125
let wasm_runner = root.join("target/release/pyre-wasm-runner");
121-
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
122-
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
123-
let wasm_module = if host_module.exists() {
124-
host_module
125-
} else {
126-
plain_module
127-
};
126+
let wasm_module = wasm_host_module(&root);
128127
let script = root.join("pyre/bench/raise_catch_loop.py");
129128

130129
for artifact in [&dynasm, &wasm_runner, &wasm_module] {
@@ -170,13 +169,7 @@ fn recursive_call_assembler_does_not_refill_zeroed_nursery_frames() {
170169
let root = workspace_root();
171170
let dynasm = root.join("target/release/pyre-dynasm");
172171
let wasm_runner = root.join("target/release/pyre-wasm-runner");
173-
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
174-
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
175-
let wasm_module = if host_module.exists() {
176-
host_module
177-
} else {
178-
plain_module
179-
};
172+
let wasm_module = wasm_host_module(&root);
180173
let script = root.join("pyre/bench/fib_recursive.py");
181174

182175
for artifact in [&dynasm, &wasm_runner, &wasm_module] {
@@ -209,8 +202,14 @@ fn recursive_call_assembler_does_not_refill_zeroed_nursery_frames() {
209202
wasm_run.stdout, dynasm_run.stdout,
210203
"wasm recursive fib output diverged from dynasm:\n{stderr}"
211204
);
212-
assert_eq!(stat_value(&stderr, "compiles"), 4);
213-
assert_eq!(stat_value(&stderr, "BRIDGE_OK"), 3);
205+
// `compiles` is the host's module-compile tally, one per loop and one per
206+
// bridge, and `BRIDGE_OK` counts the bridges the backend accepted — the
207+
// same event `bridges_compiled` counts, since both are bumped only on the
208+
// `Ok` side of `compile_bridge`. So both follow from the committed
209+
// `pyre/bench/fib_recursive.wasm.jitstats`: `loops_compiled=1` +
210+
// `bridges_compiled=8`. Re-record these two alongside that baseline.
211+
assert_eq!(stat_value(&stderr, "compiles"), 9);
212+
assert_eq!(stat_value(&stderr, "BRIDGE_OK"), 8);
214213
assert!(
215214
!stderr.contains("memory.fill"),
216215
"recursive CA still refills a nursery that is already zeroed:\n{stderr}"
@@ -224,13 +223,7 @@ fn fannkuch_blackhole_helpers_do_not_reflect_through_the_host() {
224223
let root = workspace_root();
225224
let dynasm = root.join("target/release/pyre-dynasm");
226225
let wasm_runner = root.join("target/release/pyre-wasm-runner");
227-
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
228-
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
229-
let wasm_module = if host_module.exists() {
230-
host_module
231-
} else {
232-
plain_module
233-
};
226+
let wasm_module = wasm_host_module(&root);
234227
let script = root.join("pyre/bench/fannkuch.py");
235228
for artifact in [&dynasm, &wasm_runner, &wasm_module] {
236229
assert!(
@@ -272,13 +265,7 @@ fn terminal_declined_call_assembler_matches_dynasm_at_runtime() {
272265
let root = workspace_root();
273266
let dynasm = root.join("target/release/pyre-dynasm");
274267
let wasm_runner = root.join("target/release/pyre-wasm-runner");
275-
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
276-
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
277-
let wasm_module = if host_module.exists() {
278-
host_module
279-
} else {
280-
plain_module
281-
};
268+
let wasm_module = wasm_host_module(&root);
282269
let script = root.join("pyre/bench/ca_terminal_decline.py");
283270

284271
for artifact in [&dynasm, &wasm_runner, &wasm_module] {
@@ -329,13 +316,7 @@ fn wasm_outlier_bridges_stay_compiled_at_runtime() {
329316
let root = workspace_root();
330317
let dynasm = root.join("target/release/pyre-dynasm");
331318
let wasm_runner = root.join("target/release/pyre-wasm-runner");
332-
let host_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm-host.wasm");
333-
let plain_module = root.join("target/wasm32-unknown-unknown/release/pyre_wasm.wasm");
334-
let wasm_module = if host_module.exists() {
335-
host_module
336-
} else {
337-
plain_module
338-
};
319+
let wasm_module = wasm_host_module(&root);
339320

340321
for artifact in [&dynasm, &wasm_runner, &wasm_module] {
341322
assert!(

‎majit/majit-metainterp/src/blackhole.rs‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5029,29 +5029,29 @@ fn bhimpl_int_add(a: i64, b: i64) -> i64 {
50295029
a.wrapping_add(b)
50305030
}
50315031

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

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

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

5087-
/// RPython `rint.py:496-500 ll_int_py_mod` (oopspec `int.py_mod`).
5087+
/// Port of `rint.ll_int_py_mod` (oopspec `int.py_mod`).
50885088
/// See [`ll_int_py_div`] for the JIT-side runtime guard
50895089
/// rationale (`int_eq(rhs, 0)` + `(lhs == INT_MIN) & (rhs == -1)`).
50905090
/// Uses `wrapping_rem` for the C-style remainder step, then applies

‎majit/majit-metainterp/src/compile.rs‎

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2891,22 +2891,10 @@ mod tests {
28912891
);
28922892
}
28932893
}
2894-
/// `compile.py:855` ResumeGuardDescr `_attrs_ = ('rd_numb', 'rd_consts',
2895-
/// 'rd_virtuals', 'rd_pendingfields', 'status')` — the per-guard
2896-
/// resume payload shared by every concrete `AbstractResumeGuardDescr`
2897-
/// subclass. Pyre stores them in `UnsafeCell` so the optimizer can
2898-
/// mutate the descr in place via `FailDescr::set_rd_*` without
2899-
/// breaking the `Arc<dyn FailDescr>` identity stamped on the op.
2900-
///
2901-
/// Each slot wraps `Arc<[T]>` so `copy_all_attributes_from`
2902-
/// (compile.py:861-867) — `self.rd_consts = other.rd_consts` etc. —
2903-
/// can mirror RPython's reference-share semantics with a single
2904-
/// `Arc::clone()` rather than a `Vec::clone()` that would deep-copy
2905-
/// the bytes. External setters still accept `Option<Vec<T>>`; the
2906-
/// conversion to `Arc<[T]>` is one move per (rare) write.
2907-
// RdPayload moved to majit-backend::rd_payload (Phase C-1
2908-
// preparatory step toward backend struct deletion). Re-export from
2909-
// here so existing `compile::RdPayload` references stay resolvable.
2894+
/// Re-export the backend-owned resume payload under its historical
2895+
/// `compile::RdPayload` path. [`copy_all_attributes_from`] shares the payload's
2896+
/// `Arc`-backed sections, matching `ResumeGuardDescr.copy_all_attributes_from`
2897+
/// without changing the descriptor identity stored on a guard operation.
29102898
pub use majit_backend::RdPayload;
29112899

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

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

51815168
//
51825169
// These are the **compile role** of `TraceCtx`, mirroring RPython's

0 commit comments

Comments
 (0)