diff --git a/majit/majit-metainterp/src/compile.rs b/majit/majit-metainterp/src/compile.rs index b39b36527d7..baeba5e77a9 100644 --- a/majit/majit-metainterp/src/compile.rs +++ b/majit/majit-metainterp/src/compile.rs @@ -5407,10 +5407,6 @@ impl TraceCtx { self.inline_frames.len() } - pub fn inline_trace_depth(&self) -> usize { - self.inline_trace_positions.len() - } - /// Update the green key for this trace. /// /// RPython pyjitpl.py reached_loop_header(): when func-entry tracing @@ -5486,36 +5482,6 @@ impl TraceCtx { pub(crate) fn pop_inline_frame(&mut self) { self.inline_frames.pop(); } - - pub fn push_inline_trace_position(&mut self, green_key: u64) { - self.inline_trace_positions - .push((green_key, self.recorder.num_ops())); - } - - pub fn pop_inline_trace_position(&mut self) { - self.inline_trace_positions.pop(); - } - - pub fn truncate_inline_trace_positions(&mut self, depth: usize) { - self.inline_trace_positions.truncate(depth); - } - - /// pyjitpl.py:3538-3570 find_biggest_function - /// - /// RPython only considers portal frames recorded in - /// `portal_trace_positions`. The root frame created by - /// `initialize_state_from_start()` has no greenkey and is not added to - /// that stack, so a non-inlined root trace returns `None` and the caller - /// falls back to `prepare_trace_segmenting()`. - pub fn find_biggest_function(&self) -> Option { - let current_pos = self.recorder.num_ops(); - self.inline_trace_positions - .iter() - .copied() - .map(|(green_key, start_pos)| (green_key, current_pos.saturating_sub(start_pos))) - .max_by_key(|&(_, size)| size) - .map(|(green_key, _)| green_key) - } } #[cfg(test)] diff --git a/majit/majit-metainterp/src/pyjitpl.rs b/majit/majit-metainterp/src/pyjitpl.rs index 6079e09bba8..5290f07781e 100644 --- a/majit/majit-metainterp/src/pyjitpl.rs +++ b/majit/majit-metainterp/src/pyjitpl.rs @@ -1405,12 +1405,14 @@ pub struct MetaInterp { /// longest-traced inlined function for abort reporting; the reset /// to `None` at pyjitpl.py:2795 signals that the trace aborted. /// - /// pyre's existing `find_biggest_function` (trace_ctx.rs:625) uses - /// `TraceCtx::inline_trace_positions` — a narrower subset that only - /// tracks active inlined callees. Keeping this field here mirrors - /// RPython's shape so a future port of `find_biggest_function` can - /// line-by-line read the start/end stack; callers that merely want - /// the active-frame list should keep using `inline_trace_positions`. + /// This is the log `find_biggest_function` reads. It replaced a + /// `TraceCtx::inline_trace_positions` that held only the *active* + /// inlined callees: a live stack pops a callee on return, so the + /// frame that grew the trace and then returned — the usual culprit — + /// was gone by the time the limit was crossed. + /// + /// `arm_portal_trace_positions` re-arms it per trace; upstream instead + /// builds one `MetaInterp` per tracing attempt. pub portal_trace_positions: Option, crate::recorder::TracePosition)>>, /// pyjitpl.py:2401 `self.current_call_id = 0`. @@ -4243,6 +4245,7 @@ impl MetaInterp { (input_types, num_inputs, index_of_virtualizable) }); self.tracing = Some(ctx); + self.arm_portal_trace_positions(); // pyjitpl.py:1547-1556 auto-stamp gate inputs — see // `setup_tracing` for rationale. Bridge-trace // distinction now flows through @@ -4526,6 +4529,7 @@ impl MetaInterp { (input_types, num_inputs, index_of_virtualizable) }); self.tracing = Some(ctx); + self.arm_portal_trace_positions(); // pyjitpl.py:1547-1556 `opimpl_jit_merge_point` auto-stamp // gate inputs. Both `portal_call_depth` and // `has_compiled_targets(ptoken)` feed the primary-trace gate; @@ -5196,16 +5200,16 @@ impl MetaInterp { fn blackhole_trace_too_long_slow(&mut self) -> Option { let ctx = self.tracing.as_ref().expect("tracing is Some"); let green_key = ctx.green_key; + // pyjitpl.py:2801 `if self.current_merge_points:` — outermost + // loop's greenkey, used only when one exists (never for bridges). + let outermost_merge_key = ctx.current_merge_points_first_greenkey(); // pyjitpl.py:2793: find_biggest_function — if an inlined function // caused the bloat, disable just that function. - let huge_fn_key = ctx.find_biggest_function(); + let huge_fn_key = self.find_biggest_function(); // pyjitpl.py:2795: `self.portal_trace_positions = None` marks the // abort boundary so post-abort consumers (e.g. test inspections // at pyjitpl.py:3547) can detect a terminated trace session. self.portal_trace_positions = None; - // pyjitpl.py:2801 `if self.current_merge_points:` — outermost - // loop's greenkey, used only when one exists (never for bridges). - let outermost_merge_key = ctx.current_merge_points_first_greenkey(); if let Some(huge_fn_key) = huge_fn_key { self.warm_state.disable_noninlinable_function(huge_fn_key); // pyjitpl.py:2799-2800: stash the aborted jd_sd + greenkey so @@ -12590,6 +12594,7 @@ impl MetaInterp { ctx.set_trace_limit(self.warm_state.trace_limit() as usize); ctx.callinfocollection = self.callinfocollection.clone(); self.tracing = Some(ctx); + self.arm_portal_trace_positions(); // pyjitpl.py:2411 `self.jitdriver_sd = jitdriver_sd`: bridges // inherit the parent's driver. The bridge entry path does not // thread `driver_descriptor`, so fall back to scanning for the @@ -14148,6 +14153,74 @@ impl MetaInterp { action } + /// pyjitpl.py:3538-3575 `MetaInterp.find_biggest_function`. + /// + /// `portal_trace_positions` is a flat log, not a stack: `newframe` + /// appends `(jd_no, Some(greenkey), pos)` on entry and `popframe` + /// `(jd_no, None, pos)` on exit, so a callee that already returned still + /// has both of its entries. Walking it with a side stack therefore sizes + /// every portal frame the trace ever entered, which is the point — the + /// function to stop inlining is usually one that finished long before the + /// limit was crossed. A frame still open when the trace overflowed has no + /// closing entry; upstream measures only the outermost of those + /// (`start_stack[0]`) against the current trace position. + /// + /// `size` is the distance between two `TracePosition::_pos` cursors, the + /// `pos[0]` upstream subtracts (opencoder.py:475). + /// + /// Returns the green key of the largest frame, or `None` when the log + /// holds no closed or open portal frame — the root frame is created by + /// `initialize_state_from_start` without a greenkey and never enters the + /// log, so a trace that inlined nothing answers `None` and the caller + /// falls through to `prepare_trace_segmenting`. + pub fn find_biggest_function(&self) -> Option { + let positions = self.portal_trace_positions.as_ref()?; + let mut start_stack: Vec<(u64, usize)> = Vec::new(); + let mut max_size = 0usize; + let mut max_key = None; + for &(_jd_no, key, pos) in positions { + match key { + // pyjitpl.py:3547-3548 `if key is not None: start_stack.append`. + Some(key) => start_stack.push((key, pos._pos)), + // pyjitpl.py:3549-3559 the closing entry sizes the frame it + // closes. An unmatched close cannot happen while `newframe` / + // `popframe` are the only writers, so it is left to `pop`'s + // `None` rather than given a recovery path. + None => { + if let Some((green_key, start_pos)) = start_stack.pop() { + let size = pos._pos.saturating_sub(start_pos); + if size > max_size { + max_size = size; + max_key = Some(green_key); + } + } + } + } + } + // pyjitpl.py:3560-3570 `if start_stack:` — one frame, the outermost, + // measured against where the trace stopped. + if let Some(&(green_key, start_pos)) = start_stack.first() { + let current = self.tracing.as_ref()?.get_trace_position()._pos; + if current.saturating_sub(start_pos) > max_size { + max_key = Some(green_key); + } + } + max_key + } + + /// pyjitpl.py:2391 `self.portal_trace_positions = []`. + /// + /// Upstream gets this for free: it builds a `MetaInterp` per tracing + /// attempt, so the log starts empty and dies with the trace. pyre's + /// `MetaInterp` outlives every trace, so the list is re-armed at each + /// trace start instead. Without it the log would carry frames from + /// previous traces — whose `_pos` cursors index a different recorder — + /// and `blackhole_if_trace_too_long`'s `= None` would retire it for the + /// rest of the process after the first overflow. + fn arm_portal_trace_positions(&mut self) { + self.portal_trace_positions = Some(Vec::new()); + } + /// pyjitpl.py:2427-2429 `MetaInterp.is_main_jitcode(jitcode)`. /// /// ```python @@ -20308,6 +20381,122 @@ mod metainterp_static_data_tests { ); } + /// A MetaInterp with one recursive portal registered — the shape whose + /// frames `is_main_jitcode` admits to `portal_trace_positions` — and the + /// jitcode `perform_call` takes. Not yet tracing. + fn meta_with_recursive_portal() -> (MetaInterp<()>, std::sync::Arc) { + use crate::jitcode::JitCodeBuilder; + + let mut meta = MetaInterp::<()>::new(0); + meta.finish_setup_descrs_for_jitdrivers(); + let mut jd = crate::jitdriver::JitDriverStaticData::new(vec![], vec![]); + jd.is_recursive = true; + let idx = { + let MetaInterp { + staticdata, + backend, + .. + } = &mut meta; + std::sync::Arc::get_mut(staticdata) + .unwrap() + .register_jitdriver_sd(jd, backend) + }; + let mut jc = JitCodeBuilder::new().finish(); + jc.replace_jitdriver_sd(Some(idx)); + (meta, std::sync::Arc::new(jc)) + } + + fn start_tracing(meta: &mut MetaInterp<()>) { + let action = meta.force_start_tracing(0, (0, 0), None, &[]); + assert!(matches!(action, crate::BackEdgeAction::StartedTracing)); + } + + fn record_ops(meta: &mut MetaInterp<()>, n: usize) { + let ctx = meta.tracing.as_mut().expect("tracing is Some"); + for _ in 0..n { + ctx.record_op(majit_ir::OpCode::PtrEq, &[]); + } + } + + #[test] + fn find_biggest_function_sizes_a_callee_that_already_returned() { + // pyjitpl.py:3538-3559. The frame that grew the trace is usually one + // that returned before the limit was crossed; `portal_trace_positions` + // keeps both of its entries, so the walk can still size it. The + // `inline_trace_positions` stack this replaced popped on return and + // could only ever see the frames still open at the overflow. + let (mut meta, jc) = meta_with_recursive_portal(); + start_tracing(&mut meta); + + meta.perform_call(jc.clone(), &[], Some(0xa11)).unwrap_err(); + record_ops(&mut meta, 5); + meta.popframe(true); + + meta.perform_call(jc, &[], Some(0xb22)).unwrap_err(); + record_ops(&mut meta, 1); + meta.popframe(true); + + assert_eq!( + meta.find_biggest_function(), + Some(0xa11), + "the larger frame wins even though both have returned" + ); + } + + #[test] + fn find_biggest_function_measures_an_open_frame_against_the_current_position() { + // pyjitpl.py:3560-3570 `if start_stack:` — a frame the overflow + // interrupted has no closing entry, so its size is measured against + // where the trace stopped. + let (mut meta, jc) = meta_with_recursive_portal(); + start_tracing(&mut meta); + + meta.perform_call(jc.clone(), &[], Some(0xa11)).unwrap_err(); + record_ops(&mut meta, 1); + meta.popframe(true); + + meta.perform_call(jc, &[], Some(0xb22)).unwrap_err(); + record_ops(&mut meta, 5); + + assert_eq!(meta.find_biggest_function(), Some(0xb22)); + } + + #[test] + fn find_biggest_function_is_none_without_an_inlined_portal_frame() { + // The root frame carries no greenkey, so a trace that inlined nothing + // leaves the log empty and the caller takes the segmenting arm. + let (mut meta, _jc) = meta_with_recursive_portal(); + start_tracing(&mut meta); + record_ops(&mut meta, 5); + assert_eq!(meta.find_biggest_function(), None); + } + + #[test] + fn portal_trace_positions_are_rearmed_for_each_trace() { + // pyjitpl.py:2391. Upstream builds a MetaInterp per tracing attempt; + // pyre re-arms the log instead. Without it the `= None` that + // `blackhole_trace_too_long_slow` writes would retire the log for the + // rest of the process, and a surviving list would mix `_pos` cursors + // from a recorder the next trace does not use. + let (mut meta, jc) = meta_with_recursive_portal(); + // The state `blackhole_trace_too_long_slow` leaves behind: this + // MetaInterp already overflowed one trace and retired its log. + meta.portal_trace_positions = None; + + start_tracing(&mut meta); + assert_eq!( + meta.portal_trace_positions.as_ref().map(Vec::len), + Some(0), + "the next trace starts from an empty log, not from None" + ); + meta.perform_call(jc, &[], Some(0xa11)).unwrap_err(); + assert_eq!( + meta.portal_trace_positions.as_ref().expect("Some").len(), + 1, + "and newframe records into it again" + ); + } + #[test] fn enter_leave_portal_frame_no_op_when_not_tracing() { // Without an active TraceCtx the named entry must not panic and diff --git a/majit/majit-metainterp/src/trace_ctx.rs b/majit/majit-metainterp/src/trace_ctx.rs index 673dbe7d76f..76493dcb371 100644 --- a/majit/majit-metainterp/src/trace_ctx.rs +++ b/majit/majit-metainterp/src/trace_ctx.rs @@ -239,12 +239,6 @@ pub struct TraceCtx { /// doing tuple-equality comparisons in [`recursive_depth`] and /// [`is_tracing_key`]. pub(crate) inline_frames: Vec<(usize, usize)>, - /// Start positions for currently active inlined trace-through frames. - /// - /// This mirrors the subset of PyPy's `portal_trace_positions` that we - /// need for `find_biggest_function()`: active inlined callees and the - /// trace length at which each one started tracing. - pub(crate) inline_trace_positions: Vec<(u64, usize)>, /// Structured green key values (if provided by the interpreter). green_key_values: Option, /// Declarative driver layout metadata, if provided by the interpreter. @@ -1502,7 +1496,6 @@ impl TraceCtx { green_key_raw: (0, 0), root_green_key_raw: (0, 0), inline_frames: Vec::new(), - inline_trace_positions: Vec::new(), green_key_values: None, driver_descriptor: None, virtualizable_boxes: None, @@ -1589,7 +1582,6 @@ impl TraceCtx { green_key_raw: (0, 0), root_green_key_raw: (0, 0), inline_frames: Vec::new(), - inline_trace_positions: Vec::new(), green_key_values: Some(green_key_values), driver_descriptor: None, virtualizable_boxes: None, 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 e90a95dda3b..656842672ea 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 @@ -3,10 +3,12 @@ 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_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=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 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 e90a95dda3b..656842672ea 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 @@ -3,10 +3,12 @@ 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_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=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 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 e90a95dda3b..656842672ea 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 @@ -3,10 +3,12 @@ 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_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=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 diff --git a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.cranelift.jitstats b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.cranelift.jitstats new file mode 100644 index 00000000000..9400653eeeb --- /dev/null +++ b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.cranelift.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=1 diff --git a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.dynasm.jitstats b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.dynasm.jitstats new file mode 100644 index 00000000000..9400653eeeb --- /dev/null +++ b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.dynasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=1 diff --git a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.py b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.py new file mode 100644 index 00000000000..0e5096d0a1a --- /dev/null +++ b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.py @@ -0,0 +1,64 @@ +# Companion to blackhole_inlined_callee_local_after_escape, +# carrying that file's shape at a force the constant-depth +# `sys._getframe` arm DECLINES, so the machinery it documents stays +# covered now that its own call site folds. +# +# `try_walker_specialize_sys_getframe` (jitcode_dispatch/specialize.rs) takes +# only depth 0 at the top walk level, where `getframe`'s answer IS the portal +# virtualizable and no force is needed. +# The `_getframe` call itself folds here; the added `.f_locals` read is the +# forcing residual, and it forces the SAME frame, so the shape below is +# unchanged apart from where the force comes from. +# +# The counters recorded for this file are the ones its original +# carried before the fold; a diff against them is a real change in the escape +# machinery, not in the arm. +# +# Guard for what an adopted multi-frame blackhole chain owes its inner levels. +# +# An inlined callee assigns a local, the frame then escapes through a residual +# `sys._getframe()`, and an attribute read POSITIONED AFTER that escape reads the +# local back. The read is executed by the blackhole, not by the walk, so the +# shape holds the adopt to two separate obligations and fails differently on +# each: +# +# * every LOAD_FAST lowers to `getarrayitem_vable_r` on the level's own frame +# array, so a level whose locals were left unpublished resumes `tb` as null +# and the attribute read faults in `object_getattr_miss` -- a hard SIGSEGV +# (rc=139) with no output at all; +# * the traceback the callee stored has to name the frame the callee runs on, +# so a walk-time node anchored on any other object prints `False` here while +# still exiting 0. +# +# The second is the quieter one and the reason the assertion is an identity +# rather than a liveness check. Both need the escape to happen inside an +# INLINED callee: the same shape through the single-frame arm was always +# correct. +# +# Deliberately carries no `# pyre-check: max-pypy-ratio=` header: this guards an +# output, and the forcing read makes it a poor perf subject. +import sys + +N = 20000 + + +def catches_here(i): + try: + raise ValueError(i) + except ValueError as e: + tb = e.__traceback__ + f = sys._getframe() + _ = f.f_locals + return (tb.tb_frame is f, tb.tb_lineno - f.f_code.co_firstlineno) + + +def drive(): + seen = set() + k = 0 + while k < N: + seen.add(catches_here(k)) + k += 1 + return sorted(seen) + + +print(drive()) diff --git a/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.wasm.jitstats b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.wasm.jitstats new file mode 100644 index 00000000000..9400653eeeb --- /dev/null +++ b/pyre/bench/synth/blackhole_inlined_callee_local_after_escape_declined.wasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=1 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 959c5f5446c..1e12ce579fd 100644 --- a/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_after_store.cranelift.jitstats @@ -1,14 +1,14 @@ -bridges_compiled=0 +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=20 +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=4114 +guard_failures=201 internal_compile_panics=0 -loops_aborted=20 +loops_aborted=0 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 index 959c5f5446c..1e12ce579fd 100644 --- a/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_after_store.dynasm.jitstats @@ -1,14 +1,14 @@ -bridges_compiled=0 +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=20 +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=4114 +guard_failures=201 internal_compile_panics=0 -loops_aborted=20 +loops_aborted=0 loops_compiled=1 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 959c5f5446c..1e12ce579fd 100644 --- a/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_after_store.wasm.jitstats @@ -1,14 +1,14 @@ -bridges_compiled=0 +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=20 +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=4114 +guard_failures=201 internal_compile_panics=0 -loops_aborted=20 +loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_after_store_declined.cranelift.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store_declined.cranelift.jitstats new file mode 100644 index 00000000000..959c5f5446c --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store_declined.cranelift.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=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_declined.dynasm.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store_declined.dynasm.jitstats new file mode 100644 index 00000000000..959c5f5446c --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store_declined.dynasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=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_declined.py b/pyre/bench/synth/getframe_bridge_force_after_store_declined.py new file mode 100644 index 00000000000..a25c7783513 --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store_declined.py @@ -0,0 +1,54 @@ +# Companion to getframe_bridge_force_after_store, +# carrying that file's shape at a force the constant-depth +# `sys._getframe` arm DECLINES, so the machinery it documents stays +# covered now that its own call site folds. +# +# `try_walker_specialize_sys_getframe` (jitcode_dispatch/specialize.rs) takes +# only depth 0 at the top walk level, where `getframe`'s answer IS the portal +# virtualizable and no force is needed. +# `sys._getframe(1)` names a frame BELOW the portal, which the walk holds no +# OpRef for, so it stays the opaque forcing residual this file's shape needs. +# +# The counters recorded for this file are the ones its original +# carried before the fold; a diff against them is a real change in the escape +# machinery, not in the arm. +# +# 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. 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 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(1) + 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_declined.wasm.jitstats b/pyre/bench/synth/getframe_bridge_force_after_store_declined.wasm.jitstats new file mode 100644 index 00000000000..959c5f5446c --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_after_store_declined.wasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=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 index 7742a65cb84..1e12ce579fd 100644 --- a/pyre/bench/synth/getframe_bridge_force_plain.cranelift.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_plain.cranelift.jitstats @@ -1,12 +1,14 @@ -bridges_compiled=0 +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=20 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=4114 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=201 internal_compile_panics=0 -loops_aborted=20 +loops_aborted=0 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 index 7742a65cb84..1e12ce579fd 100644 --- a/pyre/bench/synth/getframe_bridge_force_plain.dynasm.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_plain.dynasm.jitstats @@ -1,12 +1,14 @@ -bridges_compiled=0 +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=20 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=4114 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=201 internal_compile_panics=0 -loops_aborted=20 +loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats b/pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats index 7742a65cb84..1e12ce579fd 100644 --- a/pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats +++ b/pyre/bench/synth/getframe_bridge_force_plain.wasm.jitstats @@ -1,12 +1,14 @@ -bridges_compiled=0 +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=20 +fbw_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=4114 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=201 internal_compile_panics=0 -loops_aborted=20 +loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_plain_declined.cranelift.jitstats b/pyre/bench/synth/getframe_bridge_force_plain_declined.cranelift.jitstats new file mode 100644 index 00000000000..959c5f5446c --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain_declined.cranelift.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_plain_declined.dynasm.jitstats b/pyre/bench/synth/getframe_bridge_force_plain_declined.dynasm.jitstats new file mode 100644 index 00000000000..959c5f5446c --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain_declined.dynasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_bridge_force_plain_declined.py b/pyre/bench/synth/getframe_bridge_force_plain_declined.py new file mode 100644 index 00000000000..b354794acfa --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain_declined.py @@ -0,0 +1,51 @@ +# Companion to getframe_bridge_force_plain, +# carrying that file's shape at a force the constant-depth +# `sys._getframe` arm DECLINES, so the machinery it documents stays +# covered now that its own call site folds. +# +# `try_walker_specialize_sys_getframe` (jitcode_dispatch/specialize.rs) takes +# only depth 0 at the top walk level, where `getframe`'s answer IS the portal +# virtualizable and no force is needed. +# `sys._getframe(1)` names a frame BELOW the portal, which the walk holds no +# OpRef for, so it stays the opaque forcing residual this file's shape needs. +# +# The counters recorded for this file are the ones its original +# carried before the fold; a diff against them is a real change in the escape +# machinery, not in the arm. +# +# 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(1)` 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(1) + names += len(fr.f_code.co_name) + total += i + return total, names + + +print(main()) diff --git a/pyre/bench/synth/getframe_bridge_force_plain_declined.wasm.jitstats b/pyre/bench/synth/getframe_bridge_force_plain_declined.wasm.jitstats new file mode 100644 index 00000000000..959c5f5446c --- /dev/null +++ b/pyre/bench/synth/getframe_bridge_force_plain_declined.wasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=4114 +internal_compile_panics=0 +loops_aborted=20 +loops_compiled=1 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 337a2cb819b..afd3c3c6425 100644 --- a/pyre/bench/synth/getframe_inlined_callee_own_frame.cranelift.jitstats +++ b/pyre/bench/synth/getframe_inlined_callee_own_frame.cranelift.jitstats @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=1 +loops_compiled=2 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 337a2cb819b..afd3c3c6425 100644 --- a/pyre/bench/synth/getframe_inlined_callee_own_frame.dynasm.jitstats +++ b/pyre/bench/synth/getframe_inlined_callee_own_frame.dynasm.jitstats @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=1 +loops_compiled=2 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 337a2cb819b..afd3c3c6425 100644 --- a/pyre/bench/synth/getframe_inlined_callee_own_frame.wasm.jitstats +++ b/pyre/bench/synth/getframe_inlined_callee_own_frame.wasm.jitstats @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=1 +loops_compiled=2 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 e90a95dda3b..656842672ea 100644 --- a/pyre/bench/synth/getframe_residual_callee_own_frame.cranelift.jitstats +++ b/pyre/bench/synth/getframe_residual_callee_own_frame.cranelift.jitstats @@ -3,10 +3,12 @@ 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_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=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 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 e90a95dda3b..656842672ea 100644 --- a/pyre/bench/synth/getframe_residual_callee_own_frame.dynasm.jitstats +++ b/pyre/bench/synth/getframe_residual_callee_own_frame.dynasm.jitstats @@ -3,10 +3,12 @@ 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_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=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 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 e90a95dda3b..656842672ea 100644 --- a/pyre/bench/synth/getframe_residual_callee_own_frame.wasm.jitstats +++ b/pyre/bench/synth/getframe_residual_callee_own_frame.wasm.jitstats @@ -3,10 +3,12 @@ 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_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=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=1 +loops_aborted=0 +loops_compiled=2 diff --git a/pyre/bench/synth/getframe_residual_callee_own_frame_declined.cranelift.jitstats b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.cranelift.jitstats new file mode 100644 index 00000000000..9400653eeeb --- /dev/null +++ b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.cranelift.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_residual_callee_own_frame_declined.dynasm.jitstats b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.dynasm.jitstats new file mode 100644 index 00000000000..9400653eeeb --- /dev/null +++ b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.dynasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_residual_callee_own_frame_declined.py b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.py new file mode 100644 index 00000000000..d15b022c4d3 --- /dev/null +++ b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.py @@ -0,0 +1,48 @@ +# pyre-check: max-pypy-ratio=32 +# Companion to getframe_residual_callee_own_frame, +# carrying that file's shape at a force the constant-depth +# `sys._getframe` arm DECLINES, so the machinery it documents stays +# covered now that its own call site folds. +# +# `try_walker_specialize_sys_getframe` (jitcode_dispatch/specialize.rs) takes +# only depth 0 at the top walk level, where `getframe`'s answer IS the portal +# virtualizable and no force is needed. +# The `_getframe` call itself folds here; the added `.f_locals` read is the +# forcing residual, and it forces the SAME frame, so the shape below is +# unchanged apart from where the force comes from. +# +# The counters recorded for this file are the ones its original +# carried before the fold; a diff against them is a real change in the escape +# machinery, not in the arm. +# +# Regression guard: a residual (may-force) callee that inspects its OWN frame +# via sys._getframe() must not clear the traced CALLER's virtualizable tracing +# token. Clearing it raised a spurious frame-escape with no committed resume pc, +# replaying the loop body from entry and double-applying the callee's +# non-journaled STORE_ATTR side effect -- a JIT-only wrong answer (c.n > loops). +import sys + + +class Counter: + pass + + +c = Counter() +c.n = 0 + + +def bump(x): + c.n += 1 # STORE_ATTR: non-journaled body effect + frame = sys._getframe(0) + frame.f_locals # may-force residual inspecting the callee's own frame + return x if frame is not None else -1 + + +def main(): + total = 0 + for i in range(20000): + total += bump(i) + print(total, c.n) + + +main() diff --git a/pyre/bench/synth/getframe_residual_callee_own_frame_declined.wasm.jitstats b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.wasm.jitstats new file mode 100644 index 00000000000..9400653eeeb --- /dev/null +++ b/pyre/bench/synth/getframe_residual_callee_own_frame_declined.wasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=1 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 48f9c4225bc..59f22855e15 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 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 48f9c4225bc..59f22855e15 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 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 48f9c4225bc..59f22855e15 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.cranelift.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.cranelift.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.dynasm.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.dynasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.py b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.py new file mode 100644 index 00000000000..15c72165ba9 --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.py @@ -0,0 +1,62 @@ +# Companion to getframe_root_loop_force_blackhole_crn, +# carrying that file's shape at a force the constant-depth +# `sys._getframe` arm DECLINES, so the machinery it documents stays +# covered now that its own call site folds. +# +# `try_walker_specialize_sys_getframe` (jitcode_dispatch/specialize.rs) takes +# only depth 0 at the top walk level, where `getframe`'s answer IS the portal +# virtualizable and no force is needed. +# `sys._getframe(1)` names a frame BELOW the portal, which the walk holds no +# OpRef for, so it stays the opaque forcing residual this file's shape needs. +# +# The counters recorded for this file are the ones its original +# carried before the fold; a diff against them is a real change in the escape +# machinery, not in the arm. +# +# Regression guard: SIGSEGV from a NULL operand-stack slot written by the +# single-frame blackhole's ContinueRunningNormally handoff. +# +# The walk roots at `main`, which HAS a Python loop, so its portal jitcode +# carries a jit_merge_point at the loop header. A sys._getframe force inside the +# loop latched a blackhole image built at the resume pc just past the residual; +# driving it ran to the loop back edge and raised ContinueRunningNormally at the +# merge point. The MIFrame is seeded from the live colors at the BUILD pc, but +# the merge point has its own live set, so a Ref color live at the merge but not +# at the build read back NULL -- and apply_blackhole_crn had no NULL guard, so it +# wrote a null into a live operand-stack slot. Resuming there faulted the +# interpreter (EXC_BAD_ACCESS at 0x0 in baseobjspace::next), exit 139, JIT-only. +# +# The walk's own flush declines exactly this case ("NULL operand-stack shadow +# slot (mid-expression)"); the blackhole path did not. +# +# Guarding it post-drive is NOT sufficient and this fixture also pins that: a +# post-drive decline discards a region the drive already executed and hands it +# back to the replay, which turned 199990000 into 200005595. +# +# This shape is also the one that exposed the two defects behind that NULL. The +# image seeded only the colors live at the build pc rather than the whole +# concrete bank; and the walk synchronized the virtualizable into the +# snapshot_for_tracing copy while the image's vable identity pointed at the live +# frame, so the drive read locals one Python iteration stale and accumulated +# total += 1039 where i was already 1040. +# +# All of it is now moot for the reason that matters: the CRN handoff no longer +# rebuilds the frame from the terminal register banks at all, so there is no +# NULL to write and nothing left to decline. The drive runs and this file +# adopts it five times. Its effects are idempotent, though, which is exactly +# what hid the residual heap half of a post-drive decline — see the +# `_nonidempotent` sibling. +import sys + + +def main(): + total = 0 + names = set() + for i in range(20000): + fr = sys._getframe(1) + names.add(fr.f_code.co_name) + total += i + print(total, sorted(names)) + + +main() diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.wasm.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_declined.wasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 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 48f9c4225bc..2ebe9ec52ca 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=5 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 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 48f9c4225bc..2ebe9ec52ca 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=5 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 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 48f9c4225bc..2ebe9ec52ca 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=5 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.py b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.py new file mode 100644 index 00000000000..846ec3c5479 --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.py @@ -0,0 +1,50 @@ +# Companion to getframe_root_loop_force_blackhole_crn_nonidempotent, +# carrying that file's shape at a force the constant-depth +# `sys._getframe` arm DECLINES, so the machinery it documents stays +# covered now that its own call site folds. +# +# `try_walker_specialize_sys_getframe` (jitcode_dispatch/specialize.rs) takes +# only depth 0 at the top walk level, where `getframe`'s answer IS the portal +# virtualizable and no force is needed. +# `sys._getframe(1)` names a frame BELOW the portal, which the walk holds no +# OpRef for, so it stays the opaque forcing residual this file's shape needs. +# +# The counters recorded for this file are the ones its original +# carried before the fold; a diff against them is a real change in the escape +# machinery, not in the arm. +# +# Regression guard: a blackhole drive that is declined after the fact must not +# hand its already-executed region back to a replay. +# +# Same shape as `getframe_root_loop_force_blackhole_crn`: the walk roots at +# `main`, whose portal jitcode carries a jit_merge_point at the loop header, and +# a sys._getframe force inside the loop latches a blackhole image that drives to +# the back edge and raises ContinueRunningNormally there. +# +# The difference is the effect in the driven region. The sibling accumulates +# into a local and adds an already-present element to a set, so BOTH halves of a +# post-drive decline are invisible once the frame is restored: the local comes +# back with the undo, and re-running `set.add` of the same string changes +# nothing. A list append does not have that property. With the CRN arm forced +# to decline, that sibling prints its correct 199990000 while this file prints +# 20005 appends for 20000 iterations — one extra per declined drive. +# +# That is the measurement behind `adopt_blackhole_crn`: a decline taken after +# the drive cannot be repaired by any frame-level undo, because the residual +# calls the drive ran are heap effects and not frame state. So the CRN handoff +# resumes on the frame the blackhole already wrote instead of validating a +# rebuilt register image that could reject it. +import sys + + +def main(): + total = 0 + seen = [] + for i in range(20000): + fr = sys._getframe(1) + seen.append(fr.f_code.co_name) + total += i + print(total, len(seen)) + + +main() diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 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 48f9c4225bc..59f22855e15 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 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 48f9c4225bc..59f22855e15 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 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 48f9c4225bc..59f22855e15 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=0 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 -guard_failures=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=1 internal_compile_panics=0 -loops_aborted=5 -loops_compiled=0 +loops_aborted=0 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.cranelift.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.cranelift.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.dynasm.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.dynasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.py b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.py new file mode 100644 index 00000000000..d6f3e2ad624 --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.py @@ -0,0 +1,40 @@ +# Companion to getframe_root_loop_force_while_merge, +# carrying that file's shape at a force the constant-depth +# `sys._getframe` arm DECLINES, so the machinery it documents stays +# covered now that its own call site folds. +# +# `try_walker_specialize_sys_getframe` (jitcode_dispatch/specialize.rs) takes +# only depth 0 at the top walk level, where `getframe`'s answer IS the portal +# virtualizable and no force is needed. +# `sys._getframe(1)` names a frame BELOW the portal, which the walk holds no +# OpRef for, so it stays the opaque forcing residual this file's shape needs. +# +# The counters recorded for this file are the ones its original +# carried before the fold; a diff against them is a real change in the escape +# machinery, not in the arm. +# +# Reachability fixture: a force whose blackhole drive REACHES a jit_merge_point. +# +# Companion to getframe_root_loop_force_blackhole_crn. The walk roots at `main`, +# whose `while` loop gives the portal jitcode a merge point, so driving the +# latched image from just past the sys._getframe residual runs to the back edge +# and hands back ContinueRunningNormally rather than a frame terminal. +# +# Every getframe_* fixture that predates this one forces inside a short leaf +# callee whose jitcode has nothing after the residual but a return, so all of +# them ended in DoneWithThisFrameRef and the CRN arm -- the only arm that can +# reject the image -- was never exercised at all. +import sys +_gf = sys._getframe +kept = None +def main(): + global kept + total = 0 + i = 0 + while i < 30000: + kept = _gf(1) + total = total + 1 + i = i + 1 + return total +t = main() +print(t, kept.f_code.co_name) diff --git a/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.wasm.jitstats new file mode 100644 index 00000000000..77817899ecb --- /dev/null +++ b/pyre/bench/synth/getframe_root_loop_force_while_merge_declined.wasm.jitstats @@ -0,0 +1,14 @@ +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 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 +guard_failures=0 +internal_compile_panics=0 +loops_aborted=5 +loops_compiled=0 diff --git a/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats b/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats index c1e73e6acab..e2c79ca9c9e 100644 --- a/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats +++ b/pyre/bench/synth/getframe_stored_fback_walk.cranelift.jitstats @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats b/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats index c1e73e6acab..e2c79ca9c9e 100644 --- a/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats +++ b/pyre/bench/synth/getframe_stored_fback_walk.dynasm.jitstats @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 diff --git a/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats b/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats index c1e73e6acab..e2c79ca9c9e 100644 --- a/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats +++ b/pyre/bench/synth/getframe_stored_fback_walk.wasm.jitstats @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 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 @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 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 @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 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 @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 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 @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 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 @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 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 @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 2ffd81217e4..a0d4e399ef9 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=0 internal_compile_panics=0 -loops_aborted=20 -loops_compiled=0 +loops_aborted=15 +loops_compiled=1 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 2ffd81217e4..a0d4e399ef9 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=0 internal_compile_panics=0 -loops_aborted=20 -loops_compiled=0 +loops_aborted=15 +loops_compiled=1 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 2ffd81217e4..a0d4e399ef9 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 @@ -3,10 +3,12 @@ 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_blackhole_adopted_single_frame=5 fbw_rolled_back_with_effects=0 fbw_store_journal_rollback_failed=0 +field_pos_attached_misplaced=0 +field_pos_spec_misplaced=0 guard_failures=0 internal_compile_panics=0 -loops_aborted=20 -loops_compiled=0 +loops_aborted=15 +loops_compiled=1 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 511df2a116c..2215bb8e77b 100644 --- a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.cranelift.jitstats +++ b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.cranelift.jitstats @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 100644 --- a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.dynasm.jitstats +++ b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.dynasm.jitstats @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 511df2a116c..2215bb8e77b 100644 --- a/pyre/bench/synth/getframe_while_inlined_callee_subwalk.wasm.jitstats +++ b/pyre/bench/synth/getframe_while_inlined_callee_subwalk.wasm.jitstats @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=10 -loops_compiled=0 +loops_aborted=5 +loops_compiled=1 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 cce2266e45f..b5a8d73904a 100644 --- a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.cranelift.jitstats +++ b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.cranelift.jitstats @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=25 -loops_compiled=0 +loops_aborted=15 +loops_compiled=2 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 cce2266e45f..b5a8d73904a 100644 --- a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.dynasm.jitstats +++ b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.dynasm.jitstats @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=25 -loops_compiled=0 +loops_aborted=15 +loops_compiled=2 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 cce2266e45f..b5a8d73904a 100644 --- a/pyre/bench/synth/getframe_while_subwalk_decline_shapes.wasm.jitstats +++ b/pyre/bench/synth/getframe_while_subwalk_decline_shapes.wasm.jitstats @@ -3,10 +3,12 @@ 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_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=0 internal_compile_panics=0 -loops_aborted=25 -loops_compiled=0 +loops_aborted=15 +loops_compiled=2 diff --git a/pyre/pyre-interpreter/src/module/sys/vm.rs b/pyre/pyre-interpreter/src/module/sys/vm.rs index 082b0600896..a3d34234294 100644 --- a/pyre/pyre-interpreter/src/module/sys/vm.rs +++ b/pyre/pyre-interpreter/src/module/sys/vm.rs @@ -588,21 +588,25 @@ fn simple_namespace_replace(args: &[PyObjectRef]) -> crate::PyResult { /// virtualizable fields the JIT may still be holding. /// /// The price is that each call trips `vable_after_residual_call` and aborts the -/// trace: measured 2026-08-03, 138 of the synth corpus's 219 `loops_aborted`, -/// against `abort: vable escape: 0` and `forcings: 0` on the same fixtures -/// under real pypy3. +/// trace, against `abort: vable escape: 0` and `forcings: 0` on the same +/// fixtures under real pypy3. /// -/// A constant-depth traced-through arm does NOT reclaim that. It reaches 15 of -/// the 138; 70 sit at call sites inside an inlined callee, whose virtual frame -/// carries `last_instr = -1` (`pyre-jit-trace/src/helpers.rs`) with nothing -/// updating it through the body, so folding them would compile a -/// `_getframe().f_lineno` that reports the `def` line where the abort today -/// falls back to the interpreter and answers correctly. Part of this abort -/// count is load-bearing. The lever that could take the 70 is instead the -/// escalation from a *callee* frame escape to a *portal* virtualizable force in -/// `pyre-jit/src/eval.rs`, which has no upstream counterpart — -/// `executioncontext.py:91-107 leave` forces the leaving frame's own vref and -/// only *marks* `f_back`. +/// `try_walker_specialize_sys_getframe` +/// (`pyre-jit-trace/src/jitcode_dispatch/specialize.rs`) reproduces upstream's +/// traced-through form for the one level it can resolve — depth 0 at the top +/// walk level, where the answer IS the portal virtualizable — so those call +/// sites reach neither this function nor its forces. Over the `getframe_*` +/// corpus that took `loops_aborted` 155 → 71 and `loops_compiled` 6 → 22. +/// +/// Every other shape still arrives here, and both forces stay load-bearing for +/// it. A call site inside an INLINED callee is the one the arm must keep +/// declining: that frame carries `last_instr = -1` +/// (`pyre-jit-trace/src/helpers.rs`) with nothing updating it through the body, +/// so folding it would compile a `_getframe().f_lineno` reporting the `def` +/// line where the residual's force answers correctly today. The `*_declined` +/// fixtures under `pyre/bench/synth` hold each folded shape's escape at a +/// depth the arm refuses, so the machinery behind these forces keeps its +/// coverage. pub fn getframe(depth: i64) -> crate::PyResult { let ec = current_execution_context(); let mut current = if ec.is_null() { @@ -662,6 +666,26 @@ fn sys_getframe(args: &[PyObjectRef]) -> crate::PyResult { getframe(depth) } +/// `vm.py:54 f.mark_as_escaped()` as one non-forcing call, for the walker's +/// constant-depth [`getframe`] arm. +/// +/// Upstream's traced-through `getframe` emits it as `setfield_gc(p0, 1, +/// inst_escaped)`; `escaped` is a plain field, not one of the six +/// `interp_jit.py:25-30` declares, so writing it neither reads nor materialises +/// the virtualizable. The frame reaches this helper only to address the flag +/// byte — nothing under it can call +/// [`crate::executioncontext::force_frame`], which is what keeps the arm's +/// whole point (no residual force) intact. +/// +/// Emitted as a void `CallN`, matching the upstream `setfield_gc`'s lack of a +/// result: the store is the whole point, so nothing may drop it as dead. +pub extern "C" fn jit_frame_mark_as_escaped(frame: i64) { + let f = frame as *mut crate::PyFrame; + if !f.is_null() { + unsafe { (*f).mark_as_escaped() }; + } +} + /// True iff `callable` is the canonical `sys._getframe` builtin. /// /// `sys` is an ordinary mutable module, so the JIT walker has to key on diff --git a/pyre/pyre-interpreter/src/pyframe.rs b/pyre/pyre-interpreter/src/pyframe.rs index 65f021eebb6..fb03059b64d 100644 --- a/pyre/pyre-interpreter/src/pyframe.rs +++ b/pyre/pyre-interpreter/src/pyframe.rs @@ -3182,18 +3182,18 @@ impl PyFrame { /// nohidden walker itself) returns the raw `f_backref` link. #[inline] pub fn fget_f_back(&self) -> *mut PyFrame { - // `f_back` is read from app code, which goes on to read frame fields; - // the walk itself no longer forces, so force both ends here. `self` - // matters as much as the result: an inline-published callee frame is - // materialised only by a force, and `f_backref` off an unforced one - // names the wrong caller. + // pyframe.py:767-768 `fget_f_back` → `get_f_back` → + // `ExecutionContext.getnextframe_nohidden`, with no force of either + // end. Upstream needs none: `f_backref` is a `jit.virtual_ref` + // (executioncontext.py:88-89), so the read at :80 `frame.f_backref()` + // IS the force, and it is the foldable vref one rather than an + // unconditional materialisation — + // executioncontext.py:323-331 `force_all_frames` says so outright + // ("We get this effect simply by reading the f_back field of all + // frames"). Forcing both ends concretely here instead escapes the + // virtualizable during tracing and loses the loop. let this = self as *const PyFrame as *mut PyFrame; - crate::executioncontext::force_frame(this); - let back = crate::executioncontext::ExecutionContext::getnextframe_nohidden(this); - if !back.is_null() { - crate::executioncontext::force_frame(back); - } - back + crate::executioncontext::ExecutionContext::getnextframe_nohidden(this) } /// pyframe.py:641-642 fget_code → self.getcode(). Returns the `PyCode` 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 dd640ec4858..4aee882bb1d 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs @@ -5051,6 +5051,22 @@ pub(crate) fn dispatch_residual_call_iRd_kind( return Ok((DispatchOutcome::Continue, op.next_pc)); } + // `sys._getframe()` / `sys._getframe(0)` at the top walk level: publish the + // portal virtualizable directly, the shape upstream's + // `@jit.look_inside_iff(jit.isconstant(depth))` produces by tracing the + // constant-depth walk through. Like the `locals()` arm this runs BEFORE + // `try_execute_residual_call_via_executor` arms the vable token protocol, + // which is the point: `getframe`'s two `force_frame` calls clear that token + // from inside the residual and cost the loop. Any non-matching shape falls + // through to the generic residual (SAFE). + if ctx.is_authoritative_executor + && dst_bank == 'r' + && ei.pyre_helper == majit_ir::PyreHelperKind::CallFn + && try_walker_specialize_sys_getframe(ctx, code, op, &r_args, dst)?.is_some() + { + return Ok((DispatchOutcome::Continue, op.next_pc)); + } + // `math.sqrt(x)` / `float(x)` on an exact numeric argument: inline the // domain-guarded pure `CALL_F(sqrt_nonneg_jit)` (ll_math.rs) resp. the // `CastIntToFloat` / identity conversion instead of the opaque diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs index eb93dae1417..2ebfa49260a 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs @@ -7234,6 +7234,224 @@ pub(crate) fn try_walker_specialize_builtin_locals( Ok(Some(())) } +/// `sys._getframe()` / `sys._getframe(0)` at the top walk level: publish the +/// portal virtualizable itself instead of residualizing `vm.py:42-54 getframe`. +/// +/// `getframe` is `@jit.look_inside_iff(lambda space, depth: +/// jit.isconstant(depth))` (`pypy/module/sys/vm.py:41`), so a constant depth is +/// traced THROUGH: `ec.gettopframe_nohidden()` is a vref read that +/// `pyjitpl.py:2153-2172 _do_jit_force_virtual` answers with +/// `virtualizable_boxes[-1]` under a `ptr_eq` + `implement_guard_value`, the +/// `depth == 0` test folds away, and `mark_as_escaped` is one `setfield_gc`. +/// No call and no virtualizable force anywhere — pypy3 reports `forcings: 0` +/// and `abort: vable escape: 0` on the fixtures where pyre loses the loop. +/// +/// Pyre residualizes the same walk as one opaque `bh_call_fn(_getframe, +/// PY_NULL, depth)` `CallMayForce`, and [`pyre_interpreter::module::sys::vm::getframe`]'s +/// two explicit `force_frame`s — the stand-in for the injection +/// `rvirtualizable.py:49-53 hook_access_field` performs and pyre's rtyper +/// cannot build — then clear `TOKEN_TRACING_RESCALL` inside that call, which +/// `tracing_after_residual_call` reads as an escape +/// (`VableEscapedDuringResidualCall`). Removing the residual removes both +/// forces with it, and nothing has to replace them: `last_instr` is published +/// onto the portal frame at every may-force boundary (`LiveLastInstrGuard`), +/// and every getset that reads a virtualizable field off the handed-out frame +/// (`f_locals`, and `f_lasti` / `f_lineno` through their own `jit_getattr` +/// residual) is itself such a boundary. +/// +/// Emitted shape, following `getframe`'s body line by line: +/// `guard_value(callable)`; `guard_class` + exact-class + `getfield_gc_i` on +/// the depth box, whose resulting RAW int must be a trace constant — that +/// unboxed value is what `jit.isconstant(depth)` tests upstream, where +/// `@unwrap_spec(depth=int)` has already run OUTSIDE the looked-inside graph +/// (the wrapped `W_IntObject` the residual receives is built in-trace by +/// `NewWithVtable` + `SetfieldGc` and is never constant, so testing the box +/// declines 100% of the time); `getfield_gc_r(frame, execution_context)` + +/// `getfield_gc_r(ec, topframeref)` + `ptr_eq` + `guard_true`, the port of +/// `_do_jit_force_virtual`'s identity check; and one non-forcing void `Call` +/// for `mark_as_escaped`. The result IS `standard_virtualizable_box()`, +/// exactly as `_do_jit_force_virtual` returns `standard_box`. +/// +/// The guard reads `topframeref` raw, without the vref force or the +/// hidden-frame walk `gettopframe_nohidden` performs, so the gate below +/// requires the record-time chain to need neither: `topframeref` must BE the +/// portal pointer (not a `JitVirtualRef` naming it) and the nohidden walk must +/// land on the same frame. Any other chain declines, and at runtime a +/// `topframeref` that stops matching side-exits. +/// +/// Returns `None` (fall through to the generic residual, SAFE — exactly +/// today's behaviour) for every other shape: a rebound `sys._getframe`, a +/// bound receiver, a non-int / inexact / non-constant depth, any depth other +/// than 0, an inline sub-walk, and a walk with no standard virtualizable. +/// +/// ⛔ Depth 0 at the TOP walk level is the only level this may take. Inside an +/// inline sub-walk depth 0 names the callee's virtual frame, whose +/// `last_instr` is still the `-1` its constructor wrote and which nothing +/// updates through the inlined body (`jitcode_dispatch/mod.rs` says so in the +/// tree); depth > 0 names a frame the walk holds no OpRef for at all. The +/// sub-walk gate is what makes "depth 0 == the portal" true rather than +/// assumed. +pub(crate) fn try_walker_specialize_sys_getframe( + ctx: &mut WalkContext<'_, '_, Sym>, + code: &[u8], + op: &DecodedOp, + r_args: &[OpRef], + dst: usize, +) -> Result, DispatchError> { + // `sys._getframe()` (2) or `sys._getframe(depth)` (3). + if !(2..=3).contains(&r_args.len()) { + return Ok(None); + } + let arg_concretes = read_ref_var_list_concrete(code, op, 1, ctx); + let (ConcreteValue::Ref(concrete_callable), ConcreteValue::Ref(null_or_self)) = + (arg_concretes[0], arg_concretes[1]) + else { + return Ok(None); + }; + // A non-null `null_or_self` is a bound receiver `bh_call_fn_impl` prepends + // as arg0, not a plain `sys._getframe(...)` call. + if concrete_callable.is_null() + || !null_or_self.is_null() + || !pyre_interpreter::module::sys::vm::is_builtin_getframe_function(concrete_callable) + { + return Ok(None); + } + // The depth has to be an exact plain int holding 0 before anything is + // emitted; the guards below pin both facts for the compiled loop. + let exact_int_class = pyre_object::pyobject::get_instantiate(&pyre_object::pyobject::INT_TYPE); + let depth_arg = if r_args.len() == 3 { + let ConcreteValue::Ref(depth_obj) = arg_concretes[2] else { + return Ok(None); + }; + if depth_obj.is_null() + || unsafe { + !std::ptr::eq((*depth_obj).ob_type, &pyre_object::pyobject::INT_TYPE) + || !std::ptr::eq((*depth_obj).w_class, exact_int_class) + } + || unsafe { pyre_object::w_int_get_value(depth_obj) } != 0 + { + return Ok(None); + } + Some(r_args[2]) + } else { + None + }; + // `virtualizable_boxes` describe the PORTAL frame only. An inline sub-walk + // publishes a different concrete frame, so depth 0 there names the callee — + // the level this arm must not take. + if ctx.fbw_mode.inline_subwalk || current_inline_concrete_frame() != 0 { + return Ok(None); + } + let (Some(vable_op), Some(vable_ptr)) = ( + ctx.trace_ctx.standard_virtualizable_box(), + ctx.trace_ctx.standard_virtualizable_ptr(), + ) else { + return Ok(None); + }; + let ec = + pyre_interpreter::call::getexecutioncontext() as *mut pyre_interpreter::PyExecutionContext; + if ec.is_null() { + return Ok(None); + } + // The emitted guard compares the RAW `topframeref` against the portal, so + // require the record-time chain to make that comparison equivalent to + // `getframe`'s own resolution: the slot holds the frame pointer itself + // (an inlined callee's `JitVirtualRef` would decline here, and so would a + // deeper portal), and the hidden-frame walk lands on that same frame. + if unsafe { (*ec).topframeref } as usize != vable_ptr { + return Ok(None); + } + let frame = unsafe { (*ec).gettopframe_nohidden() }; + if frame.is_null() || frame as usize != vable_ptr { + return Ok(None); + } + + // --- emit the specialized IR (walker-native) --- + let pre_emit_pos = ctx.trace_ctx.get_trace_position(); + + // `sys` is an ordinary mutable module, so nothing else keeps the name bound + // to this builtin across iterations. + let callable_op = r_args[0]; + if !callable_op.is_constant() { + let expected = ctx.trace_ctx.const_ref(concrete_callable as i64); + ctx.trace_ctx + .record_guard(OpCode::GuardValue, &[callable_op, expected], 0); + walker_capture_snapshot_for_last_guard(ctx, op.pc)?; + ctx.trace_ctx + .heap_cache_mut() + .replace_box(callable_op, expected); + } + // `@unwrap_spec(depth=int)` and then `jit.isconstant(depth)`: unbox first, + // and require the UNBOXED value to be the trace constant. The unbox is + // only sound behind the class guards, so the constness decline rewinds + // rather than being hoisted above them. + if let Some(depth_op) = depth_arg { + let int_type_addr = &pyre_object::pyobject::INT_TYPE as *const _ as i64; + walker_guard_class(ctx, op.pc, depth_op, int_type_addr)?; + walker_guard_exact_w_class(ctx, op.pc, depth_op, exact_int_class)?; + let raw = crate::state::opimpl_getfield_gc_i( + ctx.trace_ctx, + depth_op, + crate::descr::int_intval_descr(), + ); + ctx.trace_ctx + .set_opref_concrete(raw, majit_ir::Value::Int(0)); + if !raw.is_constant() { + ctx.trace_ctx.cut_trace_with_snapshots(pre_emit_pos); + ctx.trace_ctx.heap_cache_mut().reset(); + return Ok(None); + } + } + // `ec = space.getexecutioncontext()` — recovered off the portal frame, the + // same route `walker_ec_enter` takes (`inline_call.rs`), since the outer + // frame's `execution_context` is always the true one. + let ec_op = ctx.trace_ctx.record_op_with_descr( + OpCode::GetfieldGcR, + &[vable_op], + crate::descr::pyframe_execution_context_descr(), + ); + ctx.trace_ctx + .set_opref_concrete(ec_op, majit_ir::Value::Ref(majit_ir::GcRef(ec as usize))); + // `f = ec.gettopframe_nohidden()` followed by `pyjitpl.py:2166-2168`'s + // `ptr_eq(vref_box, standard_box)` + `implement_guard_value`: the identity + // this arm resolved at record time, re-checked every compiled iteration. + let topframeref_op = ctx.trace_ctx.record_op_with_descr( + OpCode::GetfieldGcR, + &[ec_op], + crate::descr::ec_topframeref_descr(), + ); + ctx.trace_ctx.set_opref_concrete( + topframeref_op, + majit_ir::Value::Ref(majit_ir::GcRef(vable_ptr)), + ); + let is_standard = ctx + .trace_ctx + .record_op(OpCode::PtrEq, &[topframeref_op, vable_op]); + ctx.trace_ctx + .set_opref_concrete(is_standard, majit_ir::Value::Int(1)); + walker_emit_fold_guard_with_snapshot(ctx, op.pc, OpCode::GuardTrue, &[is_standard])?; + // `f.mark_as_escaped()` — vm.py:54. `escaped` is not one of the six fields + // `interp_jit.py:25-30` declares, so the store cannot force; it is + // load-bearing at `executioncontext.py:99-106 leave`, which forces the + // leaving frame's own vref only for a frame that escaped. + ctx.trace_ctx.call_void_typed_with_effect( + pyre_interpreter::module::sys::vm::jit_frame_mark_as_escaped as *const (), + &[vable_op], + &[majit_ir::Type::Ref], + majit_ir::EffectInfo::new( + majit_ir::ExtraEffect::CannotRaise, + majit_ir::OopSpecIndex::None, + ), + ); + // The walk IS the interpreter running, so the recorded store has to take + // effect here too — the residual would have applied it before returning. + unsafe { (*frame).mark_as_escaped() }; + + // `return f` — `_do_jit_force_virtual` hands back `standard_box` itself. + write_residual_call_result_to_dst(ctx, op.pc, dst, 'r', vable_op)?; + Ok(Some(())) +} + /// `math.sqrt(x)` on an exact int/float argument: inline the domain-guarded /// pure `CALL_F(sqrt_nonneg_jit)` (ll_math.rs `ll_math_sqrt` → `sqrt_nonneg`, /// EF_ELIDABLE_CANNOT_RAISE) instead of the opaque