diff --git a/majit/majit-gc/src/collector.rs b/majit/majit-gc/src/collector.rs index a50fe14694a..906a13e8924 100644 --- a/majit/majit-gc/src/collector.rs +++ b/majit/majit-gc/src/collector.rs @@ -2426,6 +2426,12 @@ impl MiniMarkGC { } self.refresh_published_nursery_top(); + // incminimark.py:1965 `self.root_walker.finished_minor_collection()`, + // the callback framework.py:135-138 reads out of `_jit2gc`: after the + // nursery is reset and accounted for, and before the timing and the + // gc-minor hook below. + crate::invoke_after_minor_collection_hook(); + // incminimark.py:1962-1974 — report the completed minor before the // wrapper advances the incremental major state machine. let duration = start.elapsed_secs(); diff --git a/majit/majit-gc/src/lib.rs b/majit/majit-gc/src/lib.rs index 358450b444a..c7a0fedca5c 100644 --- a/majit/majit-gc/src/lib.rs +++ b/majit/majit-gc/src/lib.rs @@ -28,6 +28,20 @@ pub mod shadow_stack; pub mod trace; pub mod weakref; +static AFTER_MINOR_COLLECTION_FN: std::sync::OnceLock = std::sync::OnceLock::new(); + +/// Register the callback installed as `finished_minor_collection` by +/// framework.py:135-138. Called once when the JIT counter is initialized. +pub fn register_after_minor_collection_hook(f: fn()) { + let _ = AFTER_MINOR_COLLECTION_FN.set(f); +} + +pub(crate) fn invoke_after_minor_collection_hook() { + if let Some(f) = AFTER_MINOR_COLLECTION_FN.get() { + f(); + } +} + /// GC flags stored in object headers. /// /// From incminimark.py GCFLAG_* constants. diff --git a/majit/majit-metainterp/src/jitdriver.rs b/majit/majit-metainterp/src/jitdriver.rs index 81284560bd3..71233f5136a 100644 --- a/majit/majit-metainterp/src/jitdriver.rs +++ b/majit/majit-metainterp/src/jitdriver.rs @@ -6063,6 +6063,10 @@ impl JitDriver { self.meta.last_compiled_artifact_invalidation_flag() } + pub fn clear_last_compiled_artifact_invalidation_flag(&mut self) { + self.meta.clear_last_compiled_artifact_invalidation_flag(); + } + /// warmstate.py:437-444 starting cell's green_key (the cell on which /// TRACING must be cleared in the finally block). Returns None when /// no trace is in progress. diff --git a/majit/majit-metainterp/src/lib.rs b/majit/majit-metainterp/src/lib.rs index 41ba91c7402..36ca7dee5c1 100644 --- a/majit/majit-metainterp/src/lib.rs +++ b/majit/majit-metainterp/src/lib.rs @@ -874,7 +874,7 @@ pub fn register_stack_almost_full_hook(f: fn() -> bool) { /// Number of `MC_DIAG` slots. Declared once so the counter array and /// `MC_DIAG_LABELS` cannot drift in length — a mismatch is a compile error. -pub const MC_DIAG_SLOTS: usize = 75; +pub const MC_DIAG_SLOTS: usize = 78; /// Diagnostic-only guard-failure → bridge-trace gate tallies, read out via /// the `pyre_jit_mc_diag` guest export. Index legend: 0 = must_compile_with_values @@ -1210,6 +1210,9 @@ pub const MC_DIAG_LABELS: [&str; MC_DIAG_SLOTS] = [ "unroll_cancelled_invalid_loop", "unroll_free_retry_rescued", "unroll_free_retry_failed", + "qmut_deps_simple_loop", + "qmut_deps_entry_bridge", + "qmut_deps_blackhole_arm", ]; /// Render every [`MC_DIAG`] tally as space-separated `label=count` pairs. diff --git a/majit/majit-metainterp/src/optimizeopt/virtualstate.rs b/majit/majit-metainterp/src/optimizeopt/virtualstate.rs index c22115edfe2..0ae08d8b268 100644 --- a/majit/majit-metainterp/src/optimizeopt/virtualstate.rs +++ b/majit/majit-metainterp/src/optimizeopt/virtualstate.rs @@ -264,6 +264,10 @@ pub enum VirtualStateInfo { #[derive(Debug)] pub struct VirtualStateInfoNode { pub info: VirtualStateInfo, + /// virtualstate.py:508-518 `NotVirtualStateInfoPtr.lenbound`. + /// Only non-virtual pointer leaves populate this; all other nodes keep + /// the default `None`. + pub lenbound: Option, /// virtualstate.py:70 `AbstractVirtualStateInfo.position`. Default -1. /// Set by [`VirtualState::enum_top_level`] during construction. pub position: Cell, @@ -276,8 +280,13 @@ pub struct VirtualStateInfoNode { impl VirtualStateInfoNode { pub fn new(info: VirtualStateInfo) -> Self { + Self::new_with_lenbound(info, None) + } + + pub fn new_with_lenbound(info: VirtualStateInfo, lenbound: Option) -> Self { VirtualStateInfoNode { info, + lenbound, position: Cell::new(-1), position_in_notvirtuals: Cell::new(-1), } @@ -287,6 +296,10 @@ impl VirtualStateInfoNode { Rc::new(Self::new(info)) } + pub fn new_rc_with_lenbound(info: VirtualStateInfo, lenbound: Option) -> Rc { + Rc::new(Self::new_with_lenbound(info, lenbound)) + } + /// virtualstate.py:111-116 `AbstractVirtualStateInfo.enum`. /// ```python /// def enum(self, virtual_state): @@ -367,6 +380,7 @@ impl Clone for VirtualStateInfoNode { fn clone(&self) -> Self { VirtualStateInfoNode { info: self.info.clone(), + lenbound: self.lenbound.clone(), position: Cell::new(-1), position_in_notvirtuals: Cell::new(-1), } @@ -1496,6 +1510,34 @@ impl VirtualState { return Err(VirtualStatesCantMatch::default()); } + // virtualstate.py:529-537 NotVirtualStateInfoPtr._generate_guards: + // compare the incoming pointer length bound before dispatching on + // LEVEL_NONNULL / LEVEL_KNOWNCLASS / the base NotVirtual level. + // An incoming pointer without length information has the default + // nonnegative length range. + if let Some(expected_bound) = expected.lenbound.as_ref() { + let default_incoming_bound; + let incoming_bound = match incoming.lenbound.as_ref() { + Some(bound) => bound, + None => { + default_incoming_bound = IntBound::nonnegative(); + &default_incoming_bound + } + }; + assert!(expected_bound.are_knownbits_implied()); + if !incoming_bound.is_within_range(expected_bound.lower, expected_bound.upper) { + state.bad.insert(expected as *const _); + state.bad.insert(incoming as *const _); + if crate::log_jtet_enabled() { + eprintln!( + "[jit][jte] virtualstate length-bound mismatch arg_idx={arg_idx} \ + expected={expected_bound:?} incoming={incoming_bound:?}" + ); + } + return Err(VirtualStatesCantMatch::new("length bound does not match")); + } + } + // virtualstate.py:96-101 try/except VirtualStatesCantMatch wrapper. // If `_generate_guards` raises, RPython marks `self` and `other` // in `state.bad` so debug_print can flag the failing nodes: @@ -2352,7 +2394,7 @@ fn deep_clone_node( .collect(), }, }; - let new_rc = VirtualStateInfoNode::new_rc(cloned_info); + let new_rc = VirtualStateInfoNode::new_rc_with_lenbound(cloned_info, src.lenbound.clone()); cache.insert(key, Rc::clone(&new_rc)); new_rc } @@ -2688,7 +2730,24 @@ fn export_single_value( cache.in_progress.insert(key.clone()); let info = export_single_value_inner(box_.to_opref(), ctx, cache); - let rc = VirtualStateInfoNode::new_rc(info); + // virtualstate.py:508-518 NotVirtualStateInfoPtr.__init__: retain the + // widened ArrayPtrInfo / StrPtrInfo length bound on the per-instance + // pointer leaf. Virtual pointer infos have their own state variants and + // do not populate NotVirtualStateInfoPtr.lenbound. + let lenbound = if matches!( + &info, + VirtualStateInfo::Constant(Value::Ref(_)) + | VirtualStateInfo::KnownClass { .. } + | VirtualStateInfo::NonNull + | VirtualStateInfo::Unknown(Type::Ref) + ) { + ctx.peek_ptr_info(&box_) + .and_then(|mut ptr_info| ptr_info.getlenbound(None)) + .map(|bound| bound.widen()) + } else { + None + }; + let rc = VirtualStateInfoNode::new_rc_with_lenbound(info, lenbound); cache.in_progress.swap_remove(&key); cache.finished.insert(key, Rc::clone(&rc)); rc @@ -2943,6 +3002,12 @@ mod tests { VirtualState::new(vec![info]) } + fn vs1_with_lenbound(info: VirtualStateInfo, lenbound: Option) -> VirtualState { + VirtualState::from_shared_rcs(vec![VirtualStateInfoNode::new_rc_with_lenbound( + info, lenbound, + )]) + } + #[test] fn test_unknown_type_discrimination() { // virtualstate.py:383-410 NotVirtualStateInfoInt._generate_guards: @@ -2994,6 +3059,49 @@ mod tests { assert!(!nn.generalization_of(&vs1(VirtualStateInfo::Unknown(Type::Int)), &mut ctx)); } + #[test] + fn test_pointer_lenbound_is_checked_by_generalization_and_guard_generation() { + // virtualstate.py:529-537 NotVirtualStateInfoPtr._generate_guards: + // a narrower incoming length range is accepted, while a range that + // escapes the expected bound is rejected before LEVEL_NONNULL dispatch. + let expected = vs1_with_lenbound( + VirtualStateInfo::NonNull, + Some(IntBound::bounded(0, 10).widen()), + ); + let within = vs1_with_lenbound( + VirtualStateInfo::NonNull, + Some(IntBound::bounded(2, 8).widen()), + ); + let too_wide = vs1_with_lenbound( + VirtualStateInfo::NonNull, + Some(IntBound::bounded(0, 20).widen()), + ); + let no_bound = vs1(VirtualStateInfo::NonNull); + let nonnegative = + vs1_with_lenbound(VirtualStateInfo::NonNull, Some(IntBound::nonnegative())); + + let mut ctx = OptContext::new(128); + assert!(expected.generalization_of(&within, &mut ctx)); + assert!(!expected.generalization_of(&too_wide, &mut ctx)); + assert!(!expected.generalization_of(&no_bound, &mut ctx)); + assert!(nonnegative.generalization_of(&no_bound, &mut ctx)); + + let runtime = ctx.make_constant_ref(GcRef(0x100)); + assert!( + expected + .generate_guards(&within, &[OpRef::ref_op(10)], &[runtime], &mut ctx, false,) + .is_ok() + ); + assert!( + expected + .generate_guards(&too_wide, &[OpRef::ref_op(10)], &[runtime], &mut ctx, false,) + .is_err() + ); + + let cloned = expected.clone(); + assert_eq!(cloned.state[0].lenbound.as_ref().unwrap().upper, 10); + } + #[test] fn test_known_class_compatibility() { let mut ctx = OptContext::new(128); @@ -3251,6 +3359,28 @@ mod tests { // ── Export/Import tests ── + #[test] + fn test_export_nonvirtual_array_preserves_widened_lenbound() { + // virtualstate.py:508-518 NotVirtualStateInfoPtr.__init__: the + // non-virtual ArrayPtrInfo leaf retains getlenbound(None).widen(). + let mut ctx = OptContext::new(32); + let array_ref = OpRef::ref_op(10); + let array_box = ctx.materialize_operand_at(array_ref); + ctx.set_ptr_info( + &array_box, + PtrInfo::array(test_descr(20), IntBound::bounded(0, 10)), + ); + + let state = export_state(&[array_ref], &ctx); + assert!(matches!(state.state[0].info, VirtualStateInfo::NonNull)); + let lenbound = state.state[0] + .lenbound + .as_ref() + .expect("non-virtual array length bound"); + assert_eq!((lenbound.lower, lenbound.upper), (0, 10)); + assert!(lenbound.are_knownbits_implied()); + } + #[test] fn test_make_inputargs_skips_virtual_entries() { let descr = test_descr(7); diff --git a/majit/majit-metainterp/src/pyjitpl.rs b/majit/majit-metainterp/src/pyjitpl.rs index 21fec343166..4df9193b964 100644 --- a/majit/majit-metainterp/src/pyjitpl.rs +++ b/majit/majit-metainterp/src/pyjitpl.rs @@ -9539,6 +9539,12 @@ impl MetaInterp { let compile_time = Instant::now().saturating_duration_since(compile_start); match compile_loop_result { Ok(_) => { + // compile.py:204-207 record_loop_or_bridge registers every + // dependency against the artifact published by this compile. + self.last_compiled_artifact_invalidation_flag = Some(token.invalidation_flag()); + if !self.last_quasi_immutable_deps.is_empty() { + crate::mc_diag_bump(75); + } self.assign_guard_hashes(token.as_ref()); self.warm_state.memory_manager.keep_loop_alive(&token); // compile.py:213 record_loop_or_bridge. @@ -9748,6 +9754,12 @@ impl MetaInterp { self.last_compiled_artifact_invalidation_flag.clone() } + /// The flag names the artifact this compilation published, so a new + /// compilation attempt starts without one. + pub fn clear_last_compiled_artifact_invalidation_flag(&mut self) { + self.last_compiled_artifact_invalidation_flag = None; + } + /// Cranelift direct body-entry selector for the first compiled loop LABEL. /// /// PyPy x86 stores each TargetToken's machine-code LABEL address in @@ -12040,9 +12052,17 @@ impl MetaInterp { match compile_result { Ok(_) => { + // compile.py:204-207 record_loop_or_bridge registers every + // dependency against the artifact published by this compile. + self.last_compiled_artifact_invalidation_flag = Some(token.invalidation_flag()); self.assign_guard_hashes(token.as_ref()); self.warm_state.memory_manager.keep_loop_alive(&token); // compile.py:213 record_loop_or_bridge. + self.last_quasi_immutable_deps = + std::mem::take(&mut optimizer.quasi_immutable_deps); + if !self.last_quasi_immutable_deps.is_empty() { + crate::mc_diag_bump(76); + } self.record_loop_or_bridge(&token, &optimized_ops, trace_id); let (mut resume_data, mut exit_layouts) = compile::build_guard_metadata( &entry_inputargs, diff --git a/majit/majit-trace/src/counter.rs b/majit/majit-trace/src/counter.rs index 738f5188c2c..084eb93c845 100644 --- a/majit/majit-trace/src/counter.rs +++ b/majit/majit-trace/src/counter.rs @@ -1,4 +1,5 @@ use majit_ir::IndexMapExt; +use std::sync::atomic::{AtomicUsize, Ordering}; /// counter.py: JitCounter — float-based 5-way associative timetable. /// @@ -17,6 +18,21 @@ const ASSOCIATIVITY: usize = 5; /// counter.py:8 UINT32MAX = 2 ** 32 - 1 const UINT32MAX: u64 = 0xFFFF_FFFF; +static MINOR_COLLECTION_STEP: AtomicUsize = AtomicUsize::new(0); +static DECAY_GENERATION: AtomicUsize = AtomicUsize::new(0); + +/// counter.py:104-121 invoke_after_minor_collection +/// +/// This runs inside a minor collection, so it must remain allocation-free and +/// must not touch the counter table or acquire a lock. +fn invoke_after_minor_collection() { + let step = MINOR_COLLECTION_STEP.fetch_add(1, Ordering::Relaxed) + 1; + if step == 32 { + MINOR_COLLECTION_STEP.store(0, Ordering::Relaxed); + DECAY_GENERATION.fetch_add(1, Ordering::Relaxed); + } +} + /// One timetable entry: 5-way associative (time, subhash) pairs. /// counter.py:11-13 ENTRY struct. #[derive(Clone)] @@ -49,11 +65,15 @@ pub struct JitCounter { _nexthash: u64, /// counter.py:264 decay_by_mult — f64 (Python float). decay_by_mult: f64, + /// Last `DECAY_GENERATION` this counter applied. Each counter tracks its + /// own, so one thread's tick cannot consume another counter's decay. + last_decay_generation: usize, } impl JitCounter { /// counter.py:84-100 __init__(self, size=DEFAULT_SIZE, translator=None) pub fn new(size: usize) -> Self { + majit_gc::register_after_minor_collection_hook(invoke_after_minor_collection); let mut shift = 16u32; while (UINT32MAX >> shift) != (size as u64 - 1) { shift += 1; @@ -65,6 +85,7 @@ impl JitCounter { timetable: vec![Entry::default(); size], _nexthash: 0, decay_by_mult: 1.0, + last_decay_generation: DECAY_GENERATION.load(Ordering::Relaxed), } } @@ -147,6 +168,26 @@ impl JitCounter { /// counter.py:185-202 tick(self, hash, increment) #[inline(always)] pub fn tick(&mut self, hash: u64, increment: f64) -> bool { + // counter.py:104-121 applies the decay synchronously inside the minor + // collection. pyre defers it to the next tick because the metainterp + // owns the counter table, and reaching it from inside the collector + // would re-enter a borrow the GC does not hold. Counters are only read + // at tick time. + // + // Each JitCounter keeps its own last-seen generation because JIT_DRIVER + // in eval.rs is thread-local, giving each mutator thread its own counter. + // This still saturates: if two or more 32-collection intervals elapse + // between ticks, counter.py has applied that many decays while this + // applies one. Carrying the elapsed count would match it, but would move + // inline_chain_depth_typeflip's recorded jit-stats (bridges_compiled + // 19 -> 18, guard_failures 3820 -> 3681); left for a change that can + // re-record them on every platform. + let generation = DECAY_GENERATION.load(Ordering::Relaxed); + if generation != self.last_decay_generation { + self.last_decay_generation = generation; + self.decay_all_counters(); + } + let index = self._get_index(hash); let subhash = Self::_get_subhash(hash); let entry = &mut self.timetable[index]; diff --git a/pyre/bench/synth/class_body_exec_hot_loop.cranelift.jitstats b/pyre/bench/synth/class_body_exec_hot_loop.cranelift.jitstats index 0b6afd45307..79db0b52d9e 100644 --- a/pyre/bench/synth/class_body_exec_hot_loop.cranelift.jitstats +++ b/pyre/bench/synth/class_body_exec_hot_loop.cranelift.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=1 +bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 diff --git a/pyre/bench/synth/class_body_exec_hot_loop.dynasm.jitstats b/pyre/bench/synth/class_body_exec_hot_loop.dynasm.jitstats index 0b6afd45307..79db0b52d9e 100644 --- a/pyre/bench/synth/class_body_exec_hot_loop.dynasm.jitstats +++ b/pyre/bench/synth/class_body_exec_hot_loop.dynasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=1 +bridges_compiled=0 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 diff --git a/pyre/bench/synth/exception_traceback_loop_forms.cranelift.jitstats b/pyre/bench/synth/exception_traceback_loop_forms.cranelift.jitstats index 662db91dac1..a2969afa472 100644 --- a/pyre/bench/synth/exception_traceback_loop_forms.cranelift.jitstats +++ b/pyre/bench/synth/exception_traceback_loop_forms.cranelift.jitstats @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=810 +guard_failures=811 internal_compile_panics=0 loops_aborted=0 loops_compiled=8 +retraces_compiled=0 diff --git a/pyre/bench/synth/exception_traceback_loop_forms.dynasm.jitstats b/pyre/bench/synth/exception_traceback_loop_forms.dynasm.jitstats index 662db91dac1..a2969afa472 100644 --- a/pyre/bench/synth/exception_traceback_loop_forms.dynasm.jitstats +++ b/pyre/bench/synth/exception_traceback_loop_forms.dynasm.jitstats @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=810 +guard_failures=811 internal_compile_panics=0 loops_aborted=0 loops_compiled=8 +retraces_compiled=0 diff --git a/pyre/bench/synth/generator_tree_recursion.dynasm.jitstats b/pyre/bench/synth/generator_tree_recursion.dynasm.jitstats index 719aa2b4a73..566566e12c3 100644 --- a/pyre/bench/synth/generator_tree_recursion.dynasm.jitstats +++ b/pyre/bench/synth/generator_tree_recursion.dynasm.jitstats @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=2951 +guard_failures=2952 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/inline_chain_depth_typeflip.cranelift.jitstats b/pyre/bench/synth/inline_chain_depth_typeflip.cranelift.jitstats index 4a3f51fe394..3a3b8ddb6b6 100644 --- a/pyre/bench/synth/inline_chain_depth_typeflip.cranelift.jitstats +++ b/pyre/bench/synth/inline_chain_depth_typeflip.cranelift.jitstats @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=3818 +guard_failures=3820 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/inline_chain_depth_typeflip.dynasm.jitstats b/pyre/bench/synth/inline_chain_depth_typeflip.dynasm.jitstats index 4a3f51fe394..3a3b8ddb6b6 100644 --- a/pyre/bench/synth/inline_chain_depth_typeflip.dynasm.jitstats +++ b/pyre/bench/synth/inline_chain_depth_typeflip.dynasm.jitstats @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=3818 +guard_failures=3820 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/inline_chain_depth_typeflip.wasm.jitstats b/pyre/bench/synth/inline_chain_depth_typeflip.wasm.jitstats index 4a3f51fe394..3a3b8ddb6b6 100644 --- a/pyre/bench/synth/inline_chain_depth_typeflip.wasm.jitstats +++ b/pyre/bench/synth/inline_chain_depth_typeflip.wasm.jitstats @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=3818 +guard_failures=3820 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/str_fstring.cranelift.darwin.jitstats b/pyre/bench/synth/str_fstring.cranelift.darwin.jitstats new file mode 100644 index 00000000000..5c093f4f38b --- /dev/null +++ b/pyre/bench/synth/str_fstring.cranelift.darwin.jitstats @@ -0,0 +1,15 @@ +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 +field_pos_spec_misplaced=0 +guard_failures=658 +internal_compile_panics=0 +loops_aborted=0 +loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/str_fstring.cranelift.jitstats b/pyre/bench/synth/str_fstring.cranelift.jitstats index 5c093f4f38b..e45d14ff4ca 100644 --- a/pyre/bench/synth/str_fstring.cranelift.jitstats +++ b/pyre/bench/synth/str_fstring.cranelift.jitstats @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=658 +guard_failures=659 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/str_fstring.dynasm.win32.github-actions.jitstats b/pyre/bench/synth/str_fstring.dynasm.darwin.jitstats similarity index 100% rename from pyre/bench/synth/str_fstring.dynasm.win32.github-actions.jitstats rename to pyre/bench/synth/str_fstring.dynasm.darwin.jitstats diff --git a/pyre/bench/synth/str_fstring.dynasm.jitstats b/pyre/bench/synth/str_fstring.dynasm.jitstats index e45d14ff4ca..5c093f4f38b 100644 --- a/pyre/bench/synth/str_fstring.dynasm.jitstats +++ b/pyre/bench/synth/str_fstring.dynasm.jitstats @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=659 +guard_failures=658 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/pyre-jit/src/call_jit.rs b/pyre/pyre-jit/src/call_jit.rs index f3cde274bb6..d422e340804 100644 --- a/pyre/pyre-jit/src/call_jit.rs +++ b/pyre/pyre-jit/src/call_jit.rs @@ -3226,6 +3226,11 @@ pub fn trace_and_compile_from_bridge( // blackhole re-run on a path that would ignore the concrete result. allow_finish_direct_return: bool, ) -> BridgeResolution { + { + let (driver, _) = crate::eval::driver_pair(); + driver.clear_last_compiled_artifact_invalidation_flag(); + } + use crate::eval::build_jit_state; use crate::jit::state::PyreEnv; @@ -4029,6 +4034,8 @@ fn jit_ca_handle_guard_failure( } } }; + // compile.py:204-207 record_loop_or_bridge registers every bridge's dependencies. + crate::eval::register_quasi_immutable_deps(source_green_key); if majit_metainterp::majit_log_enabled() { eprintln!( @@ -4109,6 +4116,8 @@ fn try_compile_ca_bridge( trace_and_compile_from_bridge(descr_arc, frame, raw_values, &exit_layout, 0, false), BridgeResolution::CompiledContinue ); + // The wasm CALL_ASSEMBLER path likewise bypasses handle_fail's dependency drain. + crate::eval::register_quasi_immutable_deps(owning_key); // `MetaInterp::compile_bridge` records a wasm `Unsupported` before the // walker returns. Reuse that canonical guard identity here rather than // creating a separate CA-side decline table. diff --git a/pyre/pyre-jit/src/eval.rs b/pyre/pyre-jit/src/eval.rs index 7d321081056..ebc2cd80848 100644 --- a/pyre/pyre-jit/src/eval.rs +++ b/pyre/pyre-jit/src/eval.rs @@ -5950,7 +5950,7 @@ pub fn make_green_key(code_ptr: *const (), pc: usize) -> u64 { /// constant under a `QUASIIMMUT_FIELD(w_type, _version_tag)`. `mutated()` /// (typeobject.py:285-291) bumps the tag and walks subclasses, and the setter /// revokes each level's loops. -fn register_quasi_immutable_deps(_green_key: u64) { +pub(crate) fn register_quasi_immutable_deps(_green_key: u64) { let (driver, _) = driver_pair(); let deps: Vec<(u64, u32)> = std::mem::take(&mut driver.meta_interp_mut().last_quasi_immutable_deps); @@ -8890,24 +8890,34 @@ fn handle_fail( true, ) }; + if matches!( + &resolution, + crate::call_jit::BridgeResolution::ResumeBlackhole + ) { + let (driver, _) = driver_pair(); + if !driver + .meta_interp_mut() + .last_quasi_immutable_deps + .is_empty() + { + majit_metainterp::mc_diag_bump(77); + } + } + // compile.py:204-207 record_loop_or_bridge registers dependencies + // for every compiled bridge, independent of its next resolution. + if let Some((green_key, _, _)) = + crate::call_jit::bridge_source_identity_from_descr(descr_arc) + { + register_quasi_immutable_deps(green_key); + } match resolution { crate::call_jit::BridgeResolution::CompiledContinue => { - if let Some((green_key, _, _)) = - crate::call_jit::bridge_source_identity_from_descr(descr_arc) - { - register_quasi_immutable_deps(green_key); - } // compile.py:708: bridge compiled → ContinueRunningNormally. // RPython: the bridge is attached to the guard descr; // re-entering compiled code will follow the bridge. return HandleFailOutcome::BridgeCompiled; } crate::call_jit::BridgeResolution::Finished(cv) => { - if let Some((green_key, _, _)) = - crate::call_jit::bridge_source_identity_from_descr(descr_arc) - { - register_quasi_immutable_deps(green_key); - } // #177: the walk ran the resumed frame forward to its // return and captured the concrete result; hand it back // as `DoneWithThisFrame` (`interpret()` raising it from @@ -8921,11 +8931,6 @@ fn handle_fail( return HandleFailOutcome::BridgeFinished(v); } crate::call_jit::BridgeResolution::FinishedException(cv) => { - if let Some((green_key, _, _)) = - crate::call_jit::bridge_source_identity_from_descr(descr_arc) - { - register_quasi_immutable_deps(green_key); - } return HandleFailOutcome::BridgeRaised(finish_concrete_raise_error(cv)); } crate::call_jit::BridgeResolution::ResumeBlackhole => {} diff --git a/pyre/pyre-wasm-runner/src/main.rs b/pyre/pyre-wasm-runner/src/main.rs index e148e8d5e82..d32d565d304 100644 --- a/pyre/pyre-wasm-runner/src/main.rs +++ b/pyre/pyre-wasm-runner/src/main.rs @@ -805,6 +805,9 @@ fn run(module_path: &Path, source: &str, script: &Path) -> Result { "unroll_cancelled_invalid_loop", "unroll_free_retry_rescued", "unroll_free_retry_failed", + "qmut_deps_simple_loop", + "qmut_deps_entry_bridge", + "qmut_deps_blackhole_arm", ]; let mut parts = Vec::new(); for (i, lbl) in labels.iter().enumerate() {