Repository navigation
jit: delete the retired MIFrame value-op layer, trait, and parent_frames (Phase-6 S4+S5) - #686
Conversation
|
Warning Review limit reached
Next review available in: 56 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (19)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit 41a1aff). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patchNone. 2. Other mismatches introduced by this patchNone. 3. Pre-existing mismatches (already present before this patch)
4. Structural adaptations
|
141459c to
d224f03
Compare
…-6 S4-core) After S1-S3 retired the MIFrame trait-driven dispatch, this value-op cluster had no production callers (the FBW walker uses walker-native fast-paths). Delete: - impl TraceHelperAccess for MIFrame (trace_opcode.rs). - the inherent MIFrame value-op bridge methods (binary/compare/subscr, iter_next, unpack_sequence, build_tuple, direct_len/abs/type/isinstance/minmax, truth/unary, store_subscr, store_local/delete_local, trace_known_builtin_call, handle_reraise/handle_raise_varargs, seed_raised_exception, finishframe_exception, build_pending_inline_frame, inline_function_call, try_trace_exception_new, and the vable/vref/stack push/pop/peek/swap helpers). - the six dead capture_* facades (capture_record_branch_guard, capture_concrete_branch_truth_for_value, capture_trace_dynamic_list_index, capture_generated_list_getitem_by_strategy, capture_direct_len_value, capture_iter_next). - state.rs box_value_for_python_helper / box_args_for_python_helper. - the 16 MIFrame-typed generated_* fns in typed_trace.rs + opimpl_check_neg_index. The WalkerFrameOps-generic generated_* fns, the shared helpers (load_items_block, check_index_in_bounds, list_len_descr_for_strategy), and the production MIFrame methods (load_local_value, trace_guarded_int_payload, concrete_at, generate_guard, close_loop_args_at, ...) stay. Delete nine unit tests that exercised the removed methods. Retarget test_branch_guard_preserves_pre_pop_stack_shape and test_branch_truth_uses_concrete_parameter to their capture_generate_guard + snapshot assertions, dropping the removed capture_record_branch_guard / capture_concrete_branch_truth_for_value arms. The TraceHelperAccess trait and the emit_trace_* helpers are retired in a follow-up. cargo check --all-targets clean; cargo test green; check.py dynasm 225/225, cranelift 225/225. Assisted-by: Claude
…ayer (Phase-6 S4-comments) Update comments across the trace/jit tree (and two majit files) that named symbols deleted in S4-core (generated_* typed fns, trace_build_tuple_value, trace_plain_int_payload, compare_value_direct, binary_int_value, seed_raised_exception, build_pending_inline_frame, try_trace_exception_new, emit_call_assembler_callee_frame, box_value_for_python_helper, ...) so no comment references a removed symbol. Where a comment cited a deleted fn as the structural model for surviving walker-native code, keep the structural description and retire-tag or drop the symbol name. record_branch_guard / store_local_value references naming the surviving pyre-interpreter trait methods are left; only the trace_opcode / MIFrame references are updated. The majit-translate codegen path reference and the pyjitpl.py upstream references stay. Comment-only; cargo check green. Assisted-by: Claude
…ase-6 S4-trait)
The `impl TraceHelperAccess for MIFrame` was removed in S4-core, leaving the
`TraceHelperAccess` trait with no implementor. Its default-method bodies were
the sole callers of a set of `emit_trace_*` free fns in helpers.rs.
Delete:
- the `pub trait TraceHelperAccess` definition (helpers.rs)
- 22 `emit_trace_*` free fns whose only callers were the deleted trait bodies
- the now-dead private fn `trace_name_args` and 11 private imports it used
- the orphaned `use crate::helpers::TraceHelperAccess;` in the state.rs test module
Kept the 9 emit fns with walker callers, and
emit_trace_call_{int,ref}_typed_elidable_cannot_raise, which
tests/elidable_helper_canary_test.rs exercises directly.
Assisted-by: Claude
…ase-6 S5) MIFrame.parent_frames was populated only by the retired trait-interpret leg; the FBW walker builds its multi-frame resume snapshot through PendingInlineFrame / InlineParentFrame instead. The field was always empty on every production path, so its consumers were dead: - generate_guard's `if !self.parent_frames.is_empty()` multi-frame snapshot branch (never taken) - concrete_valuestackdepth's `if !self.parent_frames.is_empty()` early return (always false) - build_framestack_frames / extend_types_with_parents and the materialize_parent_frame_state / materialize_parent_snapshot_state helpers they called Remove the field, its 12 `Vec::new()` initializers, the four dead helpers, and the two dead guard branches. The single-frame snapshot path (build_framestack_snapshot) is unchanged. PendingInlineFrame's own `parent_frames` (the live walker inline-parent path) is untouched. Assisted-by: Claude
MIFrame::store_token_in_vable had no callers; the FBW walker emits the force-token setfield + GUARD_NOT_FORCED_2 through fbw_store_token_in_vable (jitcode_dispatch/fbw_state.rs) instead. The wrapped TraceCtx helper store_token_in_vable_setfield stays live (called by the FBW port). Assisted-by: Claude
d224f03 to
d421d77
Compare
The extracted `_unpackiterable_unknown_length` jitcode carries its `jit_merge_point` at pc 0x5b. Update the hard-coded value in `UnpackSym::loop_header_pc` and the `jd1_build_time_descrs_resolve_through_global_pool` assertion, which both read 0x5c. Assisted-by: Claude
`should_inline` / `should_inline_with_ctx` / `should_inline_core` have had no callers since the MIFrame value-op layer was retired in #686; the FBW walker runs the same gate independently. They stay because they are the only metainterp-side home of two pieces of `_opimpl_recursive_call` that the live `recursive_decision` closure does not carry: the `dont_trace_here` side effect it defers until its producer slice is wired, and the not-tracing arm. A sweep of every in-tree and out-of-workspace consumer of majit-metainterp (aheui-jit, cel-jit, the vendored aheui) finds no reference to any of the three. Assisted-by: Claude
…es (#1266) * jit: widen the walker CALL_ASSEMBLER fold past four pyre-local declines `try_walker_call_assembler_self_recursive` declined on four conditions that `_opimpl_recursive_call` / `do_residual_call` (`pyjitpl.py`) do not carry. Each is replaced by the narrower question it was standing in for: - Callee identity: a callee that is neither the walk's own code nor already on the framestack was refused outright. It is now admitted when its cell carries `JC_DONT_TRACE_HERE` (`can_inline_callable` false), the condition upstream puts on `assembler_call`, and its body carries no `raise`, no exception table and no `abort_permanent` marker — the same body facts the inline route screens on. - Caller exception table: the fold asked whether the caller's body held a `catch_exception` anywhere. It now asks whether the CALL's own coordinate falls inside a protected region, excluding the whole-body depth-zero `lasti` entry 3.14 wraps around every generator, per the discrimination `code_yields_inside_try` (`pycode.rs`) already makes. - Executed-body residual: the latch that declines a fold after a foreign non-pure residual ran now exempts the fold's own concrete-stamp executor by scope (`SELFREC_CA_FOLD_ACTIVE`) rather than by callee code identity, so a successful fold no longer latches the gate against later calls in the same walk. - Loop-carried operands: an InputArg below the call declined the fold regardless of what it held. A numeric accumulator no longer does; a live iterator still does, because folding under one costs the caller a compiled loop and a bridge on `recursive_call_frame_relocation`. Measured on dynasm, same tree, interleaved: a depth-8 recursion called a million times from a `while` body goes 1.21s -> 0.06s, depth-9 0.74s -> 0.06s; `recursive_call_frame_relocation`, `fib_recursive`, `generator_tree_recursion` and `foriter_call_resume_drops_iteration` are unchanged. `pyre/check.py` is 436/436 on both backends. Three synthetic jit-stats baselines are re-recorded for lower `guard_failures`: `ca_bridge_multiframe_resume_double_call` 3437 -> 3369, `recursion_memo_branch` 3554 -> 3491, `recursive_forced_frame_kept_stack` 800 -> 600. Assisted-by: Claude * bench/synth: add a recursion past the unroll bound driven from a loop body `step` recurses nine frames deep, two past FBW_MAX_INLINE_RECURSION, and is called from a `while` body that keeps a numeric accumulator on the value stack under the call. No existing fixture holds that pair: in `fib_recursive` and `selfrec_bridge_nontail_promote` the recursion is itself the hot loop, and `recursive_call_frame_relocation` puts a `FOR_ITER` iterator below the call instead of a numeric operand. Records `max-pypy-ratio=6` and the three backend jit-stats baselines (loops_compiled=2, bridges_compiled=4, guard_failures=651 on all three). Assisted-by: Claude * comments: correct three claims about upstream's recursive-call path `inline_call.rs` said `foreign_callee_admits_call_assembler` "reproduces the condition `_opimpl_recursive_call` puts on `assembler_call`". pyjitpl.py:1417 puts no condition there — once control reaches the fall-through it emits unconditionally. The screen is this fold's own precondition, which is what the function's own doc comment already says. The same correction applies to the message of 85d1621, which describes the foreign admission as requiring `JC_DONT_TRACE_HERE`; the gate reads body facts only and does not consult `can_inline_callable`. `decide_recursive_inline` and the `recursive_call` dispatcher both said "There is no `compile_tmp_callback` here". It is ported in `compile.rs` and two seams synthesise through it (`direct_assembler_call`, `get_or_make_portal_assembler_token_arc`). What is actually absent is a route from those seams to this decision: the predicate is pure in its five scalars, and the dispatcher reaches token resolution only through `Runtime::recursive_call_assembler_target`. Assisted-by: Claude * pyjitpl: record why should_inline is kept without callers `should_inline` / `should_inline_with_ctx` / `should_inline_core` have had no callers since the MIFrame value-op layer was retired in #686; the FBW walker runs the same gate independently. They stay because they are the only metainterp-side home of two pieces of `_opimpl_recursive_call` that the live `recursive_decision` closure does not carry: the `dont_trace_here` side effect it defers until its producer slice is wired, and the not-tracing arm. A sweep of every in-tree and out-of-workspace consumer of majit-metainterp (aheui-jit, cel-jit, the vendored aheui) finds no reference to any of the three. Assisted-by: Claude * jit: emit the top-level frame's traceback node into the trace A trace that finishes with `exit_frame_with_exception` recorded its own root frame's traceback node only for the recording pass; the compiled trace carried no runtime record, on the premise that the interpreter would record it when the trace handed back the error. That premise holds only for a trace the interpreter entered. A CALL_ASSEMBLER entry comes from another trace's compiled code, and the descr dispatches to `handle_fail_exit_frame_with_exception`, which republishes the exception and returns into the caller's machine code — no interpreter sees the error, so the entered frame contributed no node. The traceback was one frame short per CALL_ASSEMBLER entry, i.e. one per `FBW_MAX_INLINE_RECURSION + 1` levels of a recursion the walker folds: `rec(9)` ending in `1 // 0` gave 10 `rec` frames interpreted and 9 compiled. `fbw_publish_exit_last_instr` already publishes the raise coordinate the node needs, and the emitted recorder falls back to that same field. The interpreter-entry case stays at one node: `screen_frame_already_recorded` clears `attach_tb` when the chain head already names the frame. Assisted-by: Claude * bench/synth: re-record three improved wasm jit-stats baselines guard_failures fell on wasm the same way it did on the two native backends when the walker CALL_ASSEMBLER fold widened: ca_bridge_multiframe_resume_double_call 3437 -> 3369, recursion_memo_branch 3554 -> 3491, recursive_forced_frame_kept_stack 800 -> 678. The native baselines were recorded in 85d1621; these are the wasm halves. Assisted-by: Claude * jit: emit the exit-frame traceback node after the vable store-back The runtime half of the node was emitted beside the recording half, ahead of `fbw_publish_exit_last_instr` and `fbw_force_virtualizable_before_return`. It is the only consumer of the frame on that arm, so reading the frame there moved the escape ahead of the store-back: wasm synth/comprehension_object_append_hot went bridges_compiled 14 -> 17, guard_failures 2810 -> 3410. Emitting it after the store-back leaves the traceback unchanged — the publish has already settled `last_instr`, which the recorder falls back to — and returns that bench to its recorded baseline. Assisted-by: Claude
Phase-6 — delete the retired MIFrame trait-driven meta-interpreter (S4 + S5)
The FBW walker (
jitcode_dispatch) is the sole production tracer; it neverdispatches opcodes through the MIFrame trait impls, and it builds its own
multi-frame resume snapshot through
PendingInlineFrame/InlineParentFrame.This branch deletes the now-dead trait-leg.
MIFramethe type staysproduction-live — only its trait impls, the orphaned value-op layer, and the
always-empty parent-frame chain are removed.
Commits:
impl TraceHelperAccess for MIFrame, the inherentMIFrame value-op methods, and the 16+1 MIFrame-typed
generated_*(&mut MIFrame)fns in
typed_trace.rs. Keep theWalkerFrameOps-genericgenerated_*<F: WalkerFrameOps>(production) plus the shared helpers.TraceHelperAccesstrait andthe 22
emit_trace_*free fns whose only callers were its default bodies.Keep the 9 walker-called emit fns and
emit_trace_call_{int,ref}_typed_elidable_cannot_raise(exercised byelidable_helper_canary_test.rs).MIFrame.parent_framesmulti-frame chain: thefield, its 12
Vec::new()initializers, the four dead helpers(
extend_types_with_parents,build_framestack_frames,materialize_parent_frame_state,materialize_parent_snapshot_state), andthe two always-dead guard branches (
generate_guard's!is_empty()multi-framesnapshot path and
concrete_valuestackdepth's!is_empty()early return). Thesingle-frame
build_framestack_snapshotpath is unchanged; the live walkerPendingInlineFrame/InlineParentFramepath is untouched.MIFrame::store_token_in_vable(no callers; the walker emits the force-token setfield +
GUARD_NOT_FORCED_2through
fbw_store_token_in_vable).Rebased onto
origin/main(#646, the WalkSym walker genericization). An earlierGC-root stopgap commit on this branch was dropped: #684 (and the #646 merge)
landed the canonical immortal
register_pyre_class_offsetsregistration for the_collectionsdeque iterators (and_tokenizeiterator) onmain, so thebranch carries no GC-registration change.
Verification
cargo check -p pyre-jit-trace -p pyre-jit -p pyre-interpreter --all-targets --features dynasm— 0 errors.cargo test(CI--all --no-default-features --features dynasm) — green. A pre-existing off-by-one in jit: genericize the trace walker over WalkSym; unify interpreter/GC subclass ranges (#205 C2) #646's dormant jd1 unit test (unpack_state::tests::jd1_build_time_descrs_resolve_through_global_pool) is corrected here: the extracted_unpackiterable_unknown_lengthmerge point sits at pc0x5b, but the test andUnpackSym::loop_header_pchard-coded0x5c(this test was already red on all three OSes at jit: genericize the trace walker over WalkSym; unify interpreter/GC subclass ranges (#205 C2) #646's own merge). Unrelated to the MIFrame deletion — this branch touches no census/decode/LLBC machinery; the correction just re-syncs the hard-coded constant to the fresh-extraction value.python3 pyre/check.py— dynasm 241/241 (local macOS/aarch64 dynasm+cranelift, and CI ubuntu dynasm). The CI ubuntu-x86_64 run shows three pre-existing, non-regression failures also present onmain/jit: genericize the trace walker over WalkSym; unify interpreter/GC subclass ranges (#205 C2) #646 and outside this diff's scope (this branch touches no wasm/cranelift/synth/perf files, and dynasm is green in the same run — proving the shared trace side):cranelift int_loop(perf-threshold flake, 0.30s vs 0.14s on a loaded runner) and two wasm-backend "wrong output" cases (synth/delete_negative_open_slice_hot,synth/selfrec_tail_exception_unwind), which are known wasm-Linux-x86_64 gaps.🤖 Generated with Claude Code