From 7c2ba25d91a8f08b6bbec4a48c30a852928f48df Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 5 Aug 2026 22:08:54 +0900 Subject: [PATCH 01/12] jit, check.py: subtract journal-recoverable effects from the rolled-back gate `fbw_diag::ROLLED_BACK_WITH_EFFECTS` gated on `!committed && effects > 0`. `effects` is the gh#467 executed-effect odometer, and three of its four bump sites are journal pushes -- `fbw_store_journal_push`, `fbw_append_journal_push` and `fbw_cell_store_journal_push`. `fbw_store_journal_rollback` undoes all three on the same non-committed exit, before the epilogue reads the odometer, and never decrements it, so those walks were counted as irreversible. A fixture whose ten uncommitted walks carry only journaled effects reported 10; it now reports 0, with the same ten walks still in the census. The epilogue reads the three journal lengths where it read the store journal's and gates on `effects - journaled`. The odometer is written in two places, `fbw_state.rs` `set(0)` and the bump, and the `set(0)` sits inside `fbw_store_journal_reset`, which clears all three journals in the same function; `journaled <= effects` therefore holds for every walk and a `debug_assert!` pins it. `journal=` in the `[fbw-census]` line was the store journal's length alone, so an append-journal effect printed `effects=1 journal=0`, indistinguishable from a residual effect. The line prints `jstore=`, `jappend=`, `jcell=` and `unrecoverable=`. The wasm ring keeps one summed `journaled` field, width unchanged. The store-journal rollback can fail to restore a slot when a later residual shrank the list. That was reported only under the debug gate and counted nowhere, so the subtraction would have absorbed it. It now bumps `fbw_diag::STORE_JOURNAL_ROLLBACK_FAILED`, slot 11, moving `RING_BASE` to 12 in the producer and in the wasm runner's decoder. Both runners print it as `fbw_store_journal_rollback_failed` and it joins `JITSTATS_BADNESS_FIELDS`. Five of the six fixtures carrying a nonzero baseline keep their value: set_hash_protocol and raise_reg_unbound_jitstress at `unrecoverable=7`, recursive_forced_frame_kept_stack at `unrecoverable=3`, global_store_plain_dict_globals and pickle_terminal_raise_resume unchanged. `ca_bridge_multiframe_resume_double_call` on wasm falls 1 -> 0: its only uncommitted walk is `end=LoopBearingCalleeInlineUnsupport effects=1 journaled=1`, on a run reporting `fbw_store_journal_rollback_failed=0`. Assisted-by: Claude --- pyre/bench/fannkuch.cranelift.jitstats | 1 + pyre/bench/fannkuch.dynasm.jitstats | 1 + pyre/bench/fannkuch.wasm.jitstats | 1 + pyre/bench/fib_loop.cranelift.jitstats | 1 + pyre/bench/fib_loop.dynasm.jitstats | 1 + pyre/bench/fib_loop.wasm.jitstats | 1 + pyre/bench/fib_recursive.cranelift.jitstats | 1 + pyre/bench/fib_recursive.dynasm.jitstats | 1 + pyre/bench/fib_recursive.wasm.jitstats | 1 + pyre/bench/float_loop.cranelift.jitstats | 1 + pyre/bench/float_loop.dynasm.jitstats | 1 + pyre/bench/float_loop.wasm.jitstats | 1 + pyre/bench/inline_helper.cranelift.jitstats | 1 + pyre/bench/inline_helper.dynasm.jitstats | 1 + pyre/bench/inline_helper.wasm.jitstats | 1 + pyre/bench/int_loop.cranelift.jitstats | 1 + pyre/bench/int_loop.dynasm.jitstats | 1 + pyre/bench/int_loop.wasm.jitstats | 1 + pyre/bench/nbody.cranelift.jitstats | 1 + pyre/bench/nbody.dynasm.jitstats | 1 + pyre/bench/nbody.wasm.jitstats | 1 + pyre/bench/nested_loop.cranelift.jitstats | 1 + pyre/bench/nested_loop.dynasm.jitstats | 1 + pyre/bench/nested_loop.wasm.jitstats | 1 + .../bench/raise_catch_loop.cranelift.jitstats | 1 + pyre/bench/raise_catch_loop.dynasm.jitstats | 1 + pyre/bench/raise_catch_loop.wasm.jitstats | 1 + pyre/bench/spectral_norm.cranelift.jitstats | 1 + pyre/bench/spectral_norm.dynasm.jitstats | 1 + pyre/bench/spectral_norm.wasm.jitstats | 1 + ...rame_resume_double_call.cranelift.jitstats | 1 + ...tiframe_resume_double_call.dynasm.jitstats | 1 + ...ultiframe_resume_double_call.wasm.jitstats | 3 +- pyre/check.py | 1 + .../src/jitcode_dispatch/fbw_state.rs | 16 ++++++++ pyre/pyre-jit-trace/src/trace.rs | 37 +++++++++++++------ pyre/pyre-wasm-runner/src/main.rs | 23 ++++++++---- pyre/pyrex/src/lib.rs | 19 ++++++---- 38 files changed, 103 insertions(+), 28 deletions(-) diff --git a/pyre/bench/fannkuch.cranelift.jitstats b/pyre/bench/fannkuch.cranelift.jitstats index 2cceb0b9d90..be13abd78d5 100644 --- a/pyre/bench/fannkuch.cranelift.jitstats +++ b/pyre/bench/fannkuch.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=5049 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fannkuch.dynasm.jitstats b/pyre/bench/fannkuch.dynasm.jitstats index 2cceb0b9d90..be13abd78d5 100644 --- a/pyre/bench/fannkuch.dynasm.jitstats +++ b/pyre/bench/fannkuch.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=5049 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fannkuch.wasm.jitstats b/pyre/bench/fannkuch.wasm.jitstats index 8d8a0b964d4..bed36bbdf13 100644 --- a/pyre/bench/fannkuch.wasm.jitstats +++ b/pyre/bench/fannkuch.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=5049 diff --git a/pyre/bench/fib_loop.cranelift.jitstats b/pyre/bench/fib_loop.cranelift.jitstats index c2c1f34ede9..08a7d6e6cd0 100644 --- a/pyre/bench/fib_loop.cranelift.jitstats +++ b/pyre/bench/fib_loop.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=189 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_loop.dynasm.jitstats b/pyre/bench/fib_loop.dynasm.jitstats index c2c1f34ede9..08a7d6e6cd0 100644 --- a/pyre/bench/fib_loop.dynasm.jitstats +++ b/pyre/bench/fib_loop.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=189 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_loop.wasm.jitstats b/pyre/bench/fib_loop.wasm.jitstats index ef0f65b3b1e..fb4f79355d5 100644 --- a/pyre/bench/fib_loop.wasm.jitstats +++ b/pyre/bench/fib_loop.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=189 diff --git a/pyre/bench/fib_recursive.cranelift.jitstats b/pyre/bench/fib_recursive.cranelift.jitstats index 8b8d8bd32bc..6ababaee464 100644 --- a/pyre/bench/fib_recursive.cranelift.jitstats +++ b/pyre/bench/fib_recursive.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=406 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_recursive.dynasm.jitstats b/pyre/bench/fib_recursive.dynasm.jitstats index 8b8d8bd32bc..6ababaee464 100644 --- a/pyre/bench/fib_recursive.dynasm.jitstats +++ b/pyre/bench/fib_recursive.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=406 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_recursive.wasm.jitstats b/pyre/bench/fib_recursive.wasm.jitstats index 13f6bf4928d..ab4e4c18086 100644 --- a/pyre/bench/fib_recursive.wasm.jitstats +++ b/pyre/bench/fib_recursive.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=406 diff --git a/pyre/bench/float_loop.cranelift.jitstats b/pyre/bench/float_loop.cranelift.jitstats index 8beed56f050..b11f7426054 100644 --- a/pyre/bench/float_loop.cranelift.jitstats +++ b/pyre/bench/float_loop.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/float_loop.dynasm.jitstats b/pyre/bench/float_loop.dynasm.jitstats index 8beed56f050..b11f7426054 100644 --- a/pyre/bench/float_loop.dynasm.jitstats +++ b/pyre/bench/float_loop.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/float_loop.wasm.jitstats b/pyre/bench/float_loop.wasm.jitstats index 62c98090fec..09cf92188ae 100644 --- a/pyre/bench/float_loop.wasm.jitstats +++ b/pyre/bench/float_loop.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=1 diff --git a/pyre/bench/inline_helper.cranelift.jitstats b/pyre/bench/inline_helper.cranelift.jitstats index 1cc731febcf..64cc2e84e44 100644 --- a/pyre/bench/inline_helper.cranelift.jitstats +++ b/pyre/bench/inline_helper.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/inline_helper.dynasm.jitstats b/pyre/bench/inline_helper.dynasm.jitstats index 1cc731febcf..64cc2e84e44 100644 --- a/pyre/bench/inline_helper.dynasm.jitstats +++ b/pyre/bench/inline_helper.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/inline_helper.wasm.jitstats b/pyre/bench/inline_helper.wasm.jitstats index a0796ff2cd2..682906dd95a 100644 --- a/pyre/bench/inline_helper.wasm.jitstats +++ b/pyre/bench/inline_helper.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=1 diff --git a/pyre/bench/int_loop.cranelift.jitstats b/pyre/bench/int_loop.cranelift.jitstats index 8beed56f050..b11f7426054 100644 --- a/pyre/bench/int_loop.cranelift.jitstats +++ b/pyre/bench/int_loop.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/int_loop.dynasm.jitstats b/pyre/bench/int_loop.dynasm.jitstats index 8beed56f050..b11f7426054 100644 --- a/pyre/bench/int_loop.dynasm.jitstats +++ b/pyre/bench/int_loop.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/int_loop.wasm.jitstats b/pyre/bench/int_loop.wasm.jitstats index 62c98090fec..09cf92188ae 100644 --- a/pyre/bench/int_loop.wasm.jitstats +++ b/pyre/bench/int_loop.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=1 diff --git a/pyre/bench/nbody.cranelift.jitstats b/pyre/bench/nbody.cranelift.jitstats index 434fcead556..a65ebab635b 100644 --- a/pyre/bench/nbody.cranelift.jitstats +++ b/pyre/bench/nbody.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1547 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/nbody.dynasm.jitstats b/pyre/bench/nbody.dynasm.jitstats index 434fcead556..a65ebab635b 100644 --- a/pyre/bench/nbody.dynasm.jitstats +++ b/pyre/bench/nbody.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1547 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/nbody.wasm.jitstats b/pyre/bench/nbody.wasm.jitstats index 8707432d056..857ef8e45b9 100644 --- a/pyre/bench/nbody.wasm.jitstats +++ b/pyre/bench/nbody.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=1547 diff --git a/pyre/bench/nested_loop.cranelift.jitstats b/pyre/bench/nested_loop.cranelift.jitstats index 114b48b9fd0..69b7ad637da 100644 --- a/pyre/bench/nested_loop.cranelift.jitstats +++ b/pyre/bench/nested_loop.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/nested_loop.dynasm.jitstats b/pyre/bench/nested_loop.dynasm.jitstats index 114b48b9fd0..69b7ad637da 100644 --- a/pyre/bench/nested_loop.dynasm.jitstats +++ b/pyre/bench/nested_loop.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/nested_loop.wasm.jitstats b/pyre/bench/nested_loop.wasm.jitstats index 3276873edf5..a86ecc02d2d 100644 --- a/pyre/bench/nested_loop.wasm.jitstats +++ b/pyre/bench/nested_loop.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=201 diff --git a/pyre/bench/raise_catch_loop.cranelift.jitstats b/pyre/bench/raise_catch_loop.cranelift.jitstats index 114b48b9fd0..69b7ad637da 100644 --- a/pyre/bench/raise_catch_loop.cranelift.jitstats +++ b/pyre/bench/raise_catch_loop.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/raise_catch_loop.dynasm.jitstats b/pyre/bench/raise_catch_loop.dynasm.jitstats index 114b48b9fd0..69b7ad637da 100644 --- a/pyre/bench/raise_catch_loop.dynasm.jitstats +++ b/pyre/bench/raise_catch_loop.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/raise_catch_loop.wasm.jitstats b/pyre/bench/raise_catch_loop.wasm.jitstats index 3276873edf5..a86ecc02d2d 100644 --- a/pyre/bench/raise_catch_loop.wasm.jitstats +++ b/pyre/bench/raise_catch_loop.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=201 diff --git a/pyre/bench/spectral_norm.cranelift.jitstats b/pyre/bench/spectral_norm.cranelift.jitstats index fc43af8db38..c6908e3299b 100644 --- a/pyre/bench/spectral_norm.cranelift.jitstats +++ b/pyre/bench/spectral_norm.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=520 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/spectral_norm.dynasm.jitstats b/pyre/bench/spectral_norm.dynasm.jitstats index fc43af8db38..c6908e3299b 100644 --- a/pyre/bench/spectral_norm.dynasm.jitstats +++ b/pyre/bench/spectral_norm.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=520 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/spectral_norm.wasm.jitstats b/pyre/bench/spectral_norm.wasm.jitstats index c55603e53a8..2a6d7a056ba 100644 --- a/pyre/bench/spectral_norm.wasm.jitstats +++ b/pyre/bench/spectral_norm.wasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 guard_failures=520 diff --git a/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.cranelift.jitstats b/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.cranelift.jitstats index 4434c9138cc..2ba6a41eb8d 100644 --- a/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.cranelift.jitstats +++ b/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.cranelift.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=2592 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.dynasm.jitstats b/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.dynasm.jitstats index 4434c9138cc..2ba6a41eb8d 100644 --- a/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.dynasm.jitstats +++ b/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.dynasm.jitstats @@ -3,6 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=2592 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.wasm.jitstats b/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.wasm.jitstats index aef30c8349a..9608ba1836a 100644 --- a/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.wasm.jitstats +++ b/pyre/bench/synth/ca_bridge_multiframe_resume_double_call.wasm.jitstats @@ -2,7 +2,8 @@ bridges_compiled=16 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 -fbw_rolled_back_with_effects=1 +fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=2581 internal_compile_panics=0 loops_aborted=1 diff --git a/pyre/check.py b/pyre/check.py index 490ae51c122..4faba47c88c 100644 --- a/pyre/check.py +++ b/pyre/check.py @@ -724,6 +724,7 @@ def _parse_jit_stats(snapshot): "fbw_rolled_back_with_effects", "field_pos_spec_misplaced", "field_pos_attached_misplaced", + "fbw_store_journal_rollback_failed", ) # The three count-valued counters, and what a move in either direction means: diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs index 1e207dc4e6a..874b3d007cf 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs @@ -934,6 +934,9 @@ pub(crate) fn fbw_store_journal_rollback() { // shrank the list after the store — a shape the replay // already cannot undo (the residual re-runs). Surface it // under the debug gate instead of corrupting silently. + crate::trace::fbw_diag::bump( + crate::trace::fbw_diag::STORE_JOURNAL_ROLLBACK_FAILED, + ); if fbw_debug_abort_enabled() { eprintln!("[fbw-store-journal] rollback failed (index out of bounds)"); } @@ -1068,6 +1071,19 @@ pub(crate) fn fbw_store_journal_len() -> usize { FBW_STORE_JOURNAL.with(|j| j.borrow().len()) } +/// The three journals whose pushes bump [`fbw_bump_executed_effect`], as +/// `(stores, appends, cell_stores)`. Read at the walk-end epilogue before the +/// commit/rollback, their sum is the number of executed effects +/// [`fbw_store_journal_rollback`] is about to undo. The traceback journal is +/// absent because its push bumps nothing. +pub(crate) fn fbw_journaled_effect_lens() -> (usize, usize, usize) { + ( + fbw_store_journal_len(), + FBW_APPEND_JOURNAL.with(|j| j.borrow().len()), + FBW_CELL_STORE_JOURNAL.with(|j| j.borrow().len()), + ) +} + /// Mark the walk as carrying a recorded-but-unexecuted side effect only /// the legacy replay applies. pub(crate) fn fbw_mark_unjournaled_effect(cause: ResidualDecline) { diff --git a/pyre/pyre-jit-trace/src/trace.rs b/pyre/pyre-jit-trace/src/trace.rs index fe2f26726be..0c80a2728d9 100644 --- a/pyre/pyre-jit-trace/src/trace.rs +++ b/pyre/pyre-jit-trace/src/trace.rs @@ -4708,7 +4708,8 @@ fn run_perfn_walk( ); } let committed = WALK_END_FLUSH_COMMITTED.with(|c| c.get()) || terminate_no_replay; - let journal = crate::jitcode_dispatch::fbw_store_journal_len(); + let (jstore, jappend, jcell) = crate::jitcode_dispatch::fbw_journaled_effect_lens(); + let journaled = jstore + jappend + jcell; if committed { crate::jitcode_dispatch::fbw_store_journal_commit(); crate::jitcode_dispatch::fbw_exit_last_instr_commit(); @@ -4737,12 +4738,20 @@ fn run_perfn_walk( } let (unj_val, unj_sym) = crate::jitcode_dispatch::fbw_unjournaled_kinds(); let (exec_v, exec_mf, exec_pl) = crate::jitcode_dispatch::fbw_executed_residual_counts(); - // `effects` is the gh#467 executed-effect odometer: residual calls that - // were not provably side-effect free AND either wrote the live heap or - // entered a Python frame. A `committed=false` walk rolls its own store - // journal back but cannot undo these, so `committed=false effects>0` - // marks a walk whose caller is about to replay an irreversible region. + // `effects` is the gh#467 executed-effect odometer, and it counts two + // unlike things: residual calls that were not provably side-effect free + // AND either wrote the live heap or entered a Python frame, plus every + // push onto the three journals. The journal pushes are undone by the + // `fbw_store_journal_rollback` above without the odometer moving, so + // only the remainder is what a `committed=false` walk leaves behind for + // its caller's replay to apply a second time. + // + // The subtraction cannot under-report: the odometer is zeroed only by + // `fbw_store_journal_reset`, which clears all three journals in the + // same breath, so `journaled <= effects` holds for every walk. let effects = crate::jitcode_dispatch::fbw_executed_effect_count(); + debug_assert!(journaled <= effects); + let unrecoverable = effects.saturating_sub(journaled); // The same record, into a static the wasm host reads back through the // `pyre_fbw_diag` export. The guest cannot see `PYRE_FBW_CENSUS`, so // without this the wasm target has no walk-level observability at all. @@ -4752,7 +4761,8 @@ fn run_perfn_walk( committed, ctx.is_bridge_trace, effects, - journal, + journaled, + unrecoverable, exec_mf, leg, ); @@ -4760,7 +4770,8 @@ fn run_perfn_walk( eprintln!( "[fbw-census] end={end} committed={committed} leg={leg} bridge={} \ unj_val={unj_val} unj_sym={unj_sym} exec_v={exec_v} exec_mf={exec_mf} \ - exec_pl={exec_pl} effects={effects} journal={journal}", + exec_pl={exec_pl} effects={effects} jstore={jstore} jappend={jappend} \ + jcell={jcell} unrecoverable={unrecoverable}", ctx.is_bridge_trace, ); } @@ -6122,12 +6133,13 @@ pub mod fbw_diag { pub const ESCAPE_PORTAL_AND_PUBLISHED_CALLEE: usize = 8; pub const ESCAPE_FORCE_BY_PORTAL: usize = 9; pub const ESCAPE_FORCE_BY_CALLEE_ONLY: usize = 10; + pub const STORE_JOURNAL_ROLLBACK_FAILED: usize = 11; /// One ring entry per walk: four slots of outcome name (8 ASCII bytes per /// slot, little-endian) followed by one slot of packed counters. A `u64` /// export cannot carry a string, and the outcome set is far too large to /// spend a tally slot per variant. - pub const RING_BASE: usize = 11; + pub const RING_BASE: usize = 12; pub const RING_ENTRIES: usize = 24; pub const RING_STRIDE: usize = 5; pub const NAME_SLOTS: usize = 4; @@ -6172,12 +6184,13 @@ pub mod fbw_diag { committed: bool, bridge: bool, effects: usize, - journal: usize, + journaled: usize, + unrecoverable: usize, exec_mf: u32, leg: u8, ) { let index = FBW_DIAG[WALKS].fetch_add(1, Ordering::Relaxed) as usize; - if !committed && effects > 0 { + if !committed && unrecoverable > 0 { FBW_DIAG[ROLLED_BACK_WITH_EFFECTS].fetch_add(1, Ordering::Relaxed); } if index >= RING_ENTRIES { @@ -6198,7 +6211,7 @@ pub mod fbw_diag { | if committed { FLAG_COMMITTED } else { 0 } | ((effects as u64).min(FIELD_MASK) << SHIFT_EFFECTS) | if bridge { FLAG_BRIDGE } else { 0 } - | ((journal as u64).min(FIELD_MASK) << SHIFT_JOURNAL) + | ((journaled as u64).min(FIELD_MASK) << SHIFT_JOURNAL) | ((exec_mf as u64).min(FIELD_MASK) << SHIFT_EXEC_MF) | ((leg as u64) << SHIFT_LEG); FBW_DIAG[entry + NAME_SLOTS].store(flags, Ordering::Relaxed); diff --git a/pyre/pyre-wasm-runner/src/main.rs b/pyre/pyre-wasm-runner/src/main.rs index 6602f7f3dad..906b17e7c23 100644 --- a/pyre/pyre-wasm-runner/src/main.rs +++ b/pyre/pyre-wasm-runner/src/main.rs @@ -620,7 +620,7 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { // which the guest cannot read. Slot layout in // `pyre_jit_trace::trace::fbw_diag`. if let Ok(fbw) = instance.get_typed_func::(&mut store, "pyre_fbw_diag") { - const RING_BASE: u32 = 11; + const RING_BASE: u32 = 12; const RING_ENTRIES: u32 = 24; const RING_STRIDE: u32 = 5; const NAME_SLOTS: u32 = 4; @@ -628,12 +628,14 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { let walks = slot(0); eprintln!( "[jit-stats] fbw_diag walks={walks} ROLLED_BACK_WITH_EFFECTS={} \ - midbody_latch={}/{} escape_plain_fallback={}/{}", + midbody_latch={}/{} escape_plain_fallback={}/{} \ + fbw_store_journal_rollback_failed={}", slot(1), slot(3), slot(2), slot(5), slot(4), + slot(11), ); for entry in 0..RING_ENTRIES.min(walks as u32) { let base = RING_BASE + entry * RING_STRIDE; @@ -654,7 +656,7 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { let field = |shift: u32| (flags >> shift) & 0xffff; eprintln!( "[fbw-census] end={end} committed={} leg={} bridge={} exec_mf={} \ - effects={} journal={}", + effects={} journaled={}", flags & (1 << 1) != 0, (flags >> 56) & 0xff, flags & (1 << 2) != 0, @@ -858,14 +860,18 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { // the same key the native backends print, because `_jit_stats_change` // compares by name and a name only one backend emits gates nothing on // the others. - let fbw_rolled_back_with_effects = match instance + // Slot 11 is `STORE_JOURNAL_ROLLBACK_FAILED`, the store restores the + // rollback could not perform — the one journaled effect the walk-end + // subtraction must not silently absorb. Both slots come from one + // export lookup so an absent export names itself once. + let (fbw_rolled_back_with_effects, fbw_store_journal_rollback_failed) = match instance .get_typed_func::(&mut store, "pyre_fbw_diag") - .and_then(|f| f.call(&mut store, 1)) + .and_then(|f| Ok((f.call(&mut store, 1)?, f.call(&mut store, 11)?))) { - Ok(v) => v, + Ok(pair) => pair, Err(_) => { missing.push("pyre_fbw_diag"); - 0 + (0, 0) } }; if !missing.is_empty() { @@ -890,7 +896,8 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { field_pos_spec_checked={field_pos_spec_checked} \ field_pos_spec_misplaced={field_pos_spec_misplaced} \ field_pos_attached_checked={field_pos_attached_checked} \ - field_pos_attached_misplaced={field_pos_attached_misplaced}" + field_pos_attached_misplaced={field_pos_attached_misplaced} \ + fbw_store_journal_rollback_failed={fbw_store_journal_rollback_failed}" ); } let packed = match run_result { diff --git a/pyre/pyrex/src/lib.rs b/pyre/pyrex/src/lib.rs index b4d94e6e29d..482a2c46bd5 100644 --- a/pyre/pyrex/src/lib.rs +++ b/pyre/pyrex/src/lib.rs @@ -832,15 +832,20 @@ fn maybe_print_jit_stats() { pyre_jit::fbw_diag_counter(9), pyre_jit::fbw_diag_counter(10), ); - // Walks that ended uncommitted after a residual had already run an - // irreversible effect: the store journal cannot undo a residual that wrote - // live heap or entered a Python frame, so the replay the caller falls back - // to applies those effects twice. `fbw_diag::ROLLED_BACK_WITH_EFFECTS`, - // which the wasm runner already exports; a nonzero value names a walk-abort - // variant that reaches `run_perfn_walk`'s epilogue without an adopt leg. + // Walks that ended uncommitted after a residual had already run an effect + // no journal undoes — it wrote live heap or entered a Python frame — so the + // replay the caller falls back to applies that effect twice. + // `fbw_diag::ROLLED_BACK_WITH_EFFECTS`, which the wasm runner also exports; + // a nonzero value names a walk-abort variant that reaches + // `run_perfn_walk`'s epilogue without an adopt leg. + // + // `STORE_JOURNAL_ROLLBACK_FAILED` is the hole that subtraction would + // otherwise open: a journaled store the rollback could not restore is + // subtracted out of the count above, so it needs one of its own. eprintln!( - "[jit-stats] fbw_rolled_back_with_effects={}", + "[jit-stats] fbw_rolled_back_with_effects={} fbw_store_journal_rollback_failed={}", pyre_jit::fbw_diag_counter(1), + pyre_jit::fbw_diag_counter(11), ); let stats = pyre_jit::eval::driver_pair().0.get_stats(); eprintln!( From 7a0810ee36e324bbf1bb539a2bd20e9fdd26c8ea Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 5 Aug 2026 23:10:44 +0900 Subject: [PATCH 02/12] jit, check.py: latch the vable-escape blackhole image on a bridge walk too `residual_call.rs`'s single-frame blackhole latch carried `!ctx.trace_ctx.is_bridge_trace` as one of seven conjuncts from a default-off rollout gate. It was the only latch producer a bridge vable escape had: the abort-path `latch_abort_blackhole` is gated on `error.leaves_complete_image()`, whose variant list does not contain `VableEscapedDuringResidualCall`, so a bridge escape reached the epilogue with no image and its caller replayed the region. The comment directly above the conjunct already argues that upstream has no counterpart to either of its siblings -- ABORT_ESCAPE goes to `run_blackhole_interp_to_cancel_tracing` and runs forward, never replays. Two of the six recorded instances of the roll-back-with-effects population go to zero on all three backends. `set_hash_protocol`'s walk moves from `end=CompileTracePending committed=false leg=0 bridge=true effects=7` to `committed=true leg=1`, so those seven effects are applied once instead of twice. `recursive_forced_frame_kept_stack` moves the same way; its walk is `bridge=false` and the change reaches it through the bridge walks in the same run, which now adopt. Two fixtures for a shape the corpus did not contain: of 138 forced vable escapes across the synth corpus, none were `bridge=true`. A hot loop whose rare arm guard-fails past the trace-eagerness threshold earns a bridge, and `sys._getframe(0)` inside the bridge body forces the virtualizable without entering a Python frame. `getframe_bridge_force_plain` adopts 20 single-frame images; `getframe_bridge_force_after_store` puts an un-journaled store ahead of the forcing call, which leaves the escape's mirror slot unresolved, and records `fbw_rolled_back_with_effects=20` on the replay path. Neither fixture pinned anything without a counter: no gated field moved between the two arms, because nothing counted a successful adoption. `fbw_diag` slots 12 and 13 tally single- and multi-frame blackhole adoptions -- `RING_BASE` moves to 14 in the producer and in the wasm runner's decoder -- and both runners print them as `fbw_blackhole_adopted_single_frame` and `fbw_blackhole_adopted_multi_frame`. They join `JITSTATS_SNAPSHOT_FIELDS` and `JITSTATS_REGRESSION_ON_FALL`, where a fall is the regression: the walk stopped handing the interpreter an image. Recording them pins 110 single-frame and 56 multi-frame adoptions across 16 existing fixtures that nothing gated before. Assisted-by: Claude --- pyre/bench/fannkuch.cranelift.jitstats | 2 + pyre/bench/fannkuch.dynasm.jitstats | 2 + pyre/bench/fannkuch.wasm.jitstats | 2 + pyre/bench/fib_loop.cranelift.jitstats | 2 + pyre/bench/fib_loop.dynasm.jitstats | 2 + pyre/bench/fib_loop.wasm.jitstats | 2 + pyre/bench/fib_recursive.cranelift.jitstats | 2 + pyre/bench/fib_recursive.dynasm.jitstats | 2 + pyre/bench/fib_recursive.wasm.jitstats | 2 + pyre/bench/float_loop.cranelift.jitstats | 2 + pyre/bench/float_loop.dynasm.jitstats | 2 + pyre/bench/float_loop.wasm.jitstats | 2 + pyre/bench/inline_helper.cranelift.jitstats | 2 + pyre/bench/inline_helper.dynasm.jitstats | 2 + pyre/bench/inline_helper.wasm.jitstats | 2 + pyre/bench/int_loop.cranelift.jitstats | 2 + pyre/bench/int_loop.dynasm.jitstats | 2 + pyre/bench/int_loop.wasm.jitstats | 2 + pyre/bench/nbody.cranelift.jitstats | 2 + pyre/bench/nbody.dynasm.jitstats | 2 + pyre/bench/nbody.wasm.jitstats | 2 + pyre/bench/nested_loop.cranelift.jitstats | 2 + pyre/bench/nested_loop.dynasm.jitstats | 2 + pyre/bench/nested_loop.wasm.jitstats | 2 + .../bench/raise_catch_loop.cranelift.jitstats | 2 + pyre/bench/raise_catch_loop.dynasm.jitstats | 2 + pyre/bench/raise_catch_loop.wasm.jitstats | 2 + pyre/bench/spectral_norm.cranelift.jitstats | 2 + pyre/bench/spectral_norm.dynasm.jitstats | 2 + pyre/bench/spectral_norm.wasm.jitstats | 2 + ...llee_local_after_escape.cranelift.jitstats | 3 ++ ..._callee_local_after_escape.dynasm.jitstats | 3 ++ ...ed_callee_local_after_escape.wasm.jitstats | 3 ++ ...ridge_force_after_store.cranelift.jitstats | 12 ++++++ ...e_bridge_force_after_store.dynasm.jitstats | 12 ++++++ .../getframe_bridge_force_after_store.py | 40 +++++++++++++++++++ ...ame_bridge_force_after_store.wasm.jitstats | 12 ++++++ ...rame_bridge_force_plain.cranelift.jitstats | 12 ++++++ ...etframe_bridge_force_plain.dynasm.jitstats | 12 ++++++ .../synth/getframe_bridge_force_plain.py | 36 +++++++++++++++++ .../getframe_bridge_force_plain.wasm.jitstats | 12 ++++++ ..._nested_compiled_callee.cranelift.jitstats | 3 ++ ...als_nested_compiled_callee.dynasm.jitstats | 3 ++ ...me_force_cancel_journal.cranelift.jitstats | 3 ++ ...frame_force_cancel_journal.dynasm.jitstats | 3 ++ ...etframe_force_cancel_journal.wasm.jitstats | 3 ++ ...line_subwalk_multiframe.cranelift.jitstats | 3 ++ ..._inline_subwalk_multiframe.dynasm.jitstats | 3 ++ ...me_inline_subwalk_multiframe.wasm.jitstats | 3 ++ ...nlined_callee_own_frame.cranelift.jitstats | 3 ++ ...e_inlined_callee_own_frame.dynasm.jitstats | 3 ++ ...ame_inlined_callee_own_frame.wasm.jitstats | 3 ++ ...sidual_callee_own_frame.cranelift.jitstats | 3 ++ ..._residual_callee_own_frame.dynasm.jitstats | 3 ++ ...me_residual_callee_own_frame.wasm.jitstats | 3 ++ ...oop_force_blackhole_crn.cranelift.jitstats | 3 ++ ...t_loop_force_blackhole_crn.dynasm.jitstats | 3 ++ ...oot_loop_force_blackhole_crn.wasm.jitstats | 3 ++ ...khole_crn_nonidempotent.cranelift.jitstats | 3 ++ ...lackhole_crn_nonidempotent.dynasm.jitstats | 3 ++ ..._blackhole_crn_nonidempotent.wasm.jitstats | 3 ++ ..._loop_force_while_merge.cranelift.jitstats | 3 ++ ...oot_loop_force_while_merge.dynasm.jitstats | 3 ++ ..._root_loop_force_while_merge.wasm.jitstats | 3 ++ ...frame_stored_fback_walk.cranelift.jitstats | 3 ++ ...getframe_stored_fback_walk.dynasm.jitstats | 3 ++ .../getframe_stored_fback_walk.wasm.jitstats | 3 ++ ...r_locals_across_subwalk.cranelift.jitstats | 3 ++ ...ller_locals_across_subwalk.dynasm.jitstats | 3 ++ ...caller_locals_across_subwalk.wasm.jitstats | 3 ++ ...red_frame_outlives_call.cranelift.jitstats | 3 ++ ...ptured_frame_outlives_call.dynasm.jitstats | 3 ++ ...captured_frame_outlives_call.wasm.jitstats | 3 ++ ...ing_read_frame_identity.cranelift.jitstats | 3 ++ ...caping_read_frame_identity.dynasm.jitstats | 3 ++ ...escaping_read_frame_identity.wasm.jitstats | 3 ++ ..._inlined_callee_subwalk.cranelift.jitstats | 3 ++ ...ile_inlined_callee_subwalk.dynasm.jitstats | 3 ++ ...while_inlined_callee_subwalk.wasm.jitstats | 3 ++ ..._subwalk_decline_shapes.cranelift.jitstats | 3 ++ ...ile_subwalk_decline_shapes.dynasm.jitstats | 3 ++ ...while_subwalk_decline_shapes.wasm.jitstats | 3 ++ ...forced_frame_kept_stack.cranelift.jitstats | 3 ++ ...ve_forced_frame_kept_stack.dynasm.jitstats | 3 ++ ...sive_forced_frame_kept_stack.wasm.jitstats | 3 ++ .../set_hash_protocol.cranelift.jitstats | 3 ++ .../synth/set_hash_protocol.dynasm.jitstats | 3 ++ .../synth/set_hash_protocol.wasm.jitstats | 3 ++ ..._too_long_effect_replay.cranelift.jitstats | 3 ++ ...ace_too_long_effect_replay.dynasm.jitstats | 3 ++ ...trace_too_long_effect_replay.wasm.jitstats | 3 ++ ..._long_inline_multiframe.cranelift.jitstats | 3 ++ ...too_long_inline_multiframe.dynasm.jitstats | 3 ++ ...e_too_long_inline_multiframe.wasm.jitstats | 3 ++ pyre/check.py | 17 +++++++- .../src/jitcode_dispatch/residual_call.rs | 3 +- pyre/pyre-jit-trace/src/trace.rs | 10 ++++- pyre/pyre-wasm-runner/src/main.rs | 27 +++++++++---- pyre/pyrex/src/lib.rs | 12 +++++- 99 files changed, 432 insertions(+), 13 deletions(-) create mode 100644 pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats create mode 100644 pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats create mode 100644 pyre/bench/synth/getframe_bridge_force_after_store.py create mode 100644 pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats create mode 100644 pyre/bench/synth/getframe_bridge_force_plain.cranelift.jitstats create mode 100644 pyre/bench/synth/getframe_bridge_force_plain.dynasm.jitstats create mode 100644 pyre/bench/synth/getframe_bridge_force_plain.py create mode 100644 pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats diff --git a/pyre/bench/fannkuch.cranelift.jitstats b/pyre/bench/fannkuch.cranelift.jitstats index be13abd78d5..53255eda85e 100644 --- a/pyre/bench/fannkuch.cranelift.jitstats +++ b/pyre/bench/fannkuch.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=22 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=5049 diff --git a/pyre/bench/fannkuch.dynasm.jitstats b/pyre/bench/fannkuch.dynasm.jitstats index be13abd78d5..53255eda85e 100644 --- a/pyre/bench/fannkuch.dynasm.jitstats +++ b/pyre/bench/fannkuch.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=22 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=5049 diff --git a/pyre/bench/fannkuch.wasm.jitstats b/pyre/bench/fannkuch.wasm.jitstats index bed36bbdf13..5283e2b61b4 100644 --- a/pyre/bench/fannkuch.wasm.jitstats +++ b/pyre/bench/fannkuch.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=22 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/fib_loop.cranelift.jitstats b/pyre/bench/fib_loop.cranelift.jitstats index 08a7d6e6cd0..79d46bcf250 100644 --- a/pyre/bench/fib_loop.cranelift.jitstats +++ b/pyre/bench/fib_loop.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=189 diff --git a/pyre/bench/fib_loop.dynasm.jitstats b/pyre/bench/fib_loop.dynasm.jitstats index 08a7d6e6cd0..79d46bcf250 100644 --- a/pyre/bench/fib_loop.dynasm.jitstats +++ b/pyre/bench/fib_loop.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=189 diff --git a/pyre/bench/fib_loop.wasm.jitstats b/pyre/bench/fib_loop.wasm.jitstats index fb4f79355d5..530525191be 100644 --- a/pyre/bench/fib_loop.wasm.jitstats +++ b/pyre/bench/fib_loop.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/fib_recursive.cranelift.jitstats b/pyre/bench/fib_recursive.cranelift.jitstats index 6ababaee464..ad463b310f8 100644 --- a/pyre/bench/fib_recursive.cranelift.jitstats +++ b/pyre/bench/fib_recursive.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=406 diff --git a/pyre/bench/fib_recursive.dynasm.jitstats b/pyre/bench/fib_recursive.dynasm.jitstats index 6ababaee464..ad463b310f8 100644 --- a/pyre/bench/fib_recursive.dynasm.jitstats +++ b/pyre/bench/fib_recursive.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=406 diff --git a/pyre/bench/fib_recursive.wasm.jitstats b/pyre/bench/fib_recursive.wasm.jitstats index ab4e4c18086..00b5b2003f1 100644 --- a/pyre/bench/fib_recursive.wasm.jitstats +++ b/pyre/bench/fib_recursive.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/float_loop.cranelift.jitstats b/pyre/bench/float_loop.cranelift.jitstats index b11f7426054..8a6215f77a3 100644 --- a/pyre/bench/float_loop.cranelift.jitstats +++ b/pyre/bench/float_loop.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1 diff --git a/pyre/bench/float_loop.dynasm.jitstats b/pyre/bench/float_loop.dynasm.jitstats index b11f7426054..8a6215f77a3 100644 --- a/pyre/bench/float_loop.dynasm.jitstats +++ b/pyre/bench/float_loop.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1 diff --git a/pyre/bench/float_loop.wasm.jitstats b/pyre/bench/float_loop.wasm.jitstats index 09cf92188ae..59f22855e15 100644 --- a/pyre/bench/float_loop.wasm.jitstats +++ b/pyre/bench/float_loop.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/inline_helper.cranelift.jitstats b/pyre/bench/inline_helper.cranelift.jitstats index 64cc2e84e44..fceea6eee13 100644 --- a/pyre/bench/inline_helper.cranelift.jitstats +++ b/pyre/bench/inline_helper.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1 diff --git a/pyre/bench/inline_helper.dynasm.jitstats b/pyre/bench/inline_helper.dynasm.jitstats index 64cc2e84e44..fceea6eee13 100644 --- a/pyre/bench/inline_helper.dynasm.jitstats +++ b/pyre/bench/inline_helper.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1 diff --git a/pyre/bench/inline_helper.wasm.jitstats b/pyre/bench/inline_helper.wasm.jitstats index 682906dd95a..656842672ea 100644 --- a/pyre/bench/inline_helper.wasm.jitstats +++ b/pyre/bench/inline_helper.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/int_loop.cranelift.jitstats b/pyre/bench/int_loop.cranelift.jitstats index b11f7426054..8a6215f77a3 100644 --- a/pyre/bench/int_loop.cranelift.jitstats +++ b/pyre/bench/int_loop.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1 diff --git a/pyre/bench/int_loop.dynasm.jitstats b/pyre/bench/int_loop.dynasm.jitstats index b11f7426054..8a6215f77a3 100644 --- a/pyre/bench/int_loop.dynasm.jitstats +++ b/pyre/bench/int_loop.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1 diff --git a/pyre/bench/int_loop.wasm.jitstats b/pyre/bench/int_loop.wasm.jitstats index 09cf92188ae..59f22855e15 100644 --- a/pyre/bench/int_loop.wasm.jitstats +++ b/pyre/bench/int_loop.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/nbody.cranelift.jitstats b/pyre/bench/nbody.cranelift.jitstats index a65ebab635b..542f628a0d7 100644 --- a/pyre/bench/nbody.cranelift.jitstats +++ b/pyre/bench/nbody.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1547 diff --git a/pyre/bench/nbody.dynasm.jitstats b/pyre/bench/nbody.dynasm.jitstats index a65ebab635b..542f628a0d7 100644 --- a/pyre/bench/nbody.dynasm.jitstats +++ b/pyre/bench/nbody.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=1547 diff --git a/pyre/bench/nbody.wasm.jitstats b/pyre/bench/nbody.wasm.jitstats index 857ef8e45b9..e1f095c5920 100644 --- a/pyre/bench/nbody.wasm.jitstats +++ b/pyre/bench/nbody.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/nested_loop.cranelift.jitstats b/pyre/bench/nested_loop.cranelift.jitstats index 69b7ad637da..56ae0032eed 100644 --- a/pyre/bench/nested_loop.cranelift.jitstats +++ b/pyre/bench/nested_loop.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=201 diff --git a/pyre/bench/nested_loop.dynasm.jitstats b/pyre/bench/nested_loop.dynasm.jitstats index 69b7ad637da..56ae0032eed 100644 --- a/pyre/bench/nested_loop.dynasm.jitstats +++ b/pyre/bench/nested_loop.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=201 diff --git a/pyre/bench/nested_loop.wasm.jitstats b/pyre/bench/nested_loop.wasm.jitstats index a86ecc02d2d..1e12ce579fd 100644 --- a/pyre/bench/nested_loop.wasm.jitstats +++ b/pyre/bench/nested_loop.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/raise_catch_loop.cranelift.jitstats b/pyre/bench/raise_catch_loop.cranelift.jitstats index 69b7ad637da..56ae0032eed 100644 --- a/pyre/bench/raise_catch_loop.cranelift.jitstats +++ b/pyre/bench/raise_catch_loop.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=201 diff --git a/pyre/bench/raise_catch_loop.dynasm.jitstats b/pyre/bench/raise_catch_loop.dynasm.jitstats index 69b7ad637da..56ae0032eed 100644 --- a/pyre/bench/raise_catch_loop.dynasm.jitstats +++ b/pyre/bench/raise_catch_loop.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=201 diff --git a/pyre/bench/raise_catch_loop.wasm.jitstats b/pyre/bench/raise_catch_loop.wasm.jitstats index a86ecc02d2d..1e12ce579fd 100644 --- a/pyre/bench/raise_catch_loop.wasm.jitstats +++ b/pyre/bench/raise_catch_loop.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/spectral_norm.cranelift.jitstats b/pyre/bench/spectral_norm.cranelift.jitstats index c6908e3299b..2b79ce7e891 100644 --- a/pyre/bench/spectral_norm.cranelift.jitstats +++ b/pyre/bench/spectral_norm.cranelift.jitstats @@ -2,6 +2,8 @@ bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=520 diff --git a/pyre/bench/spectral_norm.dynasm.jitstats b/pyre/bench/spectral_norm.dynasm.jitstats index c6908e3299b..2b79ce7e891 100644 --- a/pyre/bench/spectral_norm.dynasm.jitstats +++ b/pyre/bench/spectral_norm.dynasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 guard_failures=520 diff --git a/pyre/bench/spectral_norm.wasm.jitstats b/pyre/bench/spectral_norm.wasm.jitstats index 2a6d7a056ba..fc8b4fa7afa 100644 --- a/pyre/bench/spectral_norm.wasm.jitstats +++ b/pyre/bench/spectral_norm.wasm.jitstats @@ -2,6 +2,8 @@ bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 diff --git a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.cranelift.jitstats b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.cranelift.jitstats index 4332a81169a..e90a95dda3b 100644 --- a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.cranelift.jitstats +++ b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.dynasm.jitstats b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.dynasm.jitstats index 4332a81169a..e90a95dda3b 100644 --- a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.dynasm.jitstats +++ b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.wasm.jitstats b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.wasm.jitstats index 4332a81169a..e90a95dda3b 100644 --- a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.wasm.jitstats +++ b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats new file mode 100644 index 00000000000..24f291c19cf --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats @@ -0,0 +1,12 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 +fbw_rolled_back_with_effects=20 +fbw_store_journal_rollback_failed=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats new file mode 100644 index 00000000000..24f291c19cf --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats @@ -0,0 +1,12 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 +fbw_rolled_back_with_effects=20 +fbw_store_journal_rollback_failed=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.py b/pyre/bench/synth/getframe_bridge_force_after_store.py new file mode 100644 index 00000000000..5054fc9c09a --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store.py @@ -0,0 +1,40 @@ +# The bridge forced-vable escape of `getframe_bridge_force_plain`, with one +# un-journaled store ahead of the forcing call. +# +# `box.n = i` lowers to a Void `store_attr_fn` residual: it writes live heap, so +# it bumps the executed-effect odometer, and no journal covers it. The walk +# therefore reaches `run_perfn_walk`'s epilogue uncommitted carrying an +# unrecoverable effect, and the legacy replay the caller falls back to applies +# that store a second time -- the population +# `fbw_rolled_back_with_effects` names. +# +# The store is also what leaves the escape's operand-stack mirror slot +# unresolved, so this shape cannot capture an image even where the sibling can. +# The forcing residual itself never contributes an effect: the force branch +# returns before the odometer bump, so a bridge escape needs a second, earlier +# effectful op to register at all -- which is exactly what this file adds. +import sys + +_gf = sys._getframe + + +class Box: + n = 0 + + +box = Box() + + +def main(): + total = 0 + names = 0 + for i in range(400000): + if i % 97 == 0: + box.n = i + fr = _gf(0) + names += len(fr.f_code.co_name) + total += i + return total, names, box.n + + +print(main()) diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats new file mode 100644 index 00000000000..24f291c19cf --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats @@ -0,0 +1,12 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 +fbw_rolled_back_with_effects=20 +fbw_store_journal_rollback_failed=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_plain.cranelift.jitstats b/pyre/bench/synth/getframe_bridge_force_plain.cranelift.jitstats new file mode 100644 index 00000000000..7742a65cb84 --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain.cranelift.jitstats @@ -0,0 +1,12 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=20 +fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_plain.dynasm.jitstats b/pyre/bench/synth/getframe_bridge_force_plain.dynasm.jitstats new file mode 100644 index 00000000000..7742a65cb84 --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain.dynasm.jitstats @@ -0,0 +1,12 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=20 +fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_plain.py b/pyre/bench/synth/getframe_bridge_force_plain.py new file mode 100644 index 00000000000..797d1531a77 --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain.py @@ -0,0 +1,36 @@ +# Coverage for the forced-vable escape on a BRIDGE walk, which the rest of the +# corpus never produces: of 138 forced escapes across the synth fixtures, zero +# are `bridge=true`. +# +# Shape, each clause load-bearing: +# * the `for` loop compiles on the common arm; +# * `i % 97 == 0` is the rare arm, so its guard fails ~4124 times -- past +# `DEFAULT_TRACE_EAGERNESS` -- and `start_bridge_tracing` sets +# `ctx.is_bridge_trace`, making the walk over the rare arm a bridge walk; +# * `_gf(0)` is a `CallFn` residual returning a Ref that forces the +# virtualizable, and it is a builtin, so `frame_entry_count()` does not move +# and no user Python frame is entered; +# * the call sits directly in the portal frame's loop body, so the framestack +# is empty and this is not an inline sub-walk. +# +# With nothing else in the rare arm the escape's mirror image resolves and the +# walk adopts a single-frame blackhole terminal. Its sibling +# `getframe_bridge_force_after_store` puts an un-journaled store ahead of the +# forcing call and takes the replay path instead. +import sys + +_gf = sys._getframe + + +def main(): + total = 0 + names = 0 + for i in range(400000): + if i % 97 == 0: + fr = _gf(0) + names += len(fr.f_code.co_name) + total += i + return total, names + + +print(main()) diff --git a/pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats b/pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats new file mode 100644 index 00000000000..7742a65cb84 --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats @@ -0,0 +1,12 @@ +bridges_compiled=0 +descr_set_absent=0 +descr_set_ambiguous=0 +descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=20 +fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.cranelift.jitstats b/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.cranelift.jitstats index 8aaccc5f2c5..17a65fcb8fb 100644 --- a/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.cranelift.jitstats +++ b/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=214 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.dynasm.jitstats b/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.dynasm.jitstats index 8aaccc5f2c5..17a65fcb8fb 100644 --- a/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.dynasm.jitstats +++ b/pyre/bench/synth/getframe_caller_locals_nested_compiled_callee.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=214 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/getframe_force_cancel_journal.cranelift.jitstats b/pyre/bench/synth/getframe_force_cancel_journal.cranelift.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_force_cancel_journal.cranelift.jitstats +++ b/pyre/bench/synth/getframe_force_cancel_journal.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_force_cancel_journal.dynasm.jitstats b/pyre/bench/synth/getframe_force_cancel_journal.dynasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_force_cancel_journal.dynasm.jitstats +++ b/pyre/bench/synth/getframe_force_cancel_journal.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_force_cancel_journal.wasm.jitstats b/pyre/bench/synth/getframe_force_cancel_journal.wasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_force_cancel_journal.wasm.jitstats +++ b/pyre/bench/synth/getframe_force_cancel_journal.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_inline_subwalk_multiframe.cranelift.jitstats b/pyre/bench/synth/getframe_inline_subwalk_multiframe.cranelift.jitstats index 53e8eaf6bb6..b1643b10f47 100644 --- a/pyre/bench/synth/getframe_inline_subwalk_multiframe.cranelift.jitstats +++ b/pyre/bench/synth/getframe_inline_subwalk_multiframe.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=9 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=15 diff --git a/pyre/bench/synth/getframe_inline_subwalk_multiframe.dynasm.jitstats b/pyre/bench/synth/getframe_inline_subwalk_multiframe.dynasm.jitstats index 53e8eaf6bb6..b1643b10f47 100644 --- a/pyre/bench/synth/getframe_inline_subwalk_multiframe.dynasm.jitstats +++ b/pyre/bench/synth/getframe_inline_subwalk_multiframe.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=9 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=15 diff --git a/pyre/bench/synth/getframe_inline_subwalk_multiframe.wasm.jitstats b/pyre/bench/synth/getframe_inline_subwalk_multiframe.wasm.jitstats index 53e8eaf6bb6..b1643b10f47 100644 --- a/pyre/bench/synth/getframe_inline_subwalk_multiframe.wasm.jitstats +++ b/pyre/bench/synth/getframe_inline_subwalk_multiframe.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=9 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=15 diff --git a/pyre/bench/synth/getframe_inlined_callee_own_frame.cranelift.jitstats b/pyre/bench/synth/getframe_inlined_callee_own_frame.cranelift.jitstats index 5d9438ae0aa..337a2cb819b 100644 --- a/pyre/bench/synth/getframe_inlined_callee_own_frame.cranelift.jitstats +++ b/pyre/bench/synth/getframe_inlined_callee_own_frame.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=9 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_inlined_callee_own_frame.dynasm.jitstats b/pyre/bench/synth/getframe_inlined_callee_own_frame.dynasm.jitstats index 5d9438ae0aa..337a2cb819b 100644 --- a/pyre/bench/synth/getframe_inlined_callee_own_frame.dynasm.jitstats +++ b/pyre/bench/synth/getframe_inlined_callee_own_frame.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=9 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_inlined_callee_own_frame.wasm.jitstats b/pyre/bench/synth/getframe_inlined_callee_own_frame.wasm.jitstats index 5d9438ae0aa..337a2cb819b 100644 --- a/pyre/bench/synth/getframe_inlined_callee_own_frame.wasm.jitstats +++ b/pyre/bench/synth/getframe_inlined_callee_own_frame.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=9 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_residual_callee_own_frame.cranelift.jitstats b/pyre/bench/synth/getframe_residual_callee_own_frame.cranelift.jitstats index 4332a81169a..e90a95dda3b 100644 --- a/pyre/bench/synth/getframe_residual_callee_own_frame.cranelift.jitstats +++ b/pyre/bench/synth/getframe_residual_callee_own_frame.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_residual_callee_own_frame.dynasm.jitstats b/pyre/bench/synth/getframe_residual_callee_own_frame.dynasm.jitstats index 4332a81169a..e90a95dda3b 100644 --- a/pyre/bench/synth/getframe_residual_callee_own_frame.dynasm.jitstats +++ b/pyre/bench/synth/getframe_residual_callee_own_frame.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_residual_callee_own_frame.wasm.jitstats b/pyre/bench/synth/getframe_residual_callee_own_frame.wasm.jitstats index 4332a81169a..e90a95dda3b 100644 --- a/pyre/bench/synth/getframe_residual_callee_own_frame.wasm.jitstats +++ b/pyre/bench/synth/getframe_residual_callee_own_frame.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.cranelift.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.cranelift.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.dynasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.dynasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.wasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.wasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_while_merge.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_while_merge.cranelift.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_while_merge.cranelift.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_while_merge.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_while_merge.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_while_merge.dynasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_while_merge.dynasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_while_merge.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_root_loop_force_while_merge.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_while_merge.wasm.jitstats index 17df870455c..48f9c4225bc 100644 --- a/pyre/bench/synth/getframe_root_loop_force_while_merge.wasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_while_merge.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats b/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats index 5d9438ae0aa..c1e73e6acab 100644 --- a/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats +++ b/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats b/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats index 5d9438ae0aa..c1e73e6acab 100644 --- a/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats +++ b/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats b/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats index 5d9438ae0aa..c1e73e6acab 100644 --- a/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats +++ b/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.cranelift.jitstats b/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.cranelift.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.cranelift.jitstats +++ b/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.dynasm.jitstats b/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.dynasm.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.dynasm.jitstats +++ b/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.wasm.jitstats b/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.wasm.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.wasm.jitstats +++ b/pyre/bench/synth/getframe_while_caller_locals_across_subwalk.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_captured_frame_outlives_call.cranelift.jitstats b/pyre/bench/synth/getframe_while_captured_frame_outlives_call.cranelift.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_captured_frame_outlives_call.cranelift.jitstats +++ b/pyre/bench/synth/getframe_while_captured_frame_outlives_call.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_captured_frame_outlives_call.dynasm.jitstats b/pyre/bench/synth/getframe_while_captured_frame_outlives_call.dynasm.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_captured_frame_outlives_call.dynasm.jitstats +++ b/pyre/bench/synth/getframe_while_captured_frame_outlives_call.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_captured_frame_outlives_call.wasm.jitstats b/pyre/bench/synth/getframe_while_captured_frame_outlives_call.wasm.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_captured_frame_outlives_call.wasm.jitstats +++ b/pyre/bench/synth/getframe_while_captured_frame_outlives_call.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_escaping_read_frame_identity.cranelift.jitstats b/pyre/bench/synth/getframe_while_escaping_read_frame_identity.cranelift.jitstats index f281172da6c..2ffd81217e4 100644 --- a/pyre/bench/synth/getframe_while_escaping_read_frame_identity.cranelift.jitstats +++ b/pyre/bench/synth/getframe_while_escaping_read_frame_identity.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=10 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=20 diff --git a/pyre/bench/synth/getframe_while_escaping_read_frame_identity.dynasm.jitstats b/pyre/bench/synth/getframe_while_escaping_read_frame_identity.dynasm.jitstats index f281172da6c..2ffd81217e4 100644 --- a/pyre/bench/synth/getframe_while_escaping_read_frame_identity.dynasm.jitstats +++ b/pyre/bench/synth/getframe_while_escaping_read_frame_identity.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=10 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=20 diff --git a/pyre/bench/synth/getframe_while_escaping_read_frame_identity.wasm.jitstats b/pyre/bench/synth/getframe_while_escaping_read_frame_identity.wasm.jitstats index f281172da6c..2ffd81217e4 100644 --- a/pyre/bench/synth/getframe_while_escaping_read_frame_identity.wasm.jitstats +++ b/pyre/bench/synth/getframe_while_escaping_read_frame_identity.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=10 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=20 diff --git a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.cranelift.jitstats b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.cranelift.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.cranelift.jitstats +++ b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.dynasm.jitstats b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.dynasm.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.dynasm.jitstats +++ b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.wasm.jitstats b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.wasm.jitstats index 5d9438ae0aa..511df2a116c 100644 --- a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.wasm.jitstats +++ b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=5 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=10 diff --git a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.cranelift.jitstats b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.cranelift.jitstats index 554ab2678f1..cce2266e45f 100644 --- a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.cranelift.jitstats +++ b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=15 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=25 diff --git a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.dynasm.jitstats b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.dynasm.jitstats index 554ab2678f1..cce2266e45f 100644 --- a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.dynasm.jitstats +++ b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=15 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=25 diff --git a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.wasm.jitstats b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.wasm.jitstats index 554ab2678f1..cce2266e45f 100644 --- a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.wasm.jitstats +++ b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=15 +fbw_blackhole_adopted_single_frame=10 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=25 diff --git a/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats b/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats index fc0cca7b7db..c6a4d14bc06 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1000 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats b/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats index fc0cca7b7db..c6a4d14bc06 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1000 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats b/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats index fc0cca7b7db..c6a4d14bc06 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=1000 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/set_hash_protocol.cranelift.jitstats b/pyre/bench/synth/set_hash_protocol.cranelift.jitstats index 114b48b9fd0..56ae0032eed 100644 --- a/pyre/bench/synth/set_hash_protocol.cranelift.jitstats +++ b/pyre/bench/synth/set_hash_protocol.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/set_hash_protocol.dynasm.jitstats b/pyre/bench/synth/set_hash_protocol.dynasm.jitstats index 114b48b9fd0..56ae0032eed 100644 --- a/pyre/bench/synth/set_hash_protocol.dynasm.jitstats +++ b/pyre/bench/synth/set_hash_protocol.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/set_hash_protocol.wasm.jitstats b/pyre/bench/synth/set_hash_protocol.wasm.jitstats index 114b48b9fd0..56ae0032eed 100644 --- a/pyre/bench/synth/set_hash_protocol.wasm.jitstats +++ b/pyre/bench/synth/set_hash_protocol.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/synth/trace_too_long_effect_replay.cranelift.jitstats b/pyre/bench/synth/trace_too_long_effect_replay.cranelift.jitstats index a8d069bee2a..044dedf91f8 100644 --- a/pyre/bench/synth/trace_too_long_effect_replay.cranelift.jitstats +++ b/pyre/bench/synth/trace_too_long_effect_replay.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=6 diff --git a/pyre/bench/synth/trace_too_long_effect_replay.dynasm.jitstats b/pyre/bench/synth/trace_too_long_effect_replay.dynasm.jitstats index a8d069bee2a..044dedf91f8 100644 --- a/pyre/bench/synth/trace_too_long_effect_replay.dynasm.jitstats +++ b/pyre/bench/synth/trace_too_long_effect_replay.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=6 diff --git a/pyre/bench/synth/trace_too_long_effect_replay.wasm.jitstats b/pyre/bench/synth/trace_too_long_effect_replay.wasm.jitstats index 78de8010564..ccbb05691ff 100644 --- a/pyre/bench/synth/trace_too_long_effect_replay.wasm.jitstats +++ b/pyre/bench/synth/trace_too_long_effect_replay.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=0 +fbw_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=5 diff --git a/pyre/bench/synth/trace_too_long_inline_multiframe.cranelift.jitstats b/pyre/bench/synth/trace_too_long_inline_multiframe.cranelift.jitstats index 54b641ac5c4..c7d82ff92ef 100644 --- a/pyre/bench/synth/trace_too_long_inline_multiframe.cranelift.jitstats +++ b/pyre/bench/synth/trace_too_long_inline_multiframe.cranelift.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=11 +fbw_blackhole_adopted_single_frame=12 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=24 diff --git a/pyre/bench/synth/trace_too_long_inline_multiframe.dynasm.jitstats b/pyre/bench/synth/trace_too_long_inline_multiframe.dynasm.jitstats index 54b641ac5c4..c7d82ff92ef 100644 --- a/pyre/bench/synth/trace_too_long_inline_multiframe.dynasm.jitstats +++ b/pyre/bench/synth/trace_too_long_inline_multiframe.dynasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=11 +fbw_blackhole_adopted_single_frame=12 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=24 diff --git a/pyre/bench/synth/trace_too_long_inline_multiframe.wasm.jitstats b/pyre/bench/synth/trace_too_long_inline_multiframe.wasm.jitstats index 6c7a79c9c72..489851d3141 100644 --- a/pyre/bench/synth/trace_too_long_inline_multiframe.wasm.jitstats +++ b/pyre/bench/synth/trace_too_long_inline_multiframe.wasm.jitstats @@ -2,7 +2,10 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_blackhole_adopted_multi_frame=11 +fbw_blackhole_adopted_single_frame=11 fbw_rolled_back_with_effects=0 +fbw_store_journal_rollback_failed=0 guard_failures=0 internal_compile_panics=0 loops_aborted=23 diff --git a/pyre/check.py b/pyre/check.py index 4faba47c88c..2b796fa98f6 100644 --- a/pyre/check.py +++ b/pyre/check.py @@ -749,6 +749,12 @@ def _parse_jit_stats(snapshot): # under ordinary tuning", which made a bridge collapse (27 -> 0) invisible for # as long as `guard_failures` stayed inside its band — the whole dead-bridge # class this suite exists to catch. +# * `fbw_blackhole_adopted_single_frame` and +# `fbw_blackhole_adopted_multi_frame` count successful full-body-walk +# blackhole adoptions: the walk handed the interpreter a resumable image +# instead of making its caller replay the region. A fall means the adoption +# path stopped firing and the legacy replay path came back; a rise means more +# walks avoided replay and the baseline should say so. # # A move is not *always* a defect: two loops merging into one trace lowers # `loops_compiled` while raising coverage. That is the bargain `loops_aborted` @@ -778,6 +784,8 @@ def _parse_jit_stats(snapshot): "loops_compiled", "bridges_compiled", "guard_failures", + "fbw_blackhole_adopted_single_frame", + "fbw_blackhole_adopted_multi_frame", ) @@ -791,9 +799,14 @@ def _parse_jit_stats(snapshot): # `loops_compiled` is inverted against the badness fields: it is the counter # that falls when the tracer stops admitting a frame at all, which aborts # nothing and *lowers* `guard_failures`, so a fall is the regression and a rise -# is the gain. +# is the gain. The blackhole adoption counters are inverted the same way: a +# fall means the interpreter stopped receiving an image and went back to replay. JITSTATS_REGRESSION_ON_RISE = JITSTATS_BADNESS_FIELDS + ("guard_failures",) -JITSTATS_REGRESSION_ON_FALL = ("loops_compiled",) +JITSTATS_REGRESSION_ON_FALL = ( + "loops_compiled", + "fbw_blackhole_adopted_single_frame", + "fbw_blackhole_adopted_multi_frame", +) # How many fresh runs a disagreeing fixture gets before its counters are called # stable. Only a fixture that already disagrees pays this, so the common path is diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs index e7a325e10f6..ce8ed608781 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs @@ -3087,8 +3087,7 @@ pub(crate) fn try_execute_residual_call_via_executor( // here instead left the shapes that DID commit — a re-entry guard // plus a non-idempotent store ahead of the escaping call — on the // replay path. - if !ctx.trace_ctx.is_bridge_trace - && let Some((resume_pc, result_bank, result_color)) = blackhole_result + if let Some((resume_pc, result_bank, result_color)) = blackhole_result && !ctx.fbw_mode.snapshot_sym.is_null() { let (lastop_result, last_exc_value, raising_exception) = match exec_result { diff --git a/pyre/pyre-jit-trace/src/trace.rs b/pyre/pyre-jit-trace/src/trace.rs index 0c80a2728d9..237dc9c5c80 100644 --- a/pyre/pyre-jit-trace/src/trace.rs +++ b/pyre/pyre-jit-trace/src/trace.rs @@ -2656,6 +2656,7 @@ fn try_adopt_single_frame_blackhole( // The blackhole ran the region to a frame terminal, so the resume is // the frame's RESULT, not a pc that re-runs anything. let _ = commit_walk_end(commit_leg, WalkEndResume::Terminal); + fbw_diag::bump(fbw_diag::BLACKHOLE_ADOPTED_SINGLE_FRAME); if crate::jitcode_dispatch::fbw_debug_abort_enabled() { eprintln!( "[fbw-blackhole] adopted single-frame terminal at jitcode_index={} \ @@ -3154,6 +3155,7 @@ fn try_adopt_multi_frame_blackhole( crate::jitcode_dispatch::fbw_foriter_inflight_clear(); // Same as the single-frame adoption: a frame terminal, not a resume pc. let _ = commit_walk_end(commit_leg, WalkEndResume::Terminal); + fbw_diag::bump(fbw_diag::BLACKHOLE_ADOPTED_MULTI_FRAME); if crate::jitcode_dispatch::fbw_debug_abort_enabled() { eprintln!("[fbw-blackhole] adopted multi-frame terminal depth={depth}"); } @@ -6134,12 +6136,18 @@ pub mod fbw_diag { pub const ESCAPE_FORCE_BY_PORTAL: usize = 9; pub const ESCAPE_FORCE_BY_CALLEE_ONLY: usize = 10; pub const STORE_JOURNAL_ROLLBACK_FAILED: usize = 11; + /// Successful single-frame blackhole adoptions. A fall means the walk + /// stopped handing the interpreter an image and went back to legacy replay. + pub const BLACKHOLE_ADOPTED_SINGLE_FRAME: usize = 12; + /// Successful multi-frame blackhole adoptions. A fall means the walk + /// stopped handing the interpreter an image and went back to legacy replay. + pub const BLACKHOLE_ADOPTED_MULTI_FRAME: usize = 13; /// One ring entry per walk: four slots of outcome name (8 ASCII bytes per /// slot, little-endian) followed by one slot of packed counters. A `u64` /// export cannot carry a string, and the outcome set is far too large to /// spend a tally slot per variant. - pub const RING_BASE: usize = 12; + pub const RING_BASE: usize = 14; pub const RING_ENTRIES: usize = 24; pub const RING_STRIDE: usize = 5; pub const NAME_SLOTS: usize = 4; diff --git a/pyre/pyre-wasm-runner/src/main.rs b/pyre/pyre-wasm-runner/src/main.rs index 906b17e7c23..479c6096d56 100644 --- a/pyre/pyre-wasm-runner/src/main.rs +++ b/pyre/pyre-wasm-runner/src/main.rs @@ -620,7 +620,7 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { // which the guest cannot read. Slot layout in // `pyre_jit_trace::trace::fbw_diag`. if let Ok(fbw) = instance.get_typed_func::(&mut store, "pyre_fbw_diag") { - const RING_BASE: u32 = 12; + const RING_BASE: u32 = 14; const RING_ENTRIES: u32 = 24; const RING_STRIDE: u32 = 5; const NAME_SLOTS: u32 = 4; @@ -864,14 +864,25 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { // rollback could not perform — the one journaled effect the walk-end // subtraction must not silently absorb. Both slots come from one // export lookup so an absent export names itself once. - let (fbw_rolled_back_with_effects, fbw_store_journal_rollback_failed) = match instance + let ( + fbw_rolled_back_with_effects, + fbw_store_journal_rollback_failed, + fbw_blackhole_adopted_single_frame, + fbw_blackhole_adopted_multi_frame, + ) = match instance .get_typed_func::(&mut store, "pyre_fbw_diag") - .and_then(|f| Ok((f.call(&mut store, 1)?, f.call(&mut store, 11)?))) - { - Ok(pair) => pair, + .and_then(|f| { + Ok(( + f.call(&mut store, 1)?, + f.call(&mut store, 11)?, + f.call(&mut store, 12)?, + f.call(&mut store, 13)?, + )) + }) { + Ok(slots) => slots, Err(_) => { missing.push("pyre_fbw_diag"); - (0, 0) + (0, 0, 0, 0) } }; if !missing.is_empty() { @@ -897,7 +908,9 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { field_pos_spec_misplaced={field_pos_spec_misplaced} \ field_pos_attached_checked={field_pos_attached_checked} \ field_pos_attached_misplaced={field_pos_attached_misplaced} \ - fbw_store_journal_rollback_failed={fbw_store_journal_rollback_failed}" + fbw_store_journal_rollback_failed={fbw_store_journal_rollback_failed} \ + fbw_blackhole_adopted_single_frame={fbw_blackhole_adopted_single_frame} \ + fbw_blackhole_adopted_multi_frame={fbw_blackhole_adopted_multi_frame}" ); } let packed = match run_result { diff --git a/pyre/pyrex/src/lib.rs b/pyre/pyrex/src/lib.rs index 482a2c46bd5..2ec3e25188b 100644 --- a/pyre/pyrex/src/lib.rs +++ b/pyre/pyrex/src/lib.rs @@ -842,10 +842,20 @@ fn maybe_print_jit_stats() { // `STORE_JOURNAL_ROLLBACK_FAILED` is the hole that subtraction would // otherwise open: a journaled store the rollback could not restore is // subtracted out of the count above, so it needs one of its own. + // + // The two adoption tallies are the same population read from the other end: + // the walks that DID hand the interpreter a resumable image. Nothing else + // counts them, so a change that silently sends a shape back to the legacy + // replay moves no gated counter without these. eprintln!( - "[jit-stats] fbw_rolled_back_with_effects={} fbw_store_journal_rollback_failed={}", + "[jit-stats] fbw_rolled_back_with_effects={} \ + fbw_store_journal_rollback_failed={} \ + fbw_blackhole_adopted_single_frame={} \ + fbw_blackhole_adopted_multi_frame={}", pyre_jit::fbw_diag_counter(1), pyre_jit::fbw_diag_counter(11), + pyre_jit::fbw_diag_counter(12), + pyre_jit::fbw_diag_counter(13), ); let stats = pyre_jit::eval::driver_pair().0.get_stats(); eprintln!( From 60458c63790639bf672f1cc13bb3dc5a5b9d63b7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 06:39:42 +0900 Subject: [PATCH 03/12] jit: join the rollback-failure diag bump onto one line `cargo fmt --check` flagged fbw_state.rs:922; the call fits in 100 columns. Assisted-by: Claude --- pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs index 874b3d007cf..f6712a58018 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs @@ -934,9 +934,7 @@ pub(crate) fn fbw_store_journal_rollback() { // shrank the list after the store — a shape the replay // already cannot undo (the residual re-runs). Surface it // under the debug gate instead of corrupting silently. - crate::trace::fbw_diag::bump( - crate::trace::fbw_diag::STORE_JOURNAL_ROLLBACK_FAILED, - ); + crate::trace::fbw_diag::bump(crate::trace::fbw_diag::STORE_JOURNAL_ROLLBACK_FAILED); if fbw_debug_abort_enabled() { eprintln!("[fbw-store-journal] rollback failed (index out of bounds)"); } From bbff495f734cb0a031a62ddeca597c105f9306c7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 09:45:48 +0900 Subject: [PATCH 04/12] jit: label every decline exit of the blackhole latch and tag its call site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build_multi_frame_miframe` carried three `s2dbg!`-labelled decline arms while its seven real exits were unlabelled, five of them bare `?` on an `Option` — three of those buried in the lvalue of an assignment (`*miframe.int_values.get_mut(color)? = ...`). `?` desugars to a compiler-generated `return` whose residual for `Option` is `Option`, so it carries no payload and no call-site identity: the count of declines existed, the distribution did not. Each is now a `let ... else` carrying a message. The `s2dbg!` definition moves above the empty-framestack check so that exit can log too. `latch_abort_blackhole` takes a `&'static str` origin naming its call site and threads it into `capture_vstack_mirror_image`, `multi_frame_blackhole_preflight`, and `build_multi_frame_miframe`; every message from those functions is prefixed with it. The three call sites pass "mod2730", "mod2786", "bridge1361"; the two escape-flush sites pass "escape-flush". The framestack-empty label also prints the framestack depth and `transparent_helper_subwalk`. Every message stays behind `fbw_debug_abort_enabled()`. No condition, control-flow branch, or return value changes. Assisted-by: Claude --- .../src/jitcode_dispatch/bridge_subwalk.rs | 2 +- .../src/jitcode_dispatch/mod.rs | 4 +- .../src/jitcode_dispatch/residual_call.rs | 124 ++++++++++++------ 3 files changed, 89 insertions(+), 41 deletions(-) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs index 5de1afc9f15..ac5fd0909b2 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs @@ -1358,7 +1358,7 @@ pub(crate) fn drive_bridge_frame_subwalk( // owns their banks — `drive_bridge_carrier_walk`'s abort tail adopts // the image, and a decline there leaves the pre-existing rollback. if let Err(ref error) = outcome { - let _ = latch_abort_blackhole(&sub_wc, error.stop_pc()); + let _ = latch_abort_blackhole(&sub_wc, error.stop_pc(), "bridge1361"); } outcome }; diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs index 12abc9a96d2..b6c6b6495c8 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs @@ -2829,7 +2829,7 @@ pub fn walk( && !carrier_owned && !abort_blackhole_latched() { - let _ = latch_abort_blackhole(ctx, error.stop_pc()); + let _ = latch_abort_blackhole(ctx, error.stop_pc(), "mod2730"); } return Err(error); } @@ -2885,7 +2885,7 @@ pub fn walk( // replay would resume the caller without delivering the return or // raise that this step produced. let snapshot_safe = trace_too_long_blackhole_snapshot_safe(&outcome); - let blackhole_latched = snapshot_safe && latch_abort_blackhole(ctx, pc); + let blackhole_latched = snapshot_safe && latch_abort_blackhole(ctx, pc, "mod2786"); if trace_too_long_abort_safe(&outcome, blackhole_latched, fbw_executed_effect_count()) { let ops = ctx.trace_ctx.num_recorded_ops(); crate::state::note_root_trace_too_long( diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs index ce8ed608781..c31e2642ff7 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs @@ -263,9 +263,10 @@ macro_rules! latchdbg { /// whole image, leaving the abort on the legacy entry replay. fn capture_vstack_mirror_image( ctx: &WalkContext<'_, '_, Sym>, + origin: &'static str, ) -> Option { if !ctx.vstack_valid { - latchdbg!("mirror-invalid"); + latchdbg!("origin={origin} mirror-invalid"); return None; } let mut slots = Vec::with_capacity(ctx.vstack_boxes.len()); @@ -276,7 +277,7 @@ fn capture_vstack_mirror_image( } other => { latchdbg!( - "mirror-slot {}/{} unresolved opref={opref:?} concrete={other:?}", + "origin={origin} mirror-slot {}/{} unresolved opref={opref:?} concrete={other:?}", slots.len(), ctx.vstack_boxes.len(), ); @@ -293,9 +294,10 @@ fn capture_vstack_mirror_image( pub(crate) fn latch_abort_blackhole( ctx: &WalkContext<'_, '_, Sym>, resume_pc: usize, + origin: &'static str, ) -> bool { if !ctx.is_authoritative_executor { - latchdbg!("not-authoritative"); + latchdbg!("origin={origin} not-authoritative"); return false; } let last_exc_value = match ctx.last_exc_value_concrete { @@ -323,12 +325,12 @@ pub(crate) fn latch_abort_blackhole( }) } }) else { - latchdbg!("no-snapshot-sym-jitcode"); + latchdbg!("origin={origin} no-snapshot-sym-jitcode"); return false; }; let Some(miframe) = build_trace_too_long_single_frame_miframe(ctx, jitcode, resume_pc) else { - latchdbg!("sf-build-miframe"); + latchdbg!("origin={origin} sf-build-miframe"); return false; }; // `walk()` has already executed this step, so returning TraceTooLong @@ -337,13 +339,13 @@ pub(crate) fn latch_abort_blackhole( // to entry replay. Keep the same boundary: incomplete images merely // keep recording until a later step supplies a complete handoff. let Some(jitcode_index) = i32::try_from(miframe.jitcode.index()).ok() else { - latchdbg!("sf-jitcode-index"); + latchdbg!("origin={origin} sf-jitcode-index"); return false; }; if ctx.trace_ctx.virtualizable_info().is_none() || crate::state::concrete_nlocals(cf_addr).is_none() { - latchdbg!("sf-no-vinfo-or-nlocals"); + latchdbg!("origin={origin} sf-no-vinfo-or-nlocals"); return false; } let root_addr = if live_root_addr != 0 { @@ -364,7 +366,7 @@ pub(crate) fn latch_abort_blackhole( || !crate::state::can_write_back_outer_locals(ctx.trace_ctx, vable_frame) || !crate::state::can_publish_frame_stack(cf_addr, vable_frame) { - latchdbg!("sf-vable-frame-mismatch"); + latchdbg!("origin={origin} sf-vable-frame-mismatch"); return false; } // Keep the per-frame red identity seeded by `frame_box`. The @@ -378,7 +380,7 @@ pub(crate) fn latch_abort_blackhole( // operand stack the adopter publishes. `ABORT_TOO_LONG` stops at an // opcode boundary and keeps the snapshot-array source; a capability-gap // abort stops mid-opcode and needs this. - let mirror_stack = capture_vstack_mirror_image(ctx); + let mirror_stack = capture_vstack_mirror_image(ctx, origin); FBW_SINGLE_FRAME_BLACKHOLE.with(|slot| { *slot.borrow_mut() = Some(LatchedSingleFrameBlackhole { miframe, @@ -390,13 +392,13 @@ pub(crate) fn latch_abort_blackhole( true } else if ctx.fbw_mode.inline_subwalk { let Some(framestack) = - build_multi_frame_miframe(ctx, resume_pc, InnermostMiframeBuild::TraceTooLong) + build_multi_frame_miframe(ctx, resume_pc, InnermostMiframeBuild::TraceTooLong, origin) else { - latchdbg!("mf-build-miframe"); + latchdbg!("origin={origin} mf-build-miframe"); return false; }; - if !multi_frame_blackhole_preflight(ctx, &framestack) { - latchdbg!("mf-preflight"); + if !multi_frame_blackhole_preflight(ctx, &framestack, origin) { + latchdbg!("origin={origin} mf-preflight"); return false; } FBW_MULTI_FRAME_BLACKHOLE.with(|slot| { @@ -419,7 +421,7 @@ pub(crate) fn latch_abort_blackhole( true } else { latchdbg!( - "no-arm framestack_empty={} inline_subwalk={}", + "origin={origin} no-arm framestack_empty={} inline_subwalk={}", ctx.session.borrow().framestack.is_empty(), ctx.fbw_mode.inline_subwalk ); @@ -436,9 +438,10 @@ pub(crate) fn latch_abort_blackhole( fn multi_frame_blackhole_preflight( ctx: &WalkContext<'_, '_, Sym>, framestack: &majit_metainterp::MIFrameStack, + origin: &'static str, ) -> bool { if ctx.trace_ctx.virtualizable_info().is_none() || ctx.fbw_mode.snapshot_sym.is_null() { - latchdbg!("pf-no-vinfo-or-sym"); + latchdbg!("origin={origin} pf-no-vinfo-or-sym"); return false; } let sym = unsafe { &*ctx.fbw_mode.snapshot_sym }; @@ -449,7 +452,7 @@ fn multi_frame_blackhole_preflight( }; let root = if live_root != 0 { live_root } else { snapshot }; latchdbg!( - "pf-root-caps snapshot={snapshot:#x} root={root:#x} nlocals={} locals={} writeback={} publish={}", + "origin={origin} pf-root-caps snapshot={snapshot:#x} root={root:#x} nlocals={} locals={} writeback={} publish={}", crate::state::concrete_nlocals(snapshot).is_some(), crate::state::capture_frame_locals(root).is_some(), crate::state::can_write_back_outer_locals(ctx.trace_ctx, root), @@ -466,17 +469,17 @@ fn multi_frame_blackhole_preflight( let mut seen = Vec::with_capacity(framestack.frames.len()); for (index, frame) in framestack.frames.iter().enumerate() { let Ok(jitcode_index) = i32::try_from(frame.jitcode.index()) else { - latchdbg!("pf-jitcode-index"); + latchdbg!("origin={origin} pf-jitcode-index"); return false; }; let frame_reg = crate::state::portal_red_regs_at(jitcode_index).0; if frame_reg == u16::MAX { - latchdbg!("pf-frame-reg-none"); + latchdbg!("origin={origin} pf-frame-reg-none"); return false; } let Some(frame_ptr) = frame.ref_values.get(frame_reg as usize).copied().flatten() else { latchdbg!( - "pf-frame-ptr-unset index={index}/{} jitcode={} frame_reg={frame_reg}", + "origin={origin} pf-frame-ptr-unset index={index}/{} jitcode={} frame_reg={frame_reg}", framestack.frames.len(), frame.jitcode.name() ); @@ -484,15 +487,15 @@ fn multi_frame_blackhole_preflight( }; let frame_ptr = frame_ptr as usize; let Some(stack_base) = crate::state::concrete_nlocals(frame_ptr) else { - latchdbg!("pf-nlocals"); + latchdbg!("origin={origin} pf-nlocals"); return false; }; let Some(stack_depth) = crate::state::concrete_stack_depth(frame_ptr) else { - latchdbg!("pf-stack-depth"); + latchdbg!("origin={origin} pf-stack-depth"); return false; }; let Some(array_len) = crate::state::concrete_frame_array_len(frame_ptr) else { - latchdbg!("pf-array-len"); + latchdbg!("origin={origin} pf-array-len"); return false; }; if stack_depth < stack_base @@ -501,7 +504,7 @@ fn multi_frame_blackhole_preflight( || (index > 0 && frame_ptr == root) || seen.contains(&frame_ptr) { - latchdbg!("pf-shape"); + latchdbg!("origin={origin} pf-shape"); return false; } seen.push(frame_ptr); @@ -832,11 +835,8 @@ fn build_multi_frame_miframe( ctx: &WalkContext<'_, '_, Sym>, resume_pc: usize, innermost_build: InnermostMiframeBuild, + origin: &'static str, ) -> Option { - let session = ctx.session.borrow(); - if session.framestack.is_empty() { - return None; - } macro_rules! s2dbg { ($($a:tt)*) => { if fbw_debug_abort_enabled() { @@ -844,30 +844,68 @@ fn build_multi_frame_miframe( } }; } + let session = ctx.session.borrow(); + if session.framestack.is_empty() { + s2dbg!( + "origin={origin} framestack empty depth={} transparent_helper_subwalk={}", + session.framestack.len(), + ctx.fbw_mode.transparent_helper_subwalk + ); + return None; + } let mut frames = majit_metainterp::MIFrameStack::empty(); for (index, inline) in session.framestack.iter().enumerate() { let Some(parent) = inline.parent.as_ref() else { - s2dbg!("frame {index}: no parent"); + s2dbg!("origin={origin} frame {index}: no parent"); return None; }; let Some(concrete) = parent.blackhole.as_ref() else { - s2dbg!("frame {index}: parent.blackhole None (capture missing)"); + s2dbg!("origin={origin} frame {index}: parent.blackhole None (capture missing)"); + return None; + }; + let Some(pjc) = crate::state::pyjitcode_for_jitcode_index(parent.jitcode_index as i32) + else { + s2dbg!( + "origin={origin} frame {index}: no pyjitcode for parent jitcode_index={}", + parent.jitcode_index + ); return None; }; - let pjc = crate::state::pyjitcode_for_jitcode_index(parent.jitcode_index as i32)?; let mut miframe = majit_metainterp::MIFrame::new(pjc.jitcode.clone(), concrete.resume_pc); for &(color, value) in &concrete.int_values { - *miframe.int_values.get_mut(color)? = Some(value); + let bank_len = miframe.int_values.len(); + let Some(slot) = miframe.int_values.get_mut(color) else { + s2dbg!( + "origin={origin} frame {index}: int color {color} out of range (len {bank_len})" + ); + return None; + }; + *slot = Some(value); } for &(color, value) in &concrete.ref_values { - *miframe.ref_values.get_mut(color)? = Some(value as i64); + let bank_len = miframe.ref_values.len(); + let Some(slot) = miframe.ref_values.get_mut(color) else { + s2dbg!( + "origin={origin} frame {index}: ref color {color} out of range (len {bank_len})" + ); + return None; + }; + *slot = Some(value as i64); } for &(color, opref) in &concrete.float_values { let Some(majit_ir::Value::Float(value)) = ctx.trace_ctx.concrete_of_opref(opref) else { + s2dbg!("origin={origin} frame {index}: float opref {opref:?} not a stamped Float"); return None; }; - *miframe.float_values.get_mut(color)? = Some(value.to_bits() as i64); + let bank_len = miframe.float_values.len(); + let Some(slot) = miframe.float_values.get_mut(color) else { + s2dbg!( + "origin={origin} frame {index}: float color {color} out of range (len {bank_len})" + ); + return None; + }; + *slot = Some(value.to_bits() as i64); } frames.push(miframe); } @@ -880,13 +918,19 @@ fn build_multi_frame_miframe( // callee's own jitcode so all three share its coordinate space; fall back // to `snapshot_sym` for a top-level (non-sub-walk) abort. let innermost_jitcode = if let Some(consts) = ctx.inline_callee_consts { - let jc = crate::state::pyjitcode_for_jitcode_index(consts.jitcode_index)?; + let Some(jc) = crate::state::pyjitcode_for_jitcode_index(consts.jitcode_index) else { + s2dbg!( + "origin={origin} innermost: no pyjitcode for callee jitcode_index={}", + consts.jitcode_index + ); + return None; + }; jc.jitcode.clone() } else { unsafe { let sym = &*ctx.fbw_mode.snapshot_sym; if sym.jitcode().is_null() { - s2dbg!("innermost snapshot_sym jitcode null"); + s2dbg!("origin={origin} innermost snapshot_sym jitcode null"); return None; } (&(*sym.jitcode()).payload).jitcode.clone() @@ -901,11 +945,14 @@ fn build_multi_frame_miframe( } }; let Some(innermost) = innermost else { - s2dbg!("innermost build_single_frame_miframe declined"); + s2dbg!("origin={origin} innermost build_single_frame_miframe declined"); return None; }; frames.push(innermost); - s2dbg!("BUILT multi-frame depth={}", frames.frames.len()); + s2dbg!( + "origin={origin} BUILT multi-frame depth={}", + frames.frames.len() + ); Some(frames) } @@ -3113,7 +3160,7 @@ pub(crate) fn try_execute_residual_call_via_executor( // blackhole can reach a `getarrayitem_vable_r` that // reloads an operand from the virtualizable array, so // publish the root stack from the walker's mirror. - let mirror_stack = capture_vstack_mirror_image(ctx); + let mirror_stack = capture_vstack_mirror_image(ctx, "escape-flush"); FBW_SINGLE_FRAME_BLACKHOLE.with(|slot| { *slot.borrow_mut() = Some(LatchedSingleFrameBlackhole { miframe, @@ -3154,6 +3201,7 @@ pub(crate) fn try_execute_residual_call_via_executor( ctx, resume_pc, InnermostMiframeBuild::LiveMarker(lastop_result), + "escape-flush", ) { FBW_MULTI_FRAME_BLACKHOLE.with(|slot| { From 5009e20e4ba3e3a5319b57f0964e4f911b19dbf3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 12:21:19 +0900 Subject: [PATCH 05/12] jit: mark every canonical sub-jitcode descent as a transparent helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `run_sub_jitcode_walk` built its sub-context with `inline_subwalk: true` and inherited `transparent_helper_subwalk` from the caller, so a descent from a root-level Python walk ran with the flag false and the session framestack empty. Two sites read that combination: * the abort-coordinate claim in `jitcode_dispatch::walk` consumed the session's one-shot claim at a helper pc; * the post-step trace-limit check ran inside the helper and called `latch_abort_blackhole`, which takes its `inline_subwalk` arm and there calls `build_multi_frame_miframe` — which declines immediately on an empty framestack. The abort therefore latched no blackhole image and fell back to entry replay. Both sites already carry `!transparent_helper_subwalk` exclusions written for exactly this case. Set the flag on the sub-context `run_sub_jitcode_walk` builds, which is the single point every canonical helper descent passes through, and drop the `nested_helper_entry.is_some()` assignment at the builtin-gateway call site that only covered helpers entered from another sub-walk. Measured over the 373-fixture synth corpus with `PYRE_FBW_DEBUG_ABORT=1`: `mf-build-miframe` declines 97 -> 0 (94 from the trace-limit latch, 3 from the error-unwind latch); the 56 image builds that already succeeded are unchanged. `check.py` dynasm 1 / cranelift 1 / wasm 4 failed, the same rows with the same numbers as before the change. Assisted-by: Claude --- .../src/jitcode_dispatch/inline_call.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs index 807f057102e..b7d6d34f846 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs @@ -2397,7 +2397,9 @@ pub(crate) fn try_walker_inline_builtin_call( ctx.sub_jitcode_lookup = &GLOBAL_SUB_JITCODE_LOOKUP_FN; ctx.fbw_mode.inline_subwalk = true; ctx.fbw_mode.inline_caller_py_pc = Some(call_site_py_pc); - ctx.fbw_mode.transparent_helper_subwalk = nested_helper_entry.is_some(); + // `transparent_helper_subwalk` is set by `run_sub_jitcode_walk` on the + // sub-context it builds, so every descent into a canonical helper body + // carries it — not just the ones entered from another sub-walk. let _helper_frame = nested_helper_entry.map(|frame| InlineFrameGuard::enter(ctx.session, 0, Some(frame))); let walk_result = run_sub_jitcode_walk( @@ -6359,6 +6361,18 @@ pub(crate) fn run_sub_jitcode_walk( // the carried outer Python boundary. fbw_mode: FbwWalkMode { inline_subwalk: true, + // A canonical sub-jitcode body has no blackhole entry point of + // its own — the fact the `op_pc` comment above states — so the + // whole descent is transparent to the Python MIFrame stack, no + // matter whether the caller happens to be another sub-walk. + // Left inherited, a root-level descent walked with + // `inline_subwalk` set and the framestack empty, which sends + // `latch_abort_blackhole` into its multi-frame arm: that arm + // declines on the empty framestack, so the abort latched no + // image and fell back to entry replay — the outcome the + // transparent-helper exclusions at the abort-coordinate claim + // and the post-step trace-limit check exist to prevent. + transparent_helper_subwalk: true, ..ctx.fbw_mode }, session: ctx.session, From eb0cc0d9822442b88b3a2fcd0d7489d781a630d9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 12:47:26 +0900 Subject: [PATCH 06/12] jit: a same-py_pc boundary is not an out-of-order block transition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `reconcile_vstack_at_boundary` computed `cfg_successor` from the previous opcode's fallthrough and branch targets only, so a boundary reporting the py_pc the walk is already on failed the test and armed the out-of-order permutation region. One Python opcode's jitcode expansion can carry more than one boundary marker, so this fires on ordinary code. Inside that region the per-op reconcile is replaced by `ShadowReseed`, which clears every operand-stack mirror slot and re-sources them from the virtualizable shadow; a slot the shadow cannot source stays `OpRef::NONE` and `capture_vstack_mirror_image` declines on it. The repeat is already modelled by the `layout_only_boundary` arm — the observed depth matches neither successor, so it preserves the surviving slots — and that arm is the one the reorder region excludes. Accept a same-py_pc boundary as a CFG successor. A genuine self-branch is unaffected: `target_pc` reports it, so the existing clause accepts it. `getframe_bridge_force_after_store` then captures the escape image it could not build before: `fbw_rolled_back_with_effects` 20 -> 0 and `fbw_blackhole_adopted_single_frame` 0 -> 20 on dynasm, cranelift and wasm alike — the walk resumes forward instead of rolling back and letting the legacy entry replay apply its un-journaled `box.n = i` store a second time. Baselines re-recorded on all three backends and the fixture header updated to describe what it now pins. Corpus-wide `mirror-slot ... unresolved` latch declines 23 -> 2 (`PYRE_FBW_DEBUG_ABORT=1` over the 373 synth fixtures). `check.py` cranelift 1 / wasm 4 failed, all base-owned rows with unchanged numbers; dynasm 0. Assisted-by: Claude --- ...ame_bridge_force_after_store.cranelift.jitstats | 6 ++++-- ...tframe_bridge_force_after_store.dynasm.jitstats | 6 ++++-- .../synth/getframe_bridge_force_after_store.py | 13 ++++++------- ...getframe_bridge_force_after_store.wasm.jitstats | 6 ++++-- .../src/jitcode_dispatch/vstack_mirror.rs | 14 +++++++++++++- 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats index 24f291c19cf..959c5f5446c 100644 --- a/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats @@ -3,9 +3,11 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 -fbw_blackhole_adopted_single_frame=0 -fbw_rolled_back_with_effects=20 +fbw_blackhole_adopted_single_frame=20 +fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=4114 internal_compile_panics=0 loops_aborted=20 diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats index 24f291c19cf..959c5f5446c 100644 --- a/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats @@ -3,9 +3,11 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 -fbw_blackhole_adopted_single_frame=0 -fbw_rolled_back_with_effects=20 +fbw_blackhole_adopted_single_frame=20 +fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=4114 internal_compile_panics=0 loops_aborted=20 diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.py b/pyre/bench/synth/getframe_bridge_force_after_store.py index 5054fc9c09a..3d0b95ba2b1 100644 --- a/pyre/bench/synth/getframe_bridge_force_after_store.py +++ b/pyre/bench/synth/getframe_bridge_force_after_store.py @@ -2,14 +2,13 @@ # un-journaled store ahead of the forcing call. # # `box.n = i` lowers to a Void `store_attr_fn` residual: it writes live heap, so -# it bumps the executed-effect odometer, and no journal covers it. The walk -# therefore reaches `run_perfn_walk`'s epilogue uncommitted carrying an -# unrecoverable effect, and the legacy replay the caller falls back to applies -# that store a second time -- the population -# `fbw_rolled_back_with_effects` names. +# it bumps the executed-effect odometer, and no journal covers it. Rolling the +# walk back therefore cannot undo the store, and the legacy entry replay would +# apply it a second time. The escape has to capture its operand-stack mirror and +# resume forward instead, which is what the recorded +# `fbw_blackhole_adopted_single_frame` pins; `fbw_rolled_back_with_effects` back +# above zero means the capture broke and the store is running twice again. # -# The store is also what leaves the escape's operand-stack mirror slot -# unresolved, so this shape cannot capture an image even where the sibling can. # The forcing residual itself never contributes an effect: the force branch # returns before the odometer bump, so a bridge escape needs a second, earlier # effectful op to register at all -- which is exactly what this file adds. diff --git a/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats index 24f291c19cf..959c5f5446c 100644 --- a/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats @@ -3,9 +3,11 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 -fbw_blackhole_adopted_single_frame=0 -fbw_rolled_back_with_effects=20 +fbw_blackhole_adopted_single_frame=20 +fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=4114 internal_compile_panics=0 loops_aborted=20 diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs index 686b366cead..4d4da26d1eb 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs @@ -413,7 +413,19 @@ pub(crate) fn reconcile_vstack_at_boundary( | Instruction::Reraise { .. } | Instruction::RaiseVarargs { .. } ); - let cfg_successor = (has_fallthrough && new_pypc as usize == fallthrough) + // A boundary that reports the SAME py_pc is not a block transition at all: + // the walk has not left this Python opcode, so no source block was visited + // out of order. One opcode's jitcode expansion can carry more than one + // boundary marker, and treating the repeat as a backed-off transition armed + // the permutation region on the spot, which forces `ShadowReseed` below and + // clears every mirror slot — including the ones this opcode did not touch. + // The `layout_only_boundary` arm already models a repeat correctly (the + // observed depth matches neither successor of the previous opcode, so it + // preserves the surviving slots), and it is the arm the reorder region + // excludes. A genuine self-branch is unaffected: `target_pc` reports it, + // so the clause below already accepts it. + let cfg_successor = new_pypc as usize == prev_pypc + || (has_fallthrough && new_pypc as usize == fallthrough) || crate::liveness::target_pc(code, &instr, prev_pypc, op_arg) == Some(new_pypc as usize); if !cfg_successor && ctx.vstack_reorder_ceiling == u32::MAX { ctx.vstack_reorder_ceiling = (new_pypc as usize).max(prev_pypc) as u32; From e3a078e9a3e9bc0d4f19a8d35d7e651c4c499790 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 14:10:31 +0900 Subject: [PATCH 07/12] jit: stop tagging blackhole-image successes as declines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three of the lines the `[latch-decline]` / `[s2-build-decline]` / `[s2-adopt-decline]` macros carry are not refusals, and a census that groups by that tag counts them as such. * `build_multi_frame_miframe`'s closing `BUILT multi-frame depth=N` is the build's success report. It moves to a sibling macro tagged `[s2-build-ok]` (56 lines over the synth corpus). * `multi_frame_blackhole_preflight`'s `pf-root-caps` line was emitted ahead of the four-way capability test, so it also announced every passing preflight, and it re-evaluated all four predicates that the test then evaluated again. It becomes an `else if` chain naming the first capability that refuses, reported only on the refusal; the evaluation order and short-circuiting are unchanged. The corpus has no refusal, so those 11 lines are gone. * `try_adopt_multi_frame_blackhole`'s `chain root=… levels=[…]` is emitted before the root-identity check and reports recovered state on both paths. It moves to `[s2-adopt-state]` (56 lines, matching the 56 builds). The same function's `no latched multi-frame image` is a real decline but the ordinary one: `try_adopt_blackhole` tries this arm before the single-frame arm, so every single-frame latch produces one. The message says so. Over the 373 synth fixtures the decline-tagged population falls from 269 to 155, of which 153 are that fallthrough. The remaining two are the `mirror-slot … unresolved` pair. No gate, condition or return value changes; every line stays behind `fbw_debug_abort_enabled()`. Assisted-by: Claude --- .../src/jitcode_dispatch/residual_call.rs | 43 +++++++++++++------ pyre/pyre-jit-trace/src/trace.rs | 17 +++++++- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs index c31e2642ff7..774a3e074d6 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs @@ -451,18 +451,25 @@ fn multi_frame_blackhole_preflight( _ => sym.live_vable_frame_addr(), }; let root = if live_root != 0 { live_root } else { snapshot }; - latchdbg!( - "origin={origin} pf-root-caps snapshot={snapshot:#x} root={root:#x} nlocals={} locals={} writeback={} publish={}", - crate::state::concrete_nlocals(snapshot).is_some(), - crate::state::capture_frame_locals(root).is_some(), - crate::state::can_write_back_outer_locals(ctx.trace_ctx, root), - crate::state::can_publish_frame_stack(snapshot, root) - ); - if crate::state::concrete_nlocals(snapshot).is_none() - || crate::state::capture_frame_locals(root).is_none() - || !crate::state::can_write_back_outer_locals(ctx.trace_ctx, root) - || !crate::state::can_publish_frame_stack(snapshot, root) - { + // Named by the first capability that refuses, and reported only then. + // Emitted ahead of the test, the line announced every passing preflight + // under the decline tag as well, so a census could not tell a rejected root + // from an accepted one. + let refused = if crate::state::concrete_nlocals(snapshot).is_none() { + Some("nlocals") + } else if crate::state::capture_frame_locals(root).is_none() { + Some("locals") + } else if !crate::state::can_write_back_outer_locals(ctx.trace_ctx, root) { + Some("writeback") + } else if !crate::state::can_publish_frame_stack(snapshot, root) { + Some("publish") + } else { + None + }; + if let Some(refused) = refused { + latchdbg!( + "origin={origin} pf-root-caps snapshot={snapshot:#x} root={root:#x} refused={refused}" + ); return false; } @@ -844,6 +851,16 @@ fn build_multi_frame_miframe( } }; } + // The build's one success report. It has to carry its own tag: a census + // groups these lines by their `[…-decline]` prefix, so a completed build + // announced under that prefix is counted as a refusal. + macro_rules! s2built { + ($($a:tt)*) => { + if fbw_debug_abort_enabled() { + eprintln!("[s2-build-ok] {}", format!($($a)*)); + } + }; + } let session = ctx.session.borrow(); if session.framestack.is_empty() { s2dbg!( @@ -949,7 +966,7 @@ fn build_multi_frame_miframe( return None; }; frames.push(innermost); - s2dbg!( + s2built!( "origin={origin} BUILT multi-frame depth={}", frames.frames.len() ); diff --git a/pyre/pyre-jit-trace/src/trace.rs b/pyre/pyre-jit-trace/src/trace.rs index 237dc9c5c80..fd7cf96d08b 100644 --- a/pyre/pyre-jit-trace/src/trace.rs +++ b/pyre/pyre-jit-trace/src/trace.rs @@ -2702,8 +2702,21 @@ fn try_adopt_multi_frame_blackhole( } }; } + // Same gate, for a line that reports recovered state rather than a refusal. + // A census groups these lines by their tag, so a report emitted under the + // decline tag is counted as one. + macro_rules! mfstate { + ($($a:tt)*) => { + if crate::jitcode_dispatch::fbw_debug_abort_enabled() { + eprintln!("[s2-adopt-state] {}", format!($($a)*)); + } + }; + } let Some(mut latched) = crate::jitcode_dispatch::take_multi_frame_blackhole() else { - mfdbg!("no latched multi-frame image"); + // `try_adopt_blackhole` tries this arm first and the single-frame arm + // second, so an absent multi-frame image is the ordinary dispatch + // fallthrough for every single-frame latch, not a capability gap. + mfdbg!("no latched multi-frame image; single-frame adopt follows"); return false; }; let depth = latched.framestack.len(); @@ -2807,7 +2820,7 @@ fn try_adopt_multi_frame_blackhole( // The root gate stays regardless: a residual-intermediate chain is not an // MIFrame stack rooted at this portal and cannot reuse this walk's restart // coordinate. - mfdbg!( + mfstate!( "chain root={root_addr:#x} cf_addr={cf_addr:#x} levels=[{}]", per_frame .iter() From 39b64c47c0f3c3c8d702a17dcf25f69a47f6aa6b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 14:34:33 +0900 Subject: [PATCH 08/12] jit: end the vstack reorder region before the boundary that leaves it `reconcile_vstack_at_boundary` cleared `vstack_reorder_ceiling` at the end of the call, after the boundary's class had already been applied. The comment on that clear says the region lasts "until the walk passes both endpoints", but the boundary that passes them still ran under `ShadowReseed`. That exiting boundary is an ordinary sequential step: its previous opcode produced `vstack_last_ref`, and `ResultToTos` would land it on the new TOS. `ShadowReseed` instead clears the mirror and re-sources from the virtualizable shadow, which cannot recover a slot whose only producer is a walk register -- a LOAD_NAME/LOAD_FAST temp is never written through to the portal array. The slot stays `OpRef::NONE` and `capture_vstack_mirror_image` declines the whole image. Move the clear ahead of the arming below. The order matters: a boundary that both passes the old ceiling and is itself out of order now opens a new region instead of running unprotected. `trace_too_long_effect_replay` reaches its trace-limit latch with a complete mirror: over the 373 synth fixtures the `mirror-slot ... unresolved` declines fall 2 -> 1, no other decline shape moves, and the 56 multi-frame image builds are unchanged. `check.py` dynasm 1 / cranelift 1 / wasm 4 failed, the same base-owned rows with the same numbers as before the change. The remaining one is a different cause: an unwind to an exception handler is not modelled as a CFG successor, so it arms the region across the whole handler body. Assisted-by: Claude --- .../src/jitcode_dispatch/vstack_mirror.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs index 4d4da26d1eb..5711411345e 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs @@ -427,6 +427,19 @@ pub(crate) fn reconcile_vstack_at_boundary( let cfg_successor = new_pypc as usize == prev_pypc || (has_fallthrough && new_pypc as usize == fallthrough) || crate::liveness::target_pc(code, &instr, prev_pypc, op_arg) == Some(new_pypc as usize); + // #389(b): leave the out-of-order permutation region once the walk has + // advanced PAST the py_pc it backed off from — py order is monotonic again + // and the per-op reconcile is valid from here, INCLUDING at this boundary. + // Tested after the class had already been applied, the region covered one + // boundary too many: the exiting step is an ordinary sequential one whose + // previous opcode really did produce `vstack_last_ref`, and `ShadowReseed` + // dropped that box for the reseed, which cannot recover a slot the shadow + // does not carry. Ordered before the arming below so a boundary that both + // passes the old ceiling and is itself out of order opens a new region + // instead of running unprotected. + if ctx.vstack_reorder_ceiling != u32::MAX && new_pypc > ctx.vstack_reorder_ceiling { + ctx.vstack_reorder_ceiling = u32::MAX; + } if !cfg_successor && ctx.vstack_reorder_ceiling == u32::MAX { ctx.vstack_reorder_ceiling = (new_pypc as usize).max(prev_pypc) as u32; } @@ -632,12 +645,6 @@ pub(crate) fn reconcile_vstack_at_boundary( ctx.vstack_depth = new_depth; ctx.vstack_last_ref = OpRef::NONE; } - // #389(b): leave the out-of-order permutation region once the walk has - // advanced PAST the py_pc it backed off from — py order is monotonic again - // and the per-op reconcile is valid from here. - if ctx.vstack_reorder_ceiling != u32::MAX && new_pypc > ctx.vstack_reorder_ceiling { - ctx.vstack_reorder_ceiling = u32::MAX; - } } /// Fill missing operand-stack boxes from the active inline frame's own From a160e18ec2f1d9c40497e40ab8f61038700f7b1f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 14:48:28 +0900 Subject: [PATCH 09/12] jit: model the exception-table handler edge as a CFG successor `reconcile_vstack_at_boundary`'s `cfg_successor` asked `liveness::target_pc`, which reads a branch delta out of the instruction's own operand. Under the zero-cost exception tables a handler target is not in the opcode, it is in `co_exceptiontable`, so an unwind from a covered pc to its handler matched no clause and armed the out-of-order permutation region. Arming it there is expensive: inside the region every boundary is forced to `ShadowReseed`, which clears the operand-stack mirror and re-sources from the virtualizable shadow, and the shadow cannot recover a slot whose only producer is a walk register. The ceiling is `max(prev, new)`, so an unwind backwards to a handler covers the whole handler body. `liveness::exception_target_pc` reads the covering entry's target through `pycode::lookup_exceptiontable`, converting byte offsets to code-unit indices at both ends as the codewriter's decode sites do. Across 40 exception-carrying synth fixtures the new clause is the only one that accepts the boundary on 62 of 10939 reconcile events -- 62 permutation regions that no longer open. No corpus decline moves: the one remaining `mirror-slot ... unresolved` is a `53 -> 35` transition that is not a successor of 53 under any of the four clauses (`Reraise` has no fallthrough and no branch operand, and the handler covering 53 targets 54), so it is tracked separately. The `PYRE_VSTACK_DIAG` line prints the successor set it decided against, `succ=(ft=..,br=..,exc=..)`. Without it an arming cannot be told apart from a real permutation. `check.py`: dynasm 390/390, cranelift 1 failed, wasm 4 failed -- the base-owned rows, numbers unchanged. Assisted-by: Claude --- .../src/jitcode_dispatch/vstack_mirror.rs | 21 ++++++++++++++++--- pyre/pyre-jit-trace/src/liveness.rs | 19 +++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs index 5711411345e..ac1f9e188e3 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs @@ -424,9 +424,16 @@ pub(crate) fn reconcile_vstack_at_boundary( // preserves the surviving slots), and it is the arm the reorder region // excludes. A genuine self-branch is unaffected: `target_pc` reports it, // so the clause below already accepts it. + // The handler an exception raised at `prev_pypc` transfers to is a CFG + // successor as much as a branch delta is; it just lives in + // `co_exceptiontable` instead of the opcode's operand, so `target_pc` + // cannot report it. Left out, an unwind to the handler read as a block + // permutation and armed the reorder region over the whole handler body, + // where `ShadowReseed` drops every operand box the shadow does not carry. let cfg_successor = new_pypc as usize == prev_pypc || (has_fallthrough && new_pypc as usize == fallthrough) - || crate::liveness::target_pc(code, &instr, prev_pypc, op_arg) == Some(new_pypc as usize); + || crate::liveness::target_pc(code, &instr, prev_pypc, op_arg) == Some(new_pypc as usize) + || crate::liveness::exception_target_pc(code, prev_pypc) == Some(new_pypc as usize); // #389(b): leave the out-of-order permutation region once the walk has // advanced PAST the py_pc it backed off from — py order is monotonic again // and the per-op reconcile is valid from here, INCLUDING at this boundary. @@ -450,8 +457,16 @@ pub(crate) fn reconcile_vstack_at_boundary( eprintln!( "[vstack-reconcile] prev_pypc={prev_pypc} new_pypc={new_pypc} \ new_depth={new_depth} prev_depth={} class={class:?} reorder={in_reorder_region} \ - last_ref={:?} instr={instr:?}", - ctx.vstack_depth, ctx.vstack_last_ref + succ=(ft={},br={:?},exc={:?}) last_ref={:?} instr={instr:?}", + ctx.vstack_depth, + if has_fallthrough { + fallthrough as isize + } else { + -1 + }, + crate::liveness::target_pc(code, &instr, prev_pypc, op_arg), + crate::liveness::exception_target_pc(code, prev_pypc), + ctx.vstack_last_ref ); } // A JitCode's block layout can visit source-PC floor segments out of diff --git a/pyre/pyre-jit-trace/src/liveness.rs b/pyre/pyre-jit-trace/src/liveness.rs index b2a98fd8b19..e016176d8fa 100644 --- a/pyre/pyre-jit-trace/src/liveness.rs +++ b/pyre/pyre-jit-trace/src/liveness.rs @@ -880,6 +880,25 @@ pub(crate) fn target_pc( } } +/// Handler PC an exception raised at `pc` transfers to, or `None` when no +/// entry covers `pc`. +/// +/// [`target_pc`] only reads a branch delta out of the instruction's own +/// operand, so it cannot see this edge: under the zero-cost exception tables +/// the target lives in `co_exceptiontable`, not in the raising opcode. The +/// edge is no less a control-flow successor for that — a caller enumerating +/// an opcode's successors needs both. +/// +/// `lookup_exceptiontable` speaks byte offsets and the walk speaks code-unit +/// indices, so both ends are converted here, the same convention the +/// codewriter states at its own decode sites. +pub(crate) fn exception_target_pc(code: &CodeObject, pc: usize) -> Option { + let offset = u32::try_from(pc * 2).ok()?; + let (target, _depth, _lasti) = + pyre_interpreter::pycode::lookup_exceptiontable(&code.exceptiontable, offset)?; + Some(target as usize / 2) +} + /// Cache liveness info per CodeObject pointer. /// codewriter/liveness.py parity: thread-local cache of computed liveness. pub fn liveness_for(code: *const CodeObject) -> &'static LiveVars { From 4f79cda275af57182ee5c9d203cbffbb13c7c1be Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 16:41:34 +0900 Subject: [PATCH 10/12] jit: read the exception handler's py coordinate from co_exceptiontable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `vstack_enter_exception_handler` took the handler's Python pc from the floor segment of the catch target's JitCode offset. A catch target is an out-of-line landing block — `decode_exception_catch_sites` mints one per covered py_pc and the dispatch loop emits them all after the body — so it carries no py pivot of its own and the floor answers with whatever segment it was laid out inside. On the corpus's last blackhole-image decline that named the `RERAISE` ending the handler (py 53) where the walk went on to report the handler's `PUSH_EXC_INFO` entry (py 35): wrong coordinate, wrong depth, and a backwards boundary that armed the reorder region over the whole handler body. The codewriter states this same hazard next to `abort_permanent_py_pc_by_jit_pc`. Read the coordinate from `co_exceptiontable` instead — the unwind target of the entry covering the raising opcode. `lookup_exceptiontable` keeps the last matching entry, the rule `decode_exception_catch_sites` uses to pick the landing block, so the two agree by construction. The depth follows the resolved pc; the JitCode-offset-keyed twin can only answer for the floor segment, so it stays on that path with its audit. The landing block's own bytes lower no Python opcode. `vstack_handler_landing_py` holds the handler-entry coordinate while the walk is inside them, so they no longer read as a boundary either. `[vstack-handler]` reports both coordinates; `[vstack-reconcile]` names the code object and whether the walk is a sub-walk. Over the 373 synth fixtures `latch-decline` goes 1 -> 0. Assisted-by: Claude --- .../src/jitcode_dispatch/bridge_subwalk.rs | 2 + .../src/jitcode_dispatch/inline_call.rs | 2 + .../src/jitcode_dispatch/mod.rs | 13 ++ .../src/jitcode_dispatch/tests.rs | 118 ++++++++++++++++ .../src/jitcode_dispatch/vstack_mirror.rs | 132 +++++++++++++++--- 5 files changed, 251 insertions(+), 16 deletions(-) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs index ac5fd0909b2..d4e8b5b9387 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/bridge_subwalk.rs @@ -404,6 +404,7 @@ pub fn dispatch_via_miframe( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -1248,6 +1249,7 @@ pub(crate) fn drive_bridge_frame_subwalk( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, trace_ctx: ctx, diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs index b7d6d34f846..689d6104074 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs @@ -4161,6 +4161,7 @@ pub(crate) fn try_walker_inline_resolved_user_call( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, trace_ctx: ctx.trace_ctx, @@ -6401,6 +6402,7 @@ pub(crate) fn run_sub_jitcode_walk( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs index b6c6b6495c8..d3689098381 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs @@ -1656,6 +1656,19 @@ pub struct WalkContext<'frame, 'static_a: 'frame, Sym: WalkSym> { /// the virtualizable shadow instead of replaying stack effects. Cleared once /// the walk advances past this ceiling (py-pc order is monotonic again). pub vstack_reorder_ceiling: u32, + /// The py_pc the walk's floor lookup reports while it is inside an + /// out-of-line exception LANDING block — the unwind bookkeeping the + /// codewriter emits per catch site, after the whole body, which then jumps + /// to the handler proper. `None` when the catch target is in line with its + /// handler (nothing to skip). + /// + /// Those bytes lower no Python opcode, but they are laid out inside some + /// other opcode's floor segment, so the walk reports that opcode as a + /// boundary and the mirror would replay a stack effect from a block it + /// never ran — and, py order having gone backwards, arm the reorder region + /// over the whole handler body. Hold the handler-entry coordinate until + /// the walk leaves those bytes instead. + pub vstack_handler_landing_py: Option, /// #73: the jitcode offset of the `-live-` byte that /// precedes the CURRENT opcode's guard resume point — the `-live-` /// BEFORE (`pyjitpl.py`, normal guard resume reads at diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs index 9a00341a651..e747df78c46 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs @@ -441,6 +441,7 @@ fn read_ref_reg_concrete_returns_slot_matching_symbolic_read() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -662,6 +663,7 @@ fn getfield_vable_with_none_obj_surfaces_vable_box_not_seeded() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -718,6 +720,7 @@ fn setfield_vable_with_none_obj_surfaces_vable_box_not_seeded() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -792,6 +795,7 @@ fn array_vable_handlers_with_none_obj_surface_vable_box_not_seeded() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -1043,6 +1047,7 @@ fn drive_int_add_jump_if_ovf( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -1200,6 +1205,7 @@ fn drive_alloc_with_descr( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -1393,6 +1399,7 @@ fn run_hint_step_with_descrs( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -1901,6 +1908,7 @@ fn switch_id_hit_jumps_to_matching_target() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -1958,6 +1966,7 @@ fn switch_id_miss_falls_through() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2014,6 +2023,7 @@ fn switch_id_requires_concrete_int_value() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2079,6 +2089,7 @@ fn goto_if_not_truthy_records_guard_true_and_falls_through() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2136,6 +2147,7 @@ fn goto_if_not_falsy_records_guard_false_and_jumps() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2192,6 +2204,7 @@ fn goto_if_not_requires_concrete_int_value() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2457,6 +2470,7 @@ fn inline_call_recursion_writes_subreturn_into_caller_dst_register() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2625,6 +2639,7 @@ fn inline_call_r_i_writes_int_subreturn_into_caller_int_bank() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2741,6 +2756,7 @@ fn inline_call_ir_r_populates_callee_int_and_ref_banks() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2852,6 +2868,7 @@ fn inline_call_irf_r_populates_all_three_kind_banks() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -2952,6 +2969,7 @@ fn inline_call_ir_int_arity_overflow_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3048,6 +3066,7 @@ fn inline_call_recursion_propagates_subraise_from_callee() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3126,6 +3145,7 @@ fn inline_call_with_unresolvable_descr_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3183,6 +3203,7 @@ fn inline_call_with_missing_sub_jitcode_lookup_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3236,6 +3257,7 @@ fn step_through_live_opcode_advances_by_offset_size() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3298,6 +3320,7 @@ fn step_through_ref_return_records_finish_with_descr_and_correct_arg() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3358,6 +3381,7 @@ fn ref_return_with_out_of_range_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3419,6 +3443,7 @@ fn raise_with_unwritten_register_surfaces_register_read_unbound() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3481,6 +3506,7 @@ fn step_through_int_return_records_finish_with_int_descr() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3560,6 +3586,7 @@ fn step_through_int_return_subwalk_surfaces_subreturn_some() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3627,6 +3654,7 @@ fn step_through_void_return_stashes_void_finish_payload() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3695,6 +3723,7 @@ fn step_through_void_return_subwalk_surfaces_subreturn_none() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3751,6 +3780,7 @@ fn raise_with_out_of_range_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3810,6 +3840,7 @@ fn step_through_goto_jumps_to_label_target() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3869,6 +3900,7 @@ fn step_through_goto_handles_high_byte_of_label() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -3976,6 +4008,7 @@ fn step_through_catch_exception_with_active_exception_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4030,6 +4063,7 @@ fn step_through_catch_exception_advances_past_label_operand() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4098,6 +4132,7 @@ fn step_through_raise_records_outermost_finish_and_terminates() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4180,6 +4215,7 @@ fn top_level_raise_settles_the_vable_token() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4286,6 +4322,7 @@ fn raise_r_emits_guard_class_when_concrete_exc_pinned_in_shadow() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4386,6 +4423,7 @@ fn step_through_reraise_at_top_level_records_outermost_finish() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4463,6 +4501,7 @@ fn step_through_reraise_without_last_exc_value_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4517,6 +4556,7 @@ fn raise_at_top_level_populates_last_exc_value_before_finish() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4635,6 +4675,7 @@ fn inline_call_subraise_jumps_to_caller_catch_exception_target() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4756,6 +4797,7 @@ fn inline_call_subraise_without_caller_catch_bubbles_up_in_subwalk() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4825,6 +4867,7 @@ fn step_through_int_copy_advances_past_operand_bytes() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4891,6 +4934,7 @@ fn int_copy_writes_src_value_into_dst_register() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -4948,6 +4992,7 @@ fn int_copy_with_out_of_range_dst_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5004,6 +5049,7 @@ fn int_copy_with_out_of_range_src_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5080,6 +5126,7 @@ fn step_through_ref_copy_advances_past_operand_bytes() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5144,6 +5191,7 @@ fn ref_copy_writes_src_value_into_dst_register() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5199,6 +5247,7 @@ fn ref_copy_with_out_of_range_dst_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5253,6 +5302,7 @@ fn ref_copy_with_out_of_range_src_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5318,6 +5368,7 @@ fn drive_int_binop(opname: &str, expected_opcode: majit_ir::OpCode) { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5543,6 +5594,7 @@ fn drive_int_between( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5680,6 +5732,7 @@ fn drive_float_binop(opname: &str, expected_opcode: majit_ir::OpCode) { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5770,6 +5823,7 @@ fn drive_float_unop(opname: &str, expected_opcode: majit_ir::OpCode) { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5848,6 +5902,7 @@ fn drive_int_unop(opname: &str, expected_opcode: majit_ir::OpCode) { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -5946,6 +6001,7 @@ fn drive_ptr_compare(opname: &str, expected_opcode: majit_ir::OpCode) { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6117,6 +6173,7 @@ fn run_float_step( vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6311,6 +6368,7 @@ fn float_add_with_out_of_range_src_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6366,6 +6424,7 @@ fn int_add_with_out_of_range_src_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6423,6 +6482,7 @@ fn int_add_with_out_of_range_dst_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6488,6 +6548,7 @@ fn unsupported_opname_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6544,6 +6605,7 @@ fn ptr_nonzero_records_ptrne_with_box_and_null() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6693,6 +6755,7 @@ fn abort_result_r_is_pure_pc_advance() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6759,6 +6822,7 @@ fn ref_guard_value_records_guardvalue_with_concrete_constant() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6843,6 +6907,7 @@ fn int_guard_value_records_guardvalue_with_concrete_constant() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -6928,6 +6993,7 @@ fn ref_guard_value_on_const_records_nothing() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7023,6 +7089,7 @@ fn step_through_residual_call_r_r_records_callr_with_descr_and_args() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7192,6 +7259,7 @@ fn residual_call_r_r_with_elidable_cannot_raise_records_callpurer_no_guard() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7278,6 +7346,7 @@ fn authoritative_walker_executes_may_force_call_and_stamps_result() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7336,6 +7405,7 @@ fn non_authoritative_walker_does_not_execute_may_force_call() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7409,6 +7479,7 @@ fn authoritative_walker_transcribes_may_force_raise_to_last_exc() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7506,6 +7577,7 @@ fn may_force_with_active_vable_executes_and_clears_token() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7607,6 +7679,7 @@ fn may_force_vable_escape_surfaces_typed_abort() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7683,6 +7756,7 @@ fn residual_call_r_r_with_not_in_trace_oopspec_returns_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7746,6 +7820,7 @@ fn residual_call_r_r_with_jit_force_virtual_oopspec_returns_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7803,6 +7878,7 @@ fn residual_call_r_r_with_elidable_can_raise_records_callpurer_plus_guard() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7872,6 +7948,7 @@ fn residual_call_r_r_with_cannot_raise_records_callr_no_guard() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -7943,6 +8020,7 @@ fn residual_call_r_r_writes_recorder_result_into_dst_register() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8034,6 +8112,7 @@ fn residual_call_r_r_can_raise_writes_dst_before_guard_no_exception() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8114,6 +8193,7 @@ fn residual_call_ir_r_can_raise_writes_dst_before_guard_no_exception() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8193,6 +8273,7 @@ fn residual_call_r_r_with_out_of_range_dst_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8252,6 +8333,7 @@ fn residual_call_r_r_with_descr_index_out_of_range_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8349,6 +8431,7 @@ fn step_through_residual_call_r_i_records_calli_with_int_dst_writeback() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8445,6 +8528,7 @@ fn residual_call_r_i_with_elidable_cannot_raise_records_callpurei_no_guard() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8549,6 +8633,7 @@ fn step_through_residual_call_ir_r_records_callr_with_int_and_ref_args() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8685,6 +8770,7 @@ fn residual_call_ir_r_permutes_argboxes_per_arg_types_abi() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8757,6 +8843,7 @@ fn residual_call_descr_not_call_descr_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8815,6 +8902,7 @@ fn residual_call_r_r_with_out_of_range_arg_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -8897,6 +8985,7 @@ fn walk_return_value_helper_terminates_at_first_ref_return() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9010,6 +9099,7 @@ fn walk_pop_top_helper_terminates_with_recorded_ops() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9113,6 +9203,7 @@ fn inline_call_with_more_args_than_callee_regs_surfaces_arity_mismatch() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9215,6 +9306,7 @@ fn inline_call_r_v_accepts_void_returning_callee() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9296,6 +9388,7 @@ fn inline_call_r_v_rejects_non_void_returning_callee() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9380,6 +9473,7 @@ fn inline_call_ir_v_accepts_void_returning_callee() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9462,6 +9556,7 @@ fn inline_call_ir_v_rejects_non_void_returning_callee() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9549,6 +9644,7 @@ fn inline_call_irf_v_accepts_void_returning_callee() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9634,6 +9730,7 @@ fn inline_call_irf_v_rejects_non_void_returning_callee() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9708,6 +9805,7 @@ fn getfield_gc_i_cache_miss_records_op_and_writes_dst() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9803,6 +9901,7 @@ fn getfield_gc_i_cache_hit_returns_cached_box_without_recording() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9876,6 +9975,7 @@ fn getfield_gc_r_cache_miss_records_op_and_writes_ref_dst() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -9937,6 +10037,7 @@ fn getfield_gc_with_out_of_range_obj_register_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10010,6 +10111,7 @@ fn getfield_vable_i_routes_through_metainterp_and_writes_dst() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10103,6 +10205,7 @@ fn setfield_vable_i_routes_through_metainterp_records_setfield_gc_fallback() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10186,6 +10289,7 @@ fn setfield_gc_i_redundant_write_skips_recording() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10247,6 +10351,7 @@ fn setfield_gc_i_fresh_write_records_op_and_caches_value() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10334,6 +10439,7 @@ fn setfield_gc_r_records_setfieldgc_with_ref_valuebox() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10404,6 +10510,7 @@ fn getarrayitem_gc_r_cache_miss_records_op_and_writes_dst() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10548,6 +10655,7 @@ fn getarrayitem_gc_pure_const_operands_fold_without_recording_or_counting() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10625,6 +10733,7 @@ fn getarrayitem_gc_r_cache_hit_returns_cached_box() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -10694,6 +10803,7 @@ fn setarrayitem_gc_r_records_setarrayitemgc_with_three_args() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11012,6 +11122,7 @@ fn walk_undecodable_byte_surfaces_typed_error() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11090,6 +11201,7 @@ fn jit_merge_point_first_visit_continues_then_closes_loop() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11178,6 +11290,7 @@ fn loop_header_stamps_seen_flag() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11245,6 +11358,7 @@ fn jit_merge_point_int_form_resolves_jdindex_from_the_int_bank() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11308,6 +11422,7 @@ fn jit_merge_point_unresolved_green_key_fails_loud() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11649,6 +11764,7 @@ fn int_scratch_move_carries_the_concrete_shadow_to_the_destination() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11843,6 +11959,7 @@ fn walker_folds_a_float_result_pure_call_from_the_float_return_register() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; @@ -11942,6 +12059,7 @@ fn mayforce_null_ref_arg_exempts_the_unread_load_global_namespace() { vstack_valid: false, vstack_last_ref: OpRef::NONE, vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, live_before_jit_pc: usize::MAX, live_after_jit_pc: usize::MAX, }; diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs index ac1f9e188e3..6dbce7071e2 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs @@ -455,9 +455,11 @@ pub(crate) fn reconcile_vstack_at_boundary( crate::liveness::stack_effects(&instr, op_arg, ctx.vstack_depth); if std::env::var_os("PYRE_VSTACK_DIAG").is_some() { eprintln!( - "[vstack-reconcile] prev_pypc={prev_pypc} new_pypc={new_pypc} \ + "[vstack-reconcile] code={} sub={} prev_pypc={prev_pypc} new_pypc={new_pypc} \ new_depth={new_depth} prev_depth={} class={class:?} reorder={in_reorder_region} \ succ=(ft={},br={:?},exc={:?}) last_ref={:?} instr={instr:?}", + code.obj_name, + ctx.fbw_mode.inline_subwalk, ctx.vstack_depth, if has_fallthrough { fallthrough as isize @@ -900,8 +902,24 @@ pub(crate) fn step_vstack_mirror(ctx: &mut WalkContext<'_, '_, Sym } }; if new_pypc == ctx.vstack_cur_pypc { + // Reporting the mirror's own coordinate: whatever landing block the + // walk was crossing is behind it, so stop holding that py_pc. + ctx.vstack_handler_landing_py = None; return; } + // Still inside the landing block a catch target opened: these bytes belong + // to no Python opcode, so hold the handler-entry coordinate rather than + // treating the segment they were laid out in as a boundary. Any other + // py_pc means the walk has left them; the mirror resumes stepping from the + // handler entry, which is where the reconstruction placed it. Together + // with the clear above the hold lasts exactly as far as the landing block: + // a later boundary that happens to name the same py_pc — the opcode those + // bytes were laid out inside really is walked eventually — reconciles + // normally. + if ctx.vstack_handler_landing_py == Some(new_pypc) { + return; + } + ctx.vstack_handler_landing_py = None; // The Python-opcode boundary: sample the executed-effect odometer for any // abort leg that resumes the interpreter AT this opcode, which re-executes // it whole (`FBW_OPCODE_ENTRY_EFFECTS`). Top-level only — a sub-walk's @@ -928,6 +946,7 @@ pub(crate) fn seed_callee_vstack_mirror( ctx.vstack_depth = 0; ctx.vstack_cur_pypc = first_pypc; ctx.vstack_last_ref = OpRef::NONE; + ctx.vstack_handler_landing_py = None; ctx.vstack_valid = true; } @@ -1038,9 +1057,67 @@ pub(crate) fn seed_vstack_mirror( ctx.vstack_depth = depth; ctx.vstack_cur_pypc = first_pypc; ctx.vstack_last_ref = OpRef::NONE; + ctx.vstack_handler_landing_py = None; ctx.vstack_valid = true; } +/// Python-opcode coordinate of the handler a catch target enters. +/// +/// The obvious source — the floor segment of the catch target's JitCode offset +/// — is not reliable here. A catch target can be an out-of-line block that +/// only performs the unwind bookkeeping and then jumps to the handler proper, +/// and such a block carries no py pivot of its own, so the floor answers with +/// whatever segment it happened to be laid out inside. Observed: a catch +/// target near the end of the JitCode floored onto the `RERAISE` that ENDS the +/// handler (py 53) while the walk went on to report the handler's +/// `PUSH_EXC_INFO` entry (py 35), which armed the reorder region across the +/// whole handler body. +/// +/// `co_exceptiontable` states the same edge exactly, at the Python level the +/// mirror models: the unwind target of the entry covering the raising opcode. +/// Fall back to the floor when no entry covers it — the JitCode-level catch +/// then belongs to a construct the table does not describe. +fn handler_entry_py_pc( + code_ptr: *const pyre_interpreter::CodeObject, + raising_py_pc: u32, + floor_py: u32, +) -> u32 { + // SAFETY: the caller resolved `code_ptr` from a live jitcode payload. + let code = unsafe { &*code_ptr }; + crate::liveness::exception_target_pc(code, raising_py_pc as usize) + .and_then(|py| u32::try_from(py).ok()) + .unwrap_or(floor_py) +} + +/// Static-liveness operand-stack depth on entry to the Python opcode at `py`. +fn py_stack_depth(code_ptr: *const pyre_interpreter::CodeObject, py: u32) -> usize { + crate::liveness::liveness_for(code_ptr) + .depth_at_py_pc() + .get(py as usize) + .copied() + .unwrap_or(0) as usize +} + +/// The handler-entry coordinate the mirror adopted, under `PYRE_VSTACK_DIAG`. +/// `floor_py` is reported next to it: the two disagreeing is what an +/// out-of-line catch target looks like from the walk's side, and without both +/// numbers a spurious reorder region downstream cannot be attributed. +fn vstack_handler_diag( + arm: &str, + handler_jit_pc: usize, + from_pypc: u32, + handler_py: u32, + floor_py: u32, + handler_depth: usize, +) { + if std::env::var_os("PYRE_VSTACK_DIAG").is_some() { + eprintln!( + "[vstack-handler] {arm} handler_jit_pc={handler_jit_pc} from_pypc={from_pypc} \ + handler_py={handler_py} floor_py={floor_py} handler_depth={handler_depth}" + ); + } +} + /// #370: model the exception-unwind boundary on the operand-stack mirror. /// When a raised exception is caught by THIS frame's handler (the SubRaise /// catch in the dispatch loop), the unwinder truncates the operand stack to @@ -1087,7 +1164,7 @@ pub(crate) fn vstack_enter_exception_handler( ctx.vstack_valid = false; return; } - let (handler_py, code_ptr, twin_depth, twin_populated) = unsafe { + let (floor_py, code_ptr, twin_depth, twin_populated) = unsafe { let jc = &*sym.jitcode(); if jc.payload.code_ptr.is_null() { ctx.vstack_valid = false; @@ -1100,27 +1177,24 @@ pub(crate) fn vstack_enter_exception_handler( jc.payload.depth_containing_populated(), ) }; - // Raw py_pc-keyed static-liveness read: the unpopulated-twin fallback - // (skeleton / fixture) and the audit oracle. - let raw_depth = || { - crate::liveness::liveness_for(code_ptr) - .depth_at_py_pc() - .get(handler_py as usize) - .copied() - .unwrap_or(0) as usize - }; - let handler_depth = if twin_populated { + let handler_py = handler_entry_py_pc(code_ptr, ctx.vstack_cur_pypc, floor_py); + // The twin is a py_pc-keyed static-liveness read behind a JitCode offset, + // so it can only answer for the floor segment; on the exception-table + // coordinate it would report the landing block's segment depth. Read the + // handler's own py depth there, and on the unpopulated-twin fallback + // (skeleton / fixture). + let handler_depth = if twin_populated && handler_py == floor_py { let depth = twin_depth.unwrap_or(0) as usize; if pcmap_containing_audit_enabled() { assert_eq!( depth, - raw_depth(), - "PYRE_PCMAP_CONTAINING_AUDIT: enter-handler containing-depth twin diverged at jit_pc {handler_jit_pc} (py {handler_py})" + py_stack_depth(code_ptr, floor_py), + "PYRE_PCMAP_CONTAINING_AUDIT: enter-handler containing-depth twin diverged at jit_pc {handler_jit_pc} (py {floor_py})" ); } depth } else { - raw_depth() + py_stack_depth(code_ptr, handler_py) }; ctx.vstack_boxes.clear(); ctx.vstack_boxes.resize(handler_depth, OpRef::NONE); @@ -1128,9 +1202,18 @@ pub(crate) fn vstack_enter_exception_handler( if handler_depth >= 1 && exc != OpRef::NONE { ctx.vstack_boxes[handler_depth - 1] = exc; } + vstack_handler_diag( + "outer", + handler_jit_pc, + ctx.vstack_cur_pypc, + handler_py, + floor_py, + handler_depth, + ); ctx.vstack_cur_pypc = handler_py; ctx.vstack_depth = handler_depth; ctx.vstack_last_ref = OpRef::NONE; + ctx.vstack_handler_landing_py = (floor_py != handler_py).then_some(floor_py); // Revive: the handler-entry state is shadow-sourced, independent of the // pre-raise mirror. ctx.vstack_valid = true; @@ -1170,12 +1253,20 @@ fn vstack_enter_exception_handler_callee( ctx.vstack_valid = false; return; }; - let Some((handler_py, _code_ptr, handler_depth)) = + let Some((floor_py, code_ptr, floor_depth)) = frame.vstack_coordinate_for_jitcode_pc(handler_jit_pc) else { ctx.vstack_valid = false; return; }; + // Same out-of-line catch target as on the full-body arm, resolved against + // the callee's own code object. + let handler_py = handler_entry_py_pc(code_ptr, ctx.vstack_cur_pypc, floor_py); + let handler_depth = if handler_py == floor_py { + floor_depth + } else { + py_stack_depth(code_ptr, handler_py) + }; // Truncate to the handler's setup depth, keeping the tracked survivors; a // mirror shallower than the handler depth pads with NONE holes, which // `mirror_covers_kept` declines per slot rather than latching invalid. @@ -1184,7 +1275,16 @@ fn vstack_enter_exception_handler_callee( if handler_depth >= 1 && exc != OpRef::NONE { ctx.vstack_boxes[handler_depth - 1] = exc; } + vstack_handler_diag( + "callee", + handler_jit_pc, + ctx.vstack_cur_pypc, + handler_py, + floor_py, + handler_depth, + ); ctx.vstack_cur_pypc = handler_py; ctx.vstack_depth = handler_depth; ctx.vstack_last_ref = OpRef::NONE; + ctx.vstack_handler_landing_py = (floor_py != handler_py).then_some(floor_py); } From 4297c9644fb7be325c08b9c29cf0b2277c1601e0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 18:48:08 +0900 Subject: [PATCH 11/12] jit: run prepare_trace_segmenting's third call on the walker path `note_root_trace_too_long` carries the warm-state half of `prepare_trace_segmenting` (pyjitpl.py:2833) for the walker, which holds `&mut TraceCtx` and so cannot take `MetaInterp` to run the method itself. Its merge-point arm ran two of the three calls upstream makes there (pyjitpl.py:2843-2846): `trace_next_iteration` and `mark_force_finish_tracing`, but not `dont_trace_here`. Those two ask for the loop to be re-traced and force-finished while its callers may still inline it, so the next attempt can rebuild the trace that overflowed. `MetaInterp::prepare_trace_segmenting`, the per-opcode path's copy of the same arm, already makes all three. Assisted-by: Claude --- pyre/pyre-jit-trace/src/state.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyre/pyre-jit-trace/src/state.rs b/pyre/pyre-jit-trace/src/state.rs index 2bad450819d..a22166d0262 100644 --- a/pyre/pyre-jit-trace/src/state.rs +++ b/pyre/pyre-jit-trace/src/state.rs @@ -3182,6 +3182,11 @@ pub(crate) fn note_root_trace_too_long( // pyjitpl.py:2843-2844. warm_state.trace_next_iteration(merge_key); warm_state.mark_force_finish_tracing(merge_key); + // pyjitpl.py:2846 `warmstate.dont_trace_here(greenkey)`, the third call + // of the same arm. Without it the two above ask for the loop to be + // re-traced and force-finished while its callers may still inline it, + // so the next attempt can rebuild the very trace that overflowed. + warm_state.disable_noninlinable_function(merge_key); } if let Some(source_jct) = source_token.as_ref() { // pyjitpl.py:2857 `loop_token.retraced_count |= FORCE_BRIDGE_SEGMENTING`. From 4d07c854c13fe2d50790bfba0ec2c7e295b1fe6d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 6 Aug 2026 18:48:17 +0900 Subject: [PATCH 12/12] jit: state and pin what bounds a transparent helper descent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post-step trace-limit check (pyjitpl.py:2865 `_interpret`) is skipped inside a canonical helper descent, which has no blackhole entry point to abort at. Nothing said what still bounds the trace, and nothing tested it. The exemption defers the abort rather than dropping it, bounded by one descent. `run_sub_jitcode_walk` is the only site that sets the flag and the Python-callee sub-walk only inherits it, while every walk root is built with it clear, so an exempt descent always has a non-exempt Python frame above it; and `is_too_long` is a `num_ops > trace_limit` comparison over the shared `TraceCtx`, not an edge, so it still holds when that frame regains the check. Measured over the 373 synth fixtures: two reach the exemption at all, `trace_too_long_inline_multiframe` ending at 100 ops against a limit of 70 and `trace_too_long_effect_replay` at 115 against 100. The test walks one `inline_call_r_v/dR` past the limit under both settings of the flag — the enclosing frame aborts at its own pc, the descent finishes its body. Dropping the flag from the gate fails it. Assisted-by: Claude --- .../src/jitcode_dispatch/mod.rs | 22 ++++ .../src/jitcode_dispatch/tests.rs | 115 ++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs index d3689098381..97fab478dd3 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/mod.rs @@ -2887,6 +2887,28 @@ pub fn walk( // enclosing Python `walk()` perform this same post-step limit check at // its real per-frame coordinate, matching RPython's one-red-frame // ownership. + // + // The exemption defers the abort, it does not drop it, and the deferral + // is bounded by one descent: + // + // * `run_sub_jitcode_walk` is the only site that sets the flag, and the + // Python-callee sub-walk merely inherits it, which extends the exempt + // region downward. Every walk ROOT is built with it clear + // (`bridge_subwalk.rs`), so an exempt descent always has a non-exempt + // Python frame above it. + // * `is_too_long` is a `num_ops > trace_limit` comparison over the + // shared `TraceCtx`, not an edge, so it still holds when the helper + // returns and the enclosing frame runs this check. + // + // The exposure is therefore the ops one helper body records after + // crossing the limit. Over the 373 synth fixtures two reach it at all: + // `trace_too_long_inline_multiframe` ends at 100 ops against a limit of + // 70, and `trace_too_long_effect_replay` at 115 against 100. The size + // of that overshoot is a property of the base, not of this exemption — + // it moves whenever the walk records a different number of ops per + // helper — so re-measure it rather than inheriting the numbers. + // `helper_descent_defers_the_limit_check_to_the_enclosing_frame` pins + // both halves. if !ctx.fbw_mode.transparent_helper_subwalk && ctx.trace_ctx.is_too_long() { // `step` has advanced the register banks for `Continue`. The // other outcomes still need the match below to perform their diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs index e747df78c46..4f58640a15b 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs @@ -9137,6 +9137,121 @@ fn walk_pop_top_helper_terminates_with_recorded_ops() { // this fixture. } +/// The post-step trace-limit check (`pyjitpl.py:2865 _interpret`) is skipped +/// inside a canonical helper descent, which has no blackhole entry point to +/// abort at, and runs on the enclosing Python frame instead. +/// +/// Both halves are load-bearing and only their composition bounds the trace: +/// exempting the descent without the enclosing frame still checking would let a +/// helper record past `trace_limit` with nothing to stop it. The caller here is +/// one `inline_call_r_v/dR` — the descent — followed by its terminator, walked +/// with the limit already crossed, so the two settings of the flag differ in +/// exactly the frame that owns the check. +#[test] +fn helper_descent_defers_the_limit_check_to_the_enclosing_frame() { + fn walk_past_the_limit( + transparent_helper_subwalk: bool, + ) -> Result<(DispatchOutcome, usize), DispatchError> { + let void_ret = *insns_opname_to_byte() + .get("void_return/") + .expect("`void_return/` must be in insns table"); + let inline_byte = *insns_opname_to_byte() + .get("inline_call_r_v/dR") + .expect("`inline_call_r_v/dR` must be in insns table"); + let callee_code: &'static [u8] = Box::leak(Box::new([void_ret])); + let sub_body = SubJitCodeBody { + code: callee_code, + num_regs_r: 1, + num_regs_i: 0, + num_regs_f: 0, + constants_i: &[], + constants_r: &[], + constants_f: &[], + }; + let lookup = { + let sub_body = sub_body.clone(); + move |idx: usize| (idx == 7).then(|| sub_body.clone()) + }; + let caller_code = [ + inline_byte, + 0x07, + 0x00, // descr index 7 + 0x01, + 0x00, // R: len=1, arg=r0 + void_ret, // caller terminates + ]; + let mut tc = fresh_trace_ctx(); + let mut regs_r = distinct_const_refs(&mut tc, 4); + // One recorded op against a zero limit: the walk is over budget before + // its first step, so whichever frame owns the check aborts immediately. + tc.record_op(majit_ir::OpCode::PtrEq, &[]); + tc.set_trace_limit(0); + assert!(tc.is_too_long(), "the walk must start over budget"); + // Bridge-shaped (pyjitpl.py:2908), so the abort's warm-state half — + // `note_root_trace_too_long`, which reaches the driver through the + // merge point — is a no-op and the frame that owns the check is the + // only thing this fixture measures. + tc.clear_merge_points(); + let mut descr_pool: Vec = (0..16).map(|i| make_fail_descr(1 + i)).collect(); + descr_pool[7] = make_jitcode_descr(7); + let session = std::cell::RefCell::new(WalkSession::default()); + let mut wc = WalkContext { + callee_shadow: None, + inline_callee_consts: None, + fbw_mode: FbwWalkMode { + transparent_helper_subwalk, + ..test_fbw_mode() + }, + session: &session, + registers_r: &mut regs_r, + registers_i: &mut [], + registers_f: &mut [], + concrete_registers_r: &mut [], + concrete_registers_i: &mut [], + descr_refs: &descr_pool, + raw_descrs: RawDescrPool::Global, + is_authoritative_executor: false, + trace_ctx: &mut tc, + is_top_level: true, + sub_jitcode_lookup: &lookup, + last_exc_value: None, + last_exc_value_concrete: ConcreteValue::Null, + entry_py_pc: EntryPyPc::Py(0), + outer_resume_marker_jit_pc: None, + outer_jitcode_index: 0, + outer_active_boxes: Vec::new(), + store_subscr_fn_addr: None, + pending_guard_snapshot_error: None, + vstack_boxes: Vec::new(), + vstack_depth: 0, + vstack_cur_pypc: 0, + vstack_valid: false, + vstack_last_ref: OpRef::NONE, + vstack_reorder_ceiling: u32::MAX, + vstack_handler_landing_py: None, + live_before_jit_pc: usize::MAX, + live_after_jit_pc: usize::MAX, + }; + walk(&caller_code, 0, &mut wc) + } + + // The descent runs inside the caller's first step, so the abort coordinate + // states which frame took it: pc 0 is the `inline_call_r_v/dR` itself, and + // the callee body — whose own offsets index a different JitCode — is never + // a legal abort pc for the enclosing walk. + assert_eq!( + walk_past_the_limit(false), + Err(DispatchError::TraceTooLong { pc: 0, ops: 1 }), + "an enclosing Python frame must abort at its own step", + ); + + assert_eq!( + walk_past_the_limit(true).map(|(outcome, _)| outcome), + Ok(DispatchOutcome::Terminate), + "a helper descent must finish its body and leave the check to its caller", + ); +} + #[test] fn inline_call_with_more_args_than_callee_regs_surfaces_arity_mismatch() { // codewriter shape contract says `R-list.len() <=