From ff53f27434990c309d3b7900128ad89b0418ce78 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 3 Aug 2026 02:39:30 +0900 Subject: [PATCH] Split InterpreterFrame hot/cold fields into FrameColdData Move 10 rarely-used fields (trace, trace_lines, trace_opcodes, temporary_refs, f_locals_hidden_overlay, f_extra_locals, escaped, retained_back, pending_stack_pops, pending_unwind_from_stack) from InterpreterFrame into a lazily-allocated FrameColdData struct. InterpreterFrame now carries a single UnsafeCell>> (8 bytes) instead of ~200+ bytes of cold fields. The cold() accessor allocates on first access; frames that never trigger tracing or debugging pay no allocation cost. GC traversal skips cold data when it has not been allocated. Assisted-by: Claude --- crates/vm/src/builtins/frame.rs | 44 ++++--- crates/vm/src/builtins/type.rs | 7 +- crates/vm/src/frame.rs | 185 ++++++++++++++++------------- crates/vm/src/object/ext.rs | 8 +- crates/vm/src/protocol/callable.rs | 9 +- crates/vm/src/stdlib/_thread.rs | 4 +- crates/vm/src/vm/mod.rs | 14 ++- 7 files changed, 154 insertions(+), 117 deletions(-) diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 70ff9997946..94ec827b7a6 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -636,8 +636,14 @@ impl FrameObject { } else { self.iframe() }; - target.pending_stack_pops.store(pop_count as u32, Relaxed); - target.pending_unwind_from_stack.store(start_stack, Relaxed); + target + .cold() + .pending_stack_pops + .store(pop_count as u32, Relaxed); + target + .cold() + .pending_unwind_from_stack + .store(start_stack, Relaxed); target.lasti.store(best_addr as u32, Relaxed); Ok(()) } @@ -647,9 +653,9 @@ impl FrameObject { // Read from live source iframe if available. let live = self.find_live_source_iframe(); let trace = if !live.is_null() { - unsafe { &*live }.trace.lock().clone() + unsafe { &*live }.cold().trace.lock().clone() } else { - self.iframe().trace.lock().clone() + self.iframe().cold().trace.lock().clone() }; trace.unwrap_or_else(|| vm.ctx.none()) } @@ -667,13 +673,13 @@ impl FrameObject { PySetterValue::Delete => None, }; // Set on the materialized FrameObject. - (*self.iframe().trace.lock()).clone_from(&trace); + (*self.iframe().cold().trace.lock()).clone_from(&trace); // Also propagate to the live source iframe if this is a // materialized copy of a stack-allocated frame, so pdb's // f_trace assignment takes effect on the executing frame. let live = self.find_live_source_iframe(); if !live.is_null() { - *unsafe { &*live }.trace.lock() = trace; + *unsafe { &*live }.cold().trace.lock() = trace; } } @@ -682,7 +688,7 @@ impl FrameObject { fn f_trace_lines(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult { let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!()); - let boxed = zelf.iframe().trace_lines.lock(); + let boxed = zelf.iframe().cold().trace_lines.lock(); Ok(vm.ctx.new_bool(*boxed).into()) } @@ -701,11 +707,11 @@ impl FrameObject { .map_err(|_| vm.new_type_error("attribute value type must be bool"))?; let val = !value.as_bigint().is_zero(); - *zelf.iframe().trace_lines.lock() = val; + *zelf.iframe().cold().trace_lines.lock() = val; // Propagate to live source iframe. let live = zelf.find_live_source_iframe(); if !live.is_null() { - *unsafe { &*live }.trace_lines.lock() = val; + *unsafe { &*live }.cold().trace_lines.lock() = val; } Ok(()) @@ -718,7 +724,7 @@ impl FrameObject { #[pymember(type = "bool")] fn f_trace_opcodes(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult { let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!()); - let trace_opcodes = zelf.iframe().trace_opcodes.lock(); + let trace_opcodes = zelf.iframe().cold().trace_opcodes.lock(); Ok(vm.ctx.new_bool(*trace_opcodes).into()) } @@ -737,11 +743,11 @@ impl FrameObject { .map_err(|_| vm.new_type_error("attribute value type must be bool"))?; let val = !value.as_bigint().is_zero(); - *zelf.iframe().trace_opcodes.lock() = val; + *zelf.iframe().cold().trace_opcodes.lock() = val; // Propagate to live source iframe. let live = zelf.find_live_source_iframe(); if !live.is_null() { - *unsafe { &*live }.trace_opcodes.lock() = val; + *unsafe { &*live }.cold().trace_opcodes.lock() = val; } // TODO: Implement the equivalent of _PyEval_SetOpcodeTrace() @@ -798,10 +804,10 @@ impl Py { self.clear_stack_and_cells(); // Clear temporary refs - self.iframe().temporary_refs.lock().clear(); - self.iframe().f_locals_hidden_overlay.lock().take(); - self.iframe().f_extra_locals.lock().take(); - self.iframe().retained_back.lock().take(); + self.iframe().cold().temporary_refs.lock().clear(); + self.iframe().cold().f_locals_hidden_overlay.lock().take(); + self.iframe().cold().f_extra_locals.lock().take(); + self.iframe().cold().retained_back.lock().take(); Ok(()) } @@ -853,7 +859,7 @@ impl Py { } if prev.is_null() { // Check retained_back for frames whose callers have returned - let retained = self.iframe().retained_back.lock().clone(); + let retained = self.iframe().cold().retained_back.lock().clone(); if let Some(frame) = retained { frame.mark_escaped(); return Some(frame); @@ -879,7 +885,7 @@ impl Py { } // The caller already returned — check retained_back - let retained = self.iframe().retained_back.lock().clone(); + let retained = self.iframe().cold().retained_back.lock().clone(); if let Some(frame) = retained { frame.mark_escaped(); return Some(frame); @@ -906,7 +912,7 @@ impl Py { let iframe = unsafe { &*cur }; let fo = iframe.materialize(vm).to_owned(); if let Some(child) = child_fo.take() { - let mut guard = child.iframe().retained_back.lock(); + let mut guard = child.iframe().cold().retained_back.lock(); if guard.is_none() { *guard = Some(fo.clone()); } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index e2af7d21df4..1776270751e 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1513,7 +1513,12 @@ impl PyType { // temporary refs so they never see a dangling pointer. let keep_alive = |type_ref: PyTypeRef, retired: &mut Vec| { if let Some(frame) = vm.current_frame() { - frame.iframe().temporary_refs.lock().push(type_ref.into()); + frame + .iframe() + .cold() + .temporary_refs + .lock() + .push(type_ref.into()); } else { retired.push(type_ref.into()); } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index a05fc7184c8..bf276ec45e0 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -792,6 +792,39 @@ unsafe impl Traverse for FrameLocals { } } +/// Cold fields of InterpreterFrame that are only accessed during tracing, +/// debugging, frame inspection, or GC. Lazily allocated on first access +/// to keep the hot InterpreterFrame small. +pub(crate) struct FrameColdData { + pub trace: PyMutex>, + pub trace_lines: PyMutex, + pub trace_opcodes: PyMutex, + pub temporary_refs: PyMutex>, + pub f_locals_hidden_overlay: PyMutex>, + pub f_extra_locals: PyMutex>, + pub escaped: atomic::AtomicBool, + pub retained_back: PyMutex>, + pub pending_stack_pops: PyAtomic, + pub pending_unwind_from_stack: PyAtomic, +} + +impl Default for FrameColdData { + fn default() -> Self { + Self { + trace: PyMutex::new(None), + trace_lines: PyMutex::new(true), + trace_opcodes: PyMutex::new(false), + temporary_refs: PyMutex::new(Vec::new()), + f_locals_hidden_overlay: PyMutex::new(None), + f_extra_locals: PyMutex::new(None), + escaped: atomic::AtomicBool::new(false), + retained_back: PyMutex::new(None), + pending_stack_pops: Default::default(), + pending_unwind_from_stack: Default::default(), + } + } +} + /// Lightweight execution frame. Not a PyObject. /// Analogous to CPython's `_PyInterpreterFrame`. /// @@ -813,18 +846,10 @@ pub struct InterpreterFrame { /// index of last instruction ran pub lasti: PyAtomic, - /// Per-frame tracer function. `None` means no per-frame trace is set - /// (equivalent to `f_trace = None` in Python). This avoids a refcounted - /// None clone on every frame init. - pub trace: PyMutex>, /// Previous line number for LINE event suppression. pub(crate) prev_line: core::cell::Cell, - // member - pub trace_lines: PyMutex, - pub trace_opcodes: PyMutex, - pub temporary_refs: PyMutex>, /// Back-reference to owning generator/coroutine/async generator. /// Borrowed reference (not ref-counted) to avoid Generator↔FrameObject cycle. /// Cleared by the generator's Drop impl. @@ -836,32 +861,14 @@ pub struct InterpreterFrame { /// Used by `frame.clear()` to reject clearing an executing frame, /// even when called from a different thread. pub(crate) owner: atomic::AtomicI8, - /// Persistent overlay for `frame.f_locals` when hidden locals need a - /// snapshot separate from the backing locals mapping. - pub(crate) f_locals_hidden_overlay: PyMutex>, - /// Side storage for `f_locals` proxy keys that do not name a fast local. - /// Lazily created on first non-fast-key write. Mirrors `f_extra_locals`. - pub(crate) f_extra_locals: PyMutex>, - /// Set once a durable Python-level reference to this frame is handed out - /// (`f_locals` proxy, `sys._getframe`, `f_back`). A closed generator keeps - /// its locals alive while this is set, mirroring `frame_obj` ownership. - pub(crate) escaped: atomic::AtomicBool, - /// Strong reference to the caller frame, captured when this frame escapes - /// its execution so `f_back` still resolves after the caller returns and - /// leaves the live frame chain. - pub(crate) retained_back: PyMutex>, - /// Number of stack entries to pop after set_f_lineno returns to the - /// execution loop. set_f_lineno cannot pop directly because the - /// execution loop holds the state mutex. - pub(crate) pending_stack_pops: PyAtomic, - /// The encoded stack state that set_f_lineno wants to unwind *from*. - /// Used together with `pending_stack_pops` to identify Except entries - /// that need special exception-state handling. - pub(crate) pending_unwind_from_stack: PyAtomic, /// Pointer to the owning `Py`, or null for stack-allocated /// frames that have not been materialized yet. /// Stored as `usize` for `PyAtomic` compatibility. pub(crate) materialized: PyAtomic, + + /// Lazily-allocated cold data (tracing, debugging, frame inspection). + /// Not allocated until first access via `cold()`. + pub(crate) cold: OnceCell>, } // Raw pointers make InterpreterFrame !Send+!Sync by default. @@ -934,20 +941,11 @@ impl InterpreterFrame { locals, lasti: Radium::new(0), prev_line: core::cell::Cell::new(prev_line), - trace: PyMutex::new(None), - trace_lines: PyMutex::new(true), - trace_opcodes: PyMutex::new(false), - temporary_refs: PyMutex::new(vec![]), generator: PyAtomicBorrow::new(), previous: Radium::new(0), owner: atomic::AtomicI8::new(owner as i8), - f_locals_hidden_overlay: PyMutex::new(None), - f_extra_locals: PyMutex::new(None), - escaped: atomic::AtomicBool::new(false), - retained_back: PyMutex::new(None), - pending_stack_pops: Default::default(), - pending_unwind_from_stack: Default::default(), materialized: Radium::new(0), + cold: OnceCell::new(), } } @@ -1031,10 +1029,6 @@ impl InterpreterFrame { locals, lasti: Radium::new(self.lasti.load(Relaxed)), prev_line: core::cell::Cell::new(self.prev_line.get()), - trace: PyMutex::new(None), - trace_lines: PyMutex::new(true), - trace_opcodes: PyMutex::new(false), - temporary_refs: PyMutex::new(vec![]), generator: PyAtomicBorrow::new(), // Do NOT copy previous — it may point to stack-allocated frames // that become dangling after their call returns. The f_back chain @@ -1044,13 +1038,11 @@ impl InterpreterFrame { // If we copied Thread from the source iframe, frame.clear() would // reject the frame with "cannot clear an executing frame". owner: atomic::AtomicI8::new(FrameOwner::FrameObject as i8), - f_locals_hidden_overlay: PyMutex::new(None), - f_extra_locals: PyMutex::new(None), - escaped: atomic::AtomicBool::new(true), - retained_back: PyMutex::new(None), - pending_stack_pops: Default::default(), - pending_unwind_from_stack: Default::default(), materialized: Radium::new(0), + cold: OnceCell::from(Box::new(FrameColdData { + escaped: atomic::AtomicBool::new(true), + ..FrameColdData::default() + })), }; let frame_obj = FrameObject { @@ -1079,7 +1071,10 @@ impl InterpreterFrame { // is no longer executing and temporary_refs is cleared — at that // point the FrameObject is self-sustaining and GC can safely // traverse and collect it. - self.temporary_refs.lock().push(frame_ref.clone().into()); + self.cold() + .temporary_refs + .lock() + .push(frame_ref.clone().into()); // SAFETY: the pointer we stored above remains valid because // temporary_refs holds a strong reference. @@ -1119,20 +1114,14 @@ impl InterpreterFrame { locals, lasti: Radium::new(self.lasti.load(Relaxed)), prev_line: core::cell::Cell::new(self.prev_line.get()), - trace: PyMutex::new(None), - trace_lines: PyMutex::new(true), - trace_opcodes: PyMutex::new(false), - temporary_refs: PyMutex::new(vec![]), generator: PyAtomicBorrow::new(), previous: Radium::new(0), owner: atomic::AtomicI8::new(FrameOwner::FrameObject as i8), - f_locals_hidden_overlay: PyMutex::new(None), - f_extra_locals: PyMutex::new(None), - escaped: atomic::AtomicBool::new(true), - retained_back: PyMutex::new(None), - pending_stack_pops: Default::default(), - pending_unwind_from_stack: Default::default(), materialized: Radium::new(0), + cold: OnceCell::from(Box::new(FrameColdData { + escaped: atomic::AtomicBool::new(true), + ..FrameColdData::default() + })), }; let frame_obj = FrameObject { @@ -1175,6 +1164,19 @@ impl InterpreterFrame { Some(unsafe { &*self.func_obj }) } } + + /// Access the lazily-allocated cold data, allocating on first use. + #[inline] + pub(crate) fn cold(&self) -> &FrameColdData { + self.cold.get_or_init(|| Box::new(FrameColdData::default())) + } + + /// Access cold data without allocating. Returns `None` if cold data + /// has not been allocated yet. + #[inline] + pub(crate) fn cold_opt(&self) -> Option<&FrameColdData> { + self.cold.get().map(|b| &**b) + } } /// Python-visible frame object. Currently always wraps an `InterpreterFrame`. @@ -1323,11 +1325,13 @@ unsafe impl Traverse for FrameObject { }; iframe.localsplus.traverse(tracer_fn); iframe.locals.traverse(tracer_fn); - iframe.trace.traverse(tracer_fn); - iframe.temporary_refs.traverse(tracer_fn); - iframe.f_locals_hidden_overlay.traverse(tracer_fn); - iframe.f_extra_locals.traverse(tracer_fn); - iframe.retained_back.traverse(tracer_fn); + if let Some(cold) = iframe.cold_opt() { + cold.trace.traverse(tracer_fn); + cold.temporary_refs.traverse(tracer_fn); + cold.f_locals_hidden_overlay.traverse(tracer_fn); + cold.f_extra_locals.traverse(tracer_fn); + cold.retained_back.traverse(tracer_fn); + } } fn clear(&mut self, _out: &mut Vec) { @@ -1505,8 +1509,8 @@ impl FrameObject { for slot in fastlocals.iter_mut() { *slot = None; } - self.iframe().f_locals_hidden_overlay.lock().take(); - self.iframe().f_extra_locals.lock().take(); + self.iframe().cold().f_locals_hidden_overlay.lock().take(); + self.iframe().cold().f_extra_locals.lock().take(); } /// Store a borrowed back-reference to the owning generator/coroutine. @@ -1578,12 +1582,17 @@ impl FrameObject { /// Record that a durable Python-level reference to this frame escaped. pub(crate) fn mark_escaped(&self) { - self.iframe().escaped.store(true, atomic::Ordering::Release); + self.iframe() + .cold() + .escaped + .store(true, atomic::Ordering::Release); } /// Whether a durable reference to this frame has escaped. pub(crate) fn has_escaped(&self) -> bool { - self.iframe().escaped.load(atomic::Ordering::Acquire) + self.iframe() + .cold_opt() + .is_some_and(|c| c.escaped.load(atomic::Ordering::Acquire)) } pub fn lasti(&self) -> u32 { @@ -1769,12 +1778,12 @@ impl FrameObject { pub fn f_locals_mapping(&self, vm: &VirtualMachine) -> PyResult { self.check_locals_access(vm)?; if !self.has_active_hidden_locals() { - self.iframe().f_locals_hidden_overlay.lock().take(); + self.iframe().cold().f_locals_hidden_overlay.lock().take(); return self.locals(vm); } let overlay_dict = { - let mut overlay = self.iframe().f_locals_hidden_overlay.lock(); + let mut overlay = self.iframe().cold().f_locals_hidden_overlay.lock(); match overlay.as_ref() { Some(dict) => dict.clone(), None => { @@ -1808,7 +1817,7 @@ impl FrameObject { /// Copy the frame's extra-locals side storage (proxy keys that are not /// fast locals) into `mapping`. No-op when nothing was ever stored. fn fold_extra_locals(&self, mapping: &ArgMapping, vm: &VirtualMachine) -> PyResult<()> { - let extra = self.iframe().f_extra_locals.lock().clone(); + let extra = self.iframe().cold().f_extra_locals.lock().clone(); if let Some(extra) = extra { for (key, value) in &extra { mapping.mapping().ass_subscript(&key, Some(value), vm)?; @@ -1934,7 +1943,7 @@ impl FrameObject { { return Ok(value); } - let extra = self.iframe().f_extra_locals.lock().clone(); + let extra = self.iframe().cold().f_extra_locals.lock().clone(); if let Some(extra) = extra && let Some(value) = extra.get_item_opt(&*key, vm)? { @@ -1953,7 +1962,7 @@ impl FrameObject { if self.framelocalsproxy_getkeyindex(&key, true, vm)?.is_some() { return Ok(true); } - let extra = self.iframe().f_extra_locals.lock().clone(); + let extra = self.iframe().cold().f_extra_locals.lock().clone(); if let Some(extra) = extra { return Ok(extra.get_item_opt(&*key, vm)?.is_some()); } @@ -1991,7 +2000,7 @@ impl FrameObject { { return Err(vm.new_value_error("cannot remove local variables from FrameLocalsProxy")); } - let extra = self.iframe().f_extra_locals.lock().clone(); + let extra = self.iframe().cold().f_extra_locals.lock().clone(); if let Some(extra) = extra && extra.get_item_opt(&*key, vm)?.is_some() { @@ -2014,7 +2023,7 @@ impl FrameObject { { return Err(vm.new_value_error("cannot remove local variables from FrameLocalsProxy")); } - let extra = self.iframe().f_extra_locals.lock().clone(); + let extra = self.iframe().cold().f_extra_locals.lock().clone(); if let Some(extra) = extra && let Some(value) = extra.pop_item(&*key, vm)? { @@ -2041,7 +2050,7 @@ impl FrameObject { } fn extra_locals_get_or_create(&self, vm: &VirtualMachine) -> PyDictRef { - let mut extra = self.iframe().f_extra_locals.lock(); + let mut extra = self.iframe().cold().f_extra_locals.lock(); extra.get_or_insert_with(|| vm.ctx.new_dict()).clone() } } @@ -2372,7 +2381,7 @@ pub(crate) fn release_datastack_frame(frame: &Py, vm: &VirtualMachi // executing here (this frame is unwinding back into it), so its payload // pointer is live. { - let mut guard = frame.iframe().retained_back.lock(); + let mut guard = frame.iframe().cold().retained_back.lock(); if guard.is_none() { let prev = frame.previous_iframe(); *guard = unsafe { owned_chain_frame(prev) }; @@ -2613,31 +2622,39 @@ impl ExecutingFrame<'_> { /// Whether this frame has a per-frame trace function set. #[inline] fn trace_is_set(&self, _vm: &VirtualMachine) -> bool { - self.iframe().trace.lock().is_some() + self.iframe() + .cold_opt() + .is_some_and(|c| c.trace.lock().is_some()) } /// Access the frame's trace_opcodes lock. #[inline] fn trace_opcodes_is_set(&self) -> bool { - *self.iframe().trace_opcodes.lock() + self.iframe() + .cold_opt() + .is_some_and(|c| *c.trace_opcodes.lock()) } /// Get pending_stack_pops from the frame. #[inline] fn pending_stack_pops(&self) -> u32 { - self.iframe().pending_stack_pops.load(Relaxed) + self.iframe() + .cold_opt() + .map_or(0, |c| c.pending_stack_pops.load(Relaxed)) } /// Get pending_unwind_from_stack from the frame. #[inline] fn pending_unwind_from_stack(&self) -> i64 { - self.iframe().pending_unwind_from_stack.load(Relaxed) + self.iframe() + .cold_opt() + .map_or(0, |c| c.pending_unwind_from_stack.load(Relaxed)) } /// Set pending_stack_pops on the frame. #[inline] fn set_pending_stack_pops(&self, val: u32) { - self.iframe().pending_stack_pops.store(val, Relaxed); + self.iframe().cold().pending_stack_pops.store(val, Relaxed); } /// Run `__init__` for the tp_new specialization. `args` holds the diff --git a/crates/vm/src/object/ext.rs b/crates/vm/src/object/ext.rs index c4732d1cb3f..69ee0e3c510 100644 --- a/crates/vm/src/object/ext.rs +++ b/crates/vm/src/object/ext.rs @@ -333,7 +333,7 @@ impl PyAtomicRef { pub fn swap_to_temporary_refs(&self, pyref: PyRef, vm: &VirtualMachine) { let old = unsafe { self.swap(pyref) }; if let Some(frame) = vm.current_frame() { - frame.iframe().temporary_refs.lock().push(old.into()); + frame.iframe().cold().temporary_refs.lock().push(old.into()); } } } @@ -409,7 +409,7 @@ impl PyAtomicRef> { return; }; if let Some(frame) = vm.current_frame() { - frame.iframe().temporary_refs.lock().push(old.into()); + frame.iframe().cold().temporary_refs.lock().push(old.into()); } } } @@ -452,7 +452,7 @@ impl PyAtomicRef { pub fn swap_to_temporary_refs(&self, obj: PyObjectRef, vm: &VirtualMachine) { let old = unsafe { self.swap(obj) }; if let Some(frame) = vm.current_frame() { - frame.iframe().temporary_refs.lock().push(old); + frame.iframe().cold().temporary_refs.lock().push(old); } } } @@ -499,7 +499,7 @@ impl PyAtomicRef> { return; }; if let Some(frame) = vm.current_frame() { - frame.iframe().temporary_refs.lock().push(old); + frame.iframe().cold().temporary_refs.lock().push(old); } } } diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index 9e80c5b2c25..5a1605d4b5e 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -233,7 +233,12 @@ impl VirtualMachine { }; // Opcode events are only dispatched when f_trace_opcodes is set. - if is_opcode_event && !*frame_ref.iframe().trace_opcodes.lock() { + if is_opcode_event + && !frame_ref + .iframe() + .cold_opt() + .is_some_and(|c| *c.trace_opcodes.lock()) + { return Ok(None); } @@ -261,7 +266,7 @@ impl VirtualMachine { // trace_trampoline behavior: clear per-frame f_trace // and propagate the error. if let Some(frame_ref) = self.current_frame() { - *frame_ref.iframe().trace.lock() = None; + *frame_ref.iframe().cold().trace.lock() = None; } return Err(e); } diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index 64e5c596ba7..0a80285dbe3 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -1206,7 +1206,7 @@ pub(crate) mod _thread { let iframe = unsafe { &*cur }; let fo = iframe.materialize(vm).to_owned(); if let Some(child) = child_fo.take() { - let mut guard = child.iframe().retained_back.lock(); + let mut guard = child.iframe().cold().retained_back.lock(); if guard.is_none() { *guard = Some(fo.clone()); } @@ -1253,7 +1253,7 @@ pub(crate) mod _thread { let iframe = unsafe { &*cur }; let fo = iframe.materialize(vm).to_owned(); if let Some(child) = child_fo.take() { - let mut guard = child.iframe().retained_back.lock(); + let mut guard = child.iframe().cold().retained_back.lock(); if guard.is_none() { *guard = Some(fo.clone()); } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index a9091f895f6..a48ce56c14a 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1768,7 +1768,7 @@ impl VirtualMachine { // materialized, f_back will resolve via the TLS chain while the // caller is still executing, or return None after it returns. if strong > 1 { - let mut guard = frame.iframe().retained_back.lock(); + let mut guard = frame.iframe().cold().retained_back.lock(); if guard.is_none() { let prev_iframe = unsafe { &*old_chain }; if let Some(fo) = prev_iframe.frame_obj() { @@ -1884,7 +1884,7 @@ impl VirtualMachine { // lightweight frame has empty localsplus; live values are // read through find_live_source_iframe when needed. let back_fo = prev_iframe.materialize_chain(self); - *fo.iframe().retained_back.lock() = Some(back_fo); + *fo.iframe().cold().retained_back.lock() = Some(back_fo); } // Set owner to FrameObject since this frame is no longer // executing on a thread. @@ -1918,7 +1918,7 @@ impl VirtualMachine { crate::gc_state::gc_state() .track_object(core::ptr::NonNull::from(fo.as_object())); let live_iframe = &*iframe_ptr; - live_iframe.temporary_refs.lock().clear(); + live_iframe.cold().temporary_refs.lock().clear(); } } } @@ -2002,7 +2002,7 @@ impl VirtualMachine { // Fire 'call' trace event. current_frame() now returns the callee. let trace_result = self.trace_event(TraceEvent::Call, None)?; if let Some(local_trace) = trace_result { - *frame.iframe().trace.lock() = Some(local_trace); + *frame.iframe().cold().trace.lock() = Some(local_trace); } let result = f(frame); @@ -2011,7 +2011,11 @@ impl VirtualMachine { // PY_UNWIND fires PyTrace_RETURN with arg=None — so we fire for // both Ok and Err, matching `call_trace_protected` behavior. if self.use_tracing.get() - && (frame.iframe().trace.lock().is_some() || !self.is_none(&self.profile_func.borrow())) + && (!self.is_none(&self.profile_func.borrow()) + || frame + .iframe() + .cold_opt() + .is_some_and(|c| c.trace.lock().is_some())) { let ret_result = self.trace_event(TraceEvent::Return, None); // call_trace_protected: if trace function raises, its error