Skip to content

Commit 896cca5

Browse files
committed
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<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
1 parent 12f3646 commit 896cca5

7 files changed

Lines changed: 112 additions & 117 deletions

File tree

‎crates/vm/src/builtins/frame.rs‎

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,8 @@ impl FrameObject {
636636
} else {
637637
self.iframe()
638638
};
639-
target.pending_stack_pops.store(pop_count as u32, Relaxed);
640-
target.pending_unwind_from_stack.store(start_stack, Relaxed);
639+
target.cold().pending_stack_pops.store(pop_count as u32, Relaxed);
640+
target.cold().pending_unwind_from_stack.store(start_stack, Relaxed);
641641
target.lasti.store(best_addr as u32, Relaxed);
642642
Ok(())
643643
}
@@ -647,9 +647,9 @@ impl FrameObject {
647647
// Read from live source iframe if available.
648648
let live = self.find_live_source_iframe();
649649
let trace = if !live.is_null() {
650-
unsafe { &*live }.trace.lock().clone()
650+
unsafe { &*live }.cold().trace.lock().clone()
651651
} else {
652-
self.iframe().trace.lock().clone()
652+
self.iframe().cold().trace.lock().clone()
653653
};
654654
trace.unwrap_or_else(|| vm.ctx.none())
655655
}
@@ -667,13 +667,13 @@ impl FrameObject {
667667
PySetterValue::Delete => None,
668668
};
669669
// Set on the materialized FrameObject.
670-
(*self.iframe().trace.lock()).clone_from(&trace);
670+
(*self.iframe().cold().trace.lock()).clone_from(&trace);
671671
// Also propagate to the live source iframe if this is a
672672
// materialized copy of a stack-allocated frame, so pdb's
673673
// f_trace assignment takes effect on the executing frame.
674674
let live = self.find_live_source_iframe();
675675
if !live.is_null() {
676-
*unsafe { &*live }.trace.lock() = trace;
676+
*unsafe { &*live }.cold().trace.lock() = trace;
677677
}
678678
}
679679

@@ -682,7 +682,7 @@ impl FrameObject {
682682
fn f_trace_lines(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
683683
let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!());
684684

685-
let boxed = zelf.iframe().trace_lines.lock();
685+
let boxed = zelf.iframe().cold().trace_lines.lock();
686686
Ok(vm.ctx.new_bool(*boxed).into())
687687
}
688688

@@ -701,11 +701,11 @@ impl FrameObject {
701701
.map_err(|_| vm.new_type_error("attribute value type must be bool"))?;
702702

703703
let val = !value.as_bigint().is_zero();
704-
*zelf.iframe().trace_lines.lock() = val;
704+
*zelf.iframe().cold().trace_lines.lock() = val;
705705
// Propagate to live source iframe.
706706
let live = zelf.find_live_source_iframe();
707707
if !live.is_null() {
708-
*unsafe { &*live }.trace_lines.lock() = val;
708+
*unsafe { &*live }.cold().trace_lines.lock() = val;
709709
}
710710

711711
Ok(())
@@ -718,7 +718,7 @@ impl FrameObject {
718718
#[pymember(type = "bool")]
719719
fn f_trace_opcodes(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
720720
let zelf: FrameObjectRef = zelf.downcast().unwrap_or_else(|_| unreachable!());
721-
let trace_opcodes = zelf.iframe().trace_opcodes.lock();
721+
let trace_opcodes = zelf.iframe().cold().trace_opcodes.lock();
722722
Ok(vm.ctx.new_bool(*trace_opcodes).into())
723723
}
724724

@@ -737,11 +737,11 @@ impl FrameObject {
737737
.map_err(|_| vm.new_type_error("attribute value type must be bool"))?;
738738

739739
let val = !value.as_bigint().is_zero();
740-
*zelf.iframe().trace_opcodes.lock() = val;
740+
*zelf.iframe().cold().trace_opcodes.lock() = val;
741741
// Propagate to live source iframe.
742742
let live = zelf.find_live_source_iframe();
743743
if !live.is_null() {
744-
*unsafe { &*live }.trace_opcodes.lock() = val;
744+
*unsafe { &*live }.cold().trace_opcodes.lock() = val;
745745
}
746746

747747
// TODO: Implement the equivalent of _PyEval_SetOpcodeTrace()
@@ -798,10 +798,10 @@ impl Py<FrameObject> {
798798
self.clear_stack_and_cells();
799799

800800
// Clear temporary refs
801-
self.iframe().temporary_refs.lock().clear();
802-
self.iframe().f_locals_hidden_overlay.lock().take();
803-
self.iframe().f_extra_locals.lock().take();
804-
self.iframe().retained_back.lock().take();
801+
self.iframe().cold().temporary_refs.lock().clear();
802+
self.iframe().cold().f_locals_hidden_overlay.lock().take();
803+
self.iframe().cold().f_extra_locals.lock().take();
804+
self.iframe().cold().retained_back.lock().take();
805805

806806
Ok(())
807807
}
@@ -853,7 +853,7 @@ impl Py<FrameObject> {
853853
}
854854
if prev.is_null() {
855855
// Check retained_back for frames whose callers have returned
856-
let retained = self.iframe().retained_back.lock().clone();
856+
let retained = self.iframe().cold().retained_back.lock().clone();
857857
if let Some(frame) = retained {
858858
frame.mark_escaped();
859859
return Some(frame);
@@ -879,7 +879,7 @@ impl Py<FrameObject> {
879879
}
880880

881881
// The caller already returned — check retained_back
882-
let retained = self.iframe().retained_back.lock().clone();
882+
let retained = self.iframe().cold().retained_back.lock().clone();
883883
if let Some(frame) = retained {
884884
frame.mark_escaped();
885885
return Some(frame);
@@ -906,7 +906,7 @@ impl Py<FrameObject> {
906906
let iframe = unsafe { &*cur };
907907
let fo = iframe.materialize(vm).to_owned();
908908
if let Some(child) = child_fo.take() {
909-
let mut guard = child.iframe().retained_back.lock();
909+
let mut guard = child.iframe().cold().retained_back.lock();
910910
if guard.is_none() {
911911
*guard = Some(fo.clone());
912912
}

‎crates/vm/src/builtins/type.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1513,7 +1513,7 @@ impl PyType {
15131513
// temporary refs so they never see a dangling pointer.
15141514
let keep_alive = |type_ref: PyTypeRef, retired: &mut Vec<PyObjectRef>| {
15151515
if let Some(frame) = vm.current_frame() {
1516-
frame.iframe().temporary_refs.lock().push(type_ref.into());
1516+
frame.iframe().cold().temporary_refs.lock().push(type_ref.into());
15171517
} else {
15181518
retired.push(type_ref.into());
15191519
}

0 commit comments

Comments
 (0)