From 07c511ed562efc525329794339a7fee7c357e695 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 5 Aug 2026 18:19:05 +0900 Subject: [PATCH 1/2] jit: model LOAD_BUILD_CLASS and the MATCH_* opcodes in the walk operand-stack mirror `classify_vstack_opcode` fell through to `_ => Unmodeled` for LOAD_BUILD_CLASS, MATCH_SEQUENCE, MATCH_MAPPING, MATCH_KEYS and MATCH_CLASS, which latches `vstack_valid = false` for the rest of the walk. All five push exactly one value through `emit_pushvalue_ref!` -> `setarrayitem_vable_r`, so they take `ResultToTos`. LOAD_BUILD_CLASS shares one codewriter arm with LOAD_LOCALS, whose `abort_permanent` arm is gated on `is_locals && !is_true_portal`, so it always lowers to the residual plus the push. MATCH_CLASS is net -2 and cannot use `MultiResultFromShadow`: that class clears `vstack_depth - 1 .. new_depth`, an empty range for a net-negative opcode, and both hole-fill helpers skip non-NONE slots, so the popped subject would survive in the result slot. A `PYRE_VSTACK_DIAG=1` sweep of the 361 synth fixtures records 34462 reconcile events, of which the Unmodeled ones are 5 MatchSequence in match_sequence_of_class_patterns and 2 LoadBuildClass in slots_class_var_conflict; both go to 0. The remaining opcodes that reach the fallthrough all lower to `emit_abort_permanent!`, so the walk ends inside their own jitcode region and never applies the class. match_sequence_of_class_patterns moves loops_aborted 5 -> 0, loops_compiled 1 -> 2, bridges_compiled 0 -> 2 and guard_failures 1 -> 401 (402 on wasm), identically on all three backends. Assisted-by: Claude --- ...uence_of_class_patterns.cranelift.jitstats | 9 ++++---- ...sequence_of_class_patterns.dynasm.jitstats | 9 ++++---- ...h_sequence_of_class_patterns.wasm.jitstats | 9 ++++---- .../src/jitcode_dispatch/vstack_mirror.rs | 23 +++++++++++++++++++ 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/pyre/bench/synth/match_sequence_of_class_patterns.cranelift.jitstats b/pyre/bench/synth/match_sequence_of_class_patterns.cranelift.jitstats index 1474bce14a7..f4509ffc3bb 100644 --- a/pyre/bench/synth/match_sequence_of_class_patterns.cranelift.jitstats +++ b/pyre/bench/synth/match_sequence_of_class_patterns.cranelift.jitstats @@ -1,8 +1,9 @@ -bridges_compiled=0 +bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 -guard_failures=1 +fbw_rolled_back_with_effects=0 +guard_failures=401 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 diff --git a/pyre/bench/synth/match_sequence_of_class_patterns.dynasm.jitstats b/pyre/bench/synth/match_sequence_of_class_patterns.dynasm.jitstats index 1474bce14a7..f4509ffc3bb 100644 --- a/pyre/bench/synth/match_sequence_of_class_patterns.dynasm.jitstats +++ b/pyre/bench/synth/match_sequence_of_class_patterns.dynasm.jitstats @@ -1,8 +1,9 @@ -bridges_compiled=0 +bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 -guard_failures=1 +fbw_rolled_back_with_effects=0 +guard_failures=401 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 diff --git a/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats b/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats index 1474bce14a7..454ae31ec45 100644 --- a/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats +++ b/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats @@ -1,8 +1,9 @@ -bridges_compiled=0 +bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 -guard_failures=1 +fbw_rolled_back_with_effects=0 +guard_failures=402 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 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 d8f644537c1..686b366cead 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/vstack_mirror.rs @@ -77,6 +77,13 @@ pub(crate) fn classify_vstack_opcode( | Instruction::LoadName { .. } | Instruction::LoadDeref { .. } | Instruction::LoadLocals + // LOAD_BUILD_CLASS shares one codewriter arm and one liveness arm with + // LOAD_LOCALS (`(d + 1, d + 1)`), and the `abort_permanent` arm there is + // gated on `is_locals && !is_true_portal`, so it always lowers to the + // `load_build_class` residual plus `emit_pushvalue_ref!`. Left + // unmodeled it killed the mirror at the first `class` statement in a + // loop-owning frame. + | Instruction::LoadBuildClass | Instruction::UnaryNegative | Instruction::UnaryNot | Instruction::UnaryInvert @@ -95,6 +102,14 @@ pub(crate) fn classify_vstack_opcode( | Instruction::ToBool | Instruction::GetIter | Instruction::GetLen + // MATCH_SEQUENCE / MATCH_MAPPING / MATCH_KEYS peek their operands and + // push one result (liveness `(d + 1, d + 1)`), the same shape as GET_LEN + // and IMPORT_FROM in this group. `match_keys` is bound MayForce — it + // runs the subject's `get` — so a replay of a region containing one can + // re-enter a Python frame. + | Instruction::MatchSequence + | Instruction::MatchMapping + | Instruction::MatchKeys | Instruction::LoadAttr { .. } | Instruction::ImportFrom { .. } | Instruction::BinaryOp { .. } @@ -119,6 +134,14 @@ pub(crate) fn classify_vstack_opcode( | Instruction::ConvertValue { .. } | Instruction::BinarySlice | Instruction::ImportName { .. } + // MATCH_CLASS pops the keyword-names tuple, the class and the subject + // and pushes one result (liveness `(d - 2, d - 2)`), so the new TOS sits + // where the popped `subject` box was and `ResultToTos` overwrites it. + // `MultiResultFromShadow` would be wrong here: its pop point is + // `vstack_depth - 1`, which for a net-negative opcode is an EMPTY clear + // range, and both hole-fill helpers skip non-NONE slots — the stale + // `subject` box would survive as the result. + | Instruction::MatchClass { .. } // MAKE_FUNCTION pops the code object and pushes the built function // (net 0, `stack_effects` `(d, d)`). The `make_function_value` // residual's Ref result reaches the new TOS through the operand-stack From 68cbada6cb9cf70eb4408a82908351c0e7f00cc7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 5 Aug 2026 18:19:16 +0900 Subject: [PATCH 2/2] jit, check.py: gate the walks that roll back after an irreversible residual MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `fbw_diag` slot 1 (ROLLED_BACK_WITH_EFFECTS, trace.rs) counts walks that ended uncommitted with `effects > 0` — a residual had already written live heap or entered a Python frame, neither of which the store journal undoes, so the legacy replay the caller falls back to applies those effects twice. The counter was computed on every walk and read by no gate. Print it as `fbw_rolled_back_with_effects` from pyrex and from the wasm runner's MAJIT_STATS block, and add it to JITSTATS_BADNESS_FIELDS. The wasm runner already read the same slot, but printed it under a different key and inside the PYRE_WASM_JIT_STATS block, which check.py does not set; `_jit_stats_change` reads a field absent from a run as 0, so that key gated nothing. A baseline field that no run emits also reads as 0, so only the fixtures reporting a nonzero count needed recording: raise_reg_unbound_jitstress (1, end=ResidualCallArgUnbound), recursive_forced_frame_kept_stack (1, end=CompileTracePending) and set_hash_protocol (1, end=CompileTracePending, bridge) on all three backends, plus ca_bridge_multiframe_resume_double_call (1), global_store_plain_dict_globals (1) and pickle_terminal_raise_resume (5) on wasm only. Each count repeats identically across five runs. 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 + ...ultiframe_resume_double_call.wasm.jitstats | 1 + ...bal_store_plain_dict_globals.wasm.jitstats | 1 + ...pickle_terminal_raise_resume.wasm.jitstats | 1 + ...e_reg_unbound_jitstress.cranelift.jitstats | 1 + ...aise_reg_unbound_jitstress.dynasm.jitstats | 1 + .../raise_reg_unbound_jitstress.wasm.jitstats | 1 + ...forced_frame_kept_stack.cranelift.jitstats | 1 + ...ve_forced_frame_kept_stack.dynasm.jitstats | 1 + ...sive_forced_frame_kept_stack.wasm.jitstats | 1 + .../set_hash_protocol.cranelift.jitstats | 1 + .../synth/set_hash_protocol.dynasm.jitstats | 1 + .../synth/set_hash_protocol.wasm.jitstats | 1 + pyre/check.py | 16 +++++++++++++++ pyre/pyre-wasm-runner/src/main.rs | 20 ++++++++++++++++++- pyre/pyrex/src/lib.rs | 10 ++++++++++ 45 files changed, 87 insertions(+), 1 deletion(-) diff --git a/pyre/bench/fannkuch.cranelift.jitstats b/pyre/bench/fannkuch.cranelift.jitstats index c82e2a98f63..2cceb0b9d90 100644 --- a/pyre/bench/fannkuch.cranelift.jitstats +++ b/pyre/bench/fannkuch.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=22 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 c82e2a98f63..2cceb0b9d90 100644 --- a/pyre/bench/fannkuch.dynasm.jitstats +++ b/pyre/bench/fannkuch.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=22 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 c82e2a98f63..2cceb0b9d90 100644 --- a/pyre/bench/fannkuch.wasm.jitstats +++ b/pyre/bench/fannkuch.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=22 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=5049 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_loop.cranelift.jitstats b/pyre/bench/fib_loop.cranelift.jitstats index d8ee942f35b..7be38348ba8 100644 --- a/pyre/bench/fib_loop.cranelift.jitstats +++ b/pyre/bench/fib_loop.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=191 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_loop.dynasm.jitstats b/pyre/bench/fib_loop.dynasm.jitstats index d8ee942f35b..7be38348ba8 100644 --- a/pyre/bench/fib_loop.dynasm.jitstats +++ b/pyre/bench/fib_loop.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=191 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_loop.wasm.jitstats b/pyre/bench/fib_loop.wasm.jitstats index 06b198e7262..c2c1f34ede9 100644 --- a/pyre/bench/fib_loop.wasm.jitstats +++ b/pyre/bench/fib_loop.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=189 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/fib_recursive.cranelift.jitstats b/pyre/bench/fib_recursive.cranelift.jitstats index 4cc5ba9fb05..8b8d8bd32bc 100644 --- a/pyre/bench/fib_recursive.cranelift.jitstats +++ b/pyre/bench/fib_recursive.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 4cc5ba9fb05..8b8d8bd32bc 100644 --- a/pyre/bench/fib_recursive.dynasm.jitstats +++ b/pyre/bench/fib_recursive.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 4cc5ba9fb05..8b8d8bd32bc 100644 --- a/pyre/bench/fib_recursive.wasm.jitstats +++ b/pyre/bench/fib_recursive.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=406 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/float_loop.cranelift.jitstats b/pyre/bench/float_loop.cranelift.jitstats index 4665b398e2f..8beed56f050 100644 --- a/pyre/bench/float_loop.cranelift.jitstats +++ b/pyre/bench/float_loop.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 4665b398e2f..8beed56f050 100644 --- a/pyre/bench/float_loop.dynasm.jitstats +++ b/pyre/bench/float_loop.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 4665b398e2f..8beed56f050 100644 --- a/pyre/bench/float_loop.wasm.jitstats +++ b/pyre/bench/float_loop.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/inline_helper.cranelift.jitstats b/pyre/bench/inline_helper.cranelift.jitstats index 97609658080..1cc731febcf 100644 --- a/pyre/bench/inline_helper.cranelift.jitstats +++ b/pyre/bench/inline_helper.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 97609658080..1cc731febcf 100644 --- a/pyre/bench/inline_helper.dynasm.jitstats +++ b/pyre/bench/inline_helper.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 97609658080..1cc731febcf 100644 --- a/pyre/bench/inline_helper.wasm.jitstats +++ b/pyre/bench/inline_helper.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/int_loop.cranelift.jitstats b/pyre/bench/int_loop.cranelift.jitstats index 4665b398e2f..8beed56f050 100644 --- a/pyre/bench/int_loop.cranelift.jitstats +++ b/pyre/bench/int_loop.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 4665b398e2f..8beed56f050 100644 --- a/pyre/bench/int_loop.dynasm.jitstats +++ b/pyre/bench/int_loop.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 4665b398e2f..8beed56f050 100644 --- a/pyre/bench/int_loop.wasm.jitstats +++ b/pyre/bench/int_loop.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=1 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/nbody.cranelift.jitstats b/pyre/bench/nbody.cranelift.jitstats index e3198d8112a..434fcead556 100644 --- a/pyre/bench/nbody.cranelift.jitstats +++ b/pyre/bench/nbody.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 e3198d8112a..434fcead556 100644 --- a/pyre/bench/nbody.dynasm.jitstats +++ b/pyre/bench/nbody.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 e3198d8112a..434fcead556 100644 --- a/pyre/bench/nbody.wasm.jitstats +++ b/pyre/bench/nbody.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=1547 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/nested_loop.cranelift.jitstats b/pyre/bench/nested_loop.cranelift.jitstats index 7f27db25dfa..114b48b9fd0 100644 --- a/pyre/bench/nested_loop.cranelift.jitstats +++ b/pyre/bench/nested_loop.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 7f27db25dfa..114b48b9fd0 100644 --- a/pyre/bench/nested_loop.dynasm.jitstats +++ b/pyre/bench/nested_loop.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 7f27db25dfa..114b48b9fd0 100644 --- a/pyre/bench/nested_loop.wasm.jitstats +++ b/pyre/bench/nested_loop.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/raise_catch_loop.cranelift.jitstats b/pyre/bench/raise_catch_loop.cranelift.jitstats index 7f27db25dfa..114b48b9fd0 100644 --- a/pyre/bench/raise_catch_loop.cranelift.jitstats +++ b/pyre/bench/raise_catch_loop.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 7f27db25dfa..114b48b9fd0 100644 --- a/pyre/bench/raise_catch_loop.dynasm.jitstats +++ b/pyre/bench/raise_catch_loop.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 7f27db25dfa..114b48b9fd0 100644 --- a/pyre/bench/raise_catch_loop.wasm.jitstats +++ b/pyre/bench/raise_catch_loop.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/bench/spectral_norm.cranelift.jitstats b/pyre/bench/spectral_norm.cranelift.jitstats index a29f7468b38..fc43af8db38 100644 --- a/pyre/bench/spectral_norm.cranelift.jitstats +++ b/pyre/bench/spectral_norm.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 a29f7468b38..fc43af8db38 100644 --- a/pyre/bench/spectral_norm.dynasm.jitstats +++ b/pyre/bench/spectral_norm.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=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 a29f7468b38..fc43af8db38 100644 --- a/pyre/bench/spectral_norm.wasm.jitstats +++ b/pyre/bench/spectral_norm.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=0 guard_failures=520 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 38aab605cb6..eebb788ea08 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,6 +2,7 @@ bridges_compiled=14 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 guard_failures=3062 internal_compile_panics=0 loops_aborted=1 diff --git a/pyre/bench/synth/global_store_plain_dict_globals.wasm.jitstats b/pyre/bench/synth/global_store_plain_dict_globals.wasm.jitstats index 476d4381d82..71c8716bccd 100644 --- a/pyre/bench/synth/global_store_plain_dict_globals.wasm.jitstats +++ b/pyre/bench/synth/global_store_plain_dict_globals.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 guard_failures=1 internal_compile_panics=0 loops_aborted=1 diff --git a/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats b/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats index 5459f7d4670..03121a3ba93 100644 --- a/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats +++ b/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=5 guard_failures=451 internal_compile_panics=0 loops_aborted=13 diff --git a/pyre/bench/synth/raise_reg_unbound_jitstress.cranelift.jitstats b/pyre/bench/synth/raise_reg_unbound_jitstress.cranelift.jitstats index 5c80feb10ed..3f10f468e5e 100644 --- a/pyre/bench/synth/raise_reg_unbound_jitstress.cranelift.jitstats +++ b/pyre/bench/synth/raise_reg_unbound_jitstress.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 guard_failures=5 internal_compile_panics=0 loops_aborted=2 diff --git a/pyre/bench/synth/raise_reg_unbound_jitstress.dynasm.jitstats b/pyre/bench/synth/raise_reg_unbound_jitstress.dynasm.jitstats index 5c80feb10ed..3f10f468e5e 100644 --- a/pyre/bench/synth/raise_reg_unbound_jitstress.dynasm.jitstats +++ b/pyre/bench/synth/raise_reg_unbound_jitstress.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 guard_failures=5 internal_compile_panics=0 loops_aborted=2 diff --git a/pyre/bench/synth/raise_reg_unbound_jitstress.wasm.jitstats b/pyre/bench/synth/raise_reg_unbound_jitstress.wasm.jitstats index 5b8f8644997..4d8d6ac7bfc 100644 --- a/pyre/bench/synth/raise_reg_unbound_jitstress.wasm.jitstats +++ b/pyre/bench/synth/raise_reg_unbound_jitstress.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 guard_failures=5 internal_compile_panics=0 loops_aborted=1 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 475992bdacd..18ef46ff182 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 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 475992bdacd..18ef46ff182 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 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 475992bdacd..18ef46ff182 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 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 7f27db25dfa..0a12b8bc950 100644 --- a/pyre/bench/synth/set_hash_protocol.cranelift.jitstats +++ b/pyre/bench/synth/set_hash_protocol.cranelift.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 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 7f27db25dfa..0a12b8bc950 100644 --- a/pyre/bench/synth/set_hash_protocol.dynasm.jitstats +++ b/pyre/bench/synth/set_hash_protocol.dynasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 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 7f27db25dfa..0a12b8bc950 100644 --- a/pyre/bench/synth/set_hash_protocol.wasm.jitstats +++ b/pyre/bench/synth/set_hash_protocol.wasm.jitstats @@ -2,6 +2,7 @@ bridges_compiled=1 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 +fbw_rolled_back_with_effects=1 guard_failures=201 internal_compile_panics=0 loops_aborted=0 diff --git a/pyre/check.py b/pyre/check.py index e888dbbae63..29c9f052ee1 100644 --- a/pyre/check.py +++ b/pyre/check.py @@ -634,12 +634,28 @@ def _parse_jit_stats(snapshot): # expressed as a counter that must not rise off zero. A field absent from a # baseline reads as 0, so these gate from the first run without re-recording # anything. +# +# `fbw_rolled_back_with_effects` counts walks that ended UNCOMMITTED after a +# residual had already run an irreversible effect — it wrote live heap outside +# the journals, or it entered a Python frame. The store journal cannot undo +# either, so the legacy replay the caller falls back to applies those effects a +# second time. Upstream has no counterpart state: `_copy_data_from_miframe` +# (blackhole.py) copies the register banks unconditionally and has no failing +# path, so every abort there leaves a resumable image. Pyre's mirror is partial, +# so an abort can arrive with no image to adopt, and the difference is a wrong +# answer rather than a slow one — three such bugs (a locals-typed vable overlay, +# an unpublished escape root stack, an unmodelled LOAD_SPECIAL) were each found +# by reading this counter's population by hand. Gating it is what makes the +# fourth one fail CI instead of surfacing as an intermittent asyncio failure. +# Like `loops_aborted` it is not absolute: the baseline pins the instances that +# exist today and a rise means a new one. JITSTATS_BADNESS_FIELDS = ( "loops_aborted", "internal_compile_panics", "descr_set_absent", "descr_set_ambiguous", "descr_set_stale_absent", + "fbw_rolled_back_with_effects", ) # The three count-valued counters, and what a move in either direction means: diff --git a/pyre/pyre-wasm-runner/src/main.rs b/pyre/pyre-wasm-runner/src/main.rs index 7a32b726ec7..f28afab687e 100644 --- a/pyre/pyre-wasm-runner/src/main.rs +++ b/pyre/pyre-wasm-runner/src/main.rs @@ -833,6 +833,23 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { let descr_set_absent = counter("pyre_jit_descr_set_absent", &mut missing); let descr_set_ambiguous = counter("pyre_jit_descr_set_ambiguous", &mut missing); let descr_set_stale_absent = counter("pyre_jit_descr_set_stale_absent", &mut missing); + // Walks that ended uncommitted after a residual had already run an + // irreversible effect. Reached through the slot-indexed `pyre_fbw_diag` + // export rather than a counter of its own; slot 1 is + // `pyre_jit_trace::trace::fbw_diag::ROLLED_BACK_WITH_EFFECTS`. It carries + // 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 + .get_typed_func::(&mut store, "pyre_fbw_diag") + .and_then(|f| f.call(&mut store, 1)) + { + Ok(v) => v, + Err(_) => { + missing.push("pyre_fbw_diag"); + 0 + } + }; if !missing.is_empty() { eprintln!( "pyre-wasm-runner: MAJIT_STATS is set but the module exports no {} — \ @@ -850,7 +867,8 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { descr_set_resolved={descr_set_resolved} \ descr_set_absent={descr_set_absent} \ descr_set_ambiguous={descr_set_ambiguous} \ - descr_set_stale_absent={descr_set_stale_absent}" + descr_set_stale_absent={descr_set_stale_absent} \ + fbw_rolled_back_with_effects={fbw_rolled_back_with_effects}" ); } let packed = match run_result { diff --git a/pyre/pyrex/src/lib.rs b/pyre/pyrex/src/lib.rs index 2a51eee809d..5d2f36dfd35 100644 --- a/pyre/pyrex/src/lib.rs +++ b/pyre/pyrex/src/lib.rs @@ -823,6 +823,16 @@ 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. + eprintln!( + "[jit-stats] fbw_rolled_back_with_effects={}", + pyre_jit::fbw_diag_counter(1), + ); let stats = pyre_jit::eval::driver_pair().0.get_stats(); eprintln!( "[jit-stats] loops_compiled={} bridges_compiled={} loops_aborted={} \