Split InterpreterFrame hot/cold fields into FrameColdData (#8434)

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<Option<Box<FrameColdData>>>
(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
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-08-03 16:00:04 +09:00
1 parent 12f3646e7b
commit 9dff4d3a99
7 files changed
+154 -117

No files matched your search

+25 -19
View File
@@ -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<FrameObject> {
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<FrameObject> {
}
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<FrameObject> {
}
// 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<FrameObject> {
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());
}
+6 -1
View File
@@ -1513,7 +1513,12 @@ impl PyType {
// temporary refs so they never see a dangling pointer.
let keep_alive = |type_ref: PyTypeRef, retired: &mut Vec<PyObjectRef>| {
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());
}
+101 -84
View File
@@ -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<Option<PyObjectRef>>,
pub trace_lines: PyMutex<bool>,
pub trace_opcodes: PyMutex<bool>,
pub temporary_refs: PyMutex<Vec<PyObjectRef>>,
pub f_locals_hidden_overlay: PyMutex<Option<PyDictRef>>,
pub f_extra_locals: PyMutex<Option<PyDictRef>>,
pub escaped: atomic::AtomicBool,
pub retained_back: PyMutex<Option<FrameObjectRef>>,
pub pending_stack_pops: PyAtomic<u32>,
pub pending_unwind_from_stack: PyAtomic<i64>,
}
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<u32>,
/// 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<Option<PyObjectRef>>,
/// Previous line number for LINE event suppression.
pub(crate) prev_line: core::cell::Cell<u32>,
// member
pub trace_lines: PyMutex<bool>,
pub trace_opcodes: PyMutex<bool>,
pub temporary_refs: PyMutex<Vec<PyObjectRef>>,
/// 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<Option<PyDictRef>>,
/// 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<Option<PyDictRef>>,
/// 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<Option<FrameObjectRef>>,
/// 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<u32>,
/// 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<i64>,
/// Pointer to the owning `Py<FrameObject>`, or null for stack-allocated
/// frames that have not been materialized yet.
/// Stored as `usize` for `PyAtomic` compatibility.
pub(crate) materialized: PyAtomic<usize>,
/// Lazily-allocated cold data (tracing, debugging, frame inspection).
/// Not allocated until first access via `cold()`.
pub(crate) cold: OnceCell<Box<FrameColdData>>,
}
// 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<PyObjectRef>) {
@@ -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<ArgMapping> {
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<FrameObject>, 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
+4 -4
View File
@@ -333,7 +333,7 @@ impl<T: PyPayload> PyAtomicRef<T> {
pub fn swap_to_temporary_refs(&self, pyref: PyRef<T>, 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<T: PyPayload> PyAtomicRef<Option<T>> {
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<PyObject> {
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<Option<PyObject>> {
return;
};
if let Some(frame) = vm.current_frame() {
frame.iframe().temporary_refs.lock().push(old);
frame.iframe().cold().temporary_refs.lock().push(old);
}
}
}
+7 -2
View File
@@ -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);
}
+2 -2
View File
@@ -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());
}
+9 -5
View File
@@ -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