GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — Split InterpreterFrame hot/cold fields into FrameColdData by youknowone · Pull Request #8434 · RustPython/RustPython Conversation
|
📝 WalkthroughWalkthrough
ChangesFrame cold-state storage
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This PR optimizes call overhead by moving rarely-used tracing/debug/inspection state out of InterpreterFrame into a lazily-initialized “cold” allocation (FrameColdData), aiming to reduce per-call frame initialization costs while still supporting tracing, frame inspection, and GC traversal when needed.
Changes:
FrameColdData and routes tracing/inspection-related fields through InterpreterFrame::cold().f_trace*, f_back, temporary ref handling, retained backrefs) to use the cold storage.Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| crates/vm/src/frame.rs | Adds FrameColdData, migrates fields into lazy cold storage, updates traversal and frame APIs accordingly. |
| crates/vm/src/builtins/frame.rs | Updates Python-visible frame attribute accessors and clear/backref logic to read/write cold fields. |
| crates/vm/src/object/ext.rs | Routes swap_to_temporary_refs() to store old refs in cold temporary_refs. |
| crates/vm/src/protocol/callable.rs | Updates tracing dispatch logic to consult cold trace_opcodes / clear cold trace. |
| crates/vm/src/vm/mod.rs | Updates retained-back capture, temporary-ref cleanup, and trace event handling to use cold fields. |
| crates/vm/src/builtins/type.rs | Updates __bases__ setter keep-alive path to use cold temporary_refs. |
| crates/vm/src/stdlib/_thread.rs | Updates cross-thread frame materialization backref retention to use cold retained_back. |
crates/vm/src/frame.rs:1582
has_escaped() currently calls cold(), which forces allocation of FrameColdData even for the common case where nothing escaped (e.g. coroutine/generator close checks this routinely). That undermines the stated goal of paying zero allocation cost when tracing/inspection isn’t used.has_escaped() can treat “cold not allocated yet” as false and avoid allocating/locking in the hot path.
pub(crate) fn has_escaped(&self) -> bool {
self.iframe().cold().escaped.load(atomic::Ordering::Acquire)
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| // 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().trace_opcodes.lock() { | ||
| return Ok(None); | ||
| } |
| if self.use_tracing.get() | ||
| && (frame.iframe().trace.lock().is_some() || !self.is_none(&self.profile_func.borrow())) | ||
| && (frame.iframe().cold().trace.lock().is_some() || !self.is_none(&self.profile_func.borrow())) | ||
| { |
| pub(crate) fn cold(&self) -> &FrameColdData { | ||
| let ptr = self.cold.get(); | ||
| unsafe { (*ptr).get_or_insert_with(|| Box::new(FrameColdData::default())) } | ||
| } |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/vm/src/frame.rs`:
- Around line 1164-1170: Update InterpreterFrame::cold to replace the
UnsafeCell-based lazy initialization with
std::sync::OnceLock<Box<FrameColdData>>. Initialize the OnceLock through its
thread-safe one-time initialization API and return the stored FrameColdData
reference, preserving default allocation on first use and ensuring concurrent
callers share a single allocation.
Fix all unresolved CodeRabbit comments on this PR:
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro Plus
Run ID: 224ac896-8746-45d3-acd9-99e6b4d3580d
crates/vm/src/builtins/frame.rscrates/vm/src/builtins/type.rscrates/vm/src/frame.rscrates/vm/src/object/ext.rscrates/vm/src/protocol/callable.rscrates/vm/src/stdlib/_thread.rscrates/vm/src/vm/mod.rs
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
896cca5 to
e48d496
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
crates/vm/src/frame.rs:1173
InterpreterFrame::cold() performs unsynchronized lazy initialization via UnsafeCell<Option<...>>. Under feature="threading", InterpreterFrame is Send + Sync (see unsafe impl Send/Sync for InterpreterFrame in this file), and FrameObject APIs like frame.clear() explicitly support being called from a different thread. That combination means two threads can race to initialize cold, which is UB (data race) even though the fields inside FrameColdData are mutex/atomic protected.Consider switching cold to a thread-safe one-time init primitive already used in this repo (e.g. rustpython_common::lock::OnceCell/OnceLock) so initialization is race-free, and adjust the GC traverse path to use cold.get() instead of raw pointer reads.
/// Access the lazily-allocated cold data, allocating on first use.
#[inline]
pub(crate) fn cold(&self) -> &FrameColdData {
let ptr = self.cold.get();
unsafe { (*ptr).get_or_insert_with(|| Box::new(FrameColdData::default())) }
}
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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
e48d496 to
ff53f27
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔥
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
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
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.