Skip to content

Commit ece8197

Browse files
committed
Fix CI failures: cspell, stale materialized state, f_back chain gaps
- Fix cspell: 'amortised' → 'amortized' - Sync lasti, prev_line, and fastlocals when re-observing a materialized light frame, fixing stale f_locals/f_lineno in test_inspect and others - In with_frame, materialize any active light frame as the heavy frame's predecessor and store it in retained_back, so f_back and inspect.stack see the correct interleaved order (H_new → L_mat → H_old) - Guard invoke_light_slots on NEWLOCALS | OPTIMIZED code flags Assisted-by: Claude
1 parent 25ffe65 commit ece8197

3 files changed

Lines changed: 76 additions & 7 deletions

File tree

crates/vm/src/builtins/function.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,11 +801,17 @@ impl Py<PyFunction> {
801801
let code: &Py<PyCode> = &self.code;
802802

803803
// Generator/coroutine code and tracing must use the heavy path
804+
// Fall back to the heavy path for generators/coroutines, tracing,
805+
// and non-optimized/non-NEWLOCALS code (e.g. types.FunctionType
806+
// with a non-standard code object).
804807
if code.flags.intersects(
805808
bytecode::CodeFlags::GENERATOR
806809
| bytecode::CodeFlags::COROUTINE
807810
| bytecode::CodeFlags::ASYNC_GENERATOR,
808811
) || vm.use_tracing.get()
812+
|| !code
813+
.flags
814+
.contains(bytecode::CodeFlags::NEWLOCALS | bytecode::CodeFlags::OPTIMIZED)
809815
{
810816
return self.invoke_exact_args_slots(args, vm);
811817
}

crates/vm/src/frame.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,12 @@ impl<'a> FrameSource<'a> {
149149
#[cold]
150150
unsafe fn materialize_light_frame(light: *mut LightFrame, vm: &VirtualMachine) -> FrameRef {
151151
unsafe {
152-
// Check if already materialized
152+
// Check if already materialized — synchronize execution state
153153
let existing = *(*light).materialized.get();
154154
if !existing.is_null() {
155-
return (&*existing).to_owned();
155+
let frame_ref: FrameRef = (&*existing).to_owned();
156+
sync_light_to_materialized(light, &frame_ref);
157+
return frame_ref;
156158
}
157159

158160
// Create owned references from borrowed pointers
@@ -241,6 +243,38 @@ unsafe fn materialize_light_frame(light: *mut LightFrame, vm: &VirtualMachine) -
241243
}
242244
}
243245

246+
/// Synchronize the live light frame's execution state into its materialized
247+
/// heavy frame. Called on every re-observation so introspection APIs
248+
/// (`f_lineno`, `f_locals`, `inspect.currentframe()`) see current values.
249+
///
250+
/// # Safety
251+
/// Both `light` and `frame` must be valid and the light frame must still be
252+
/// executing (its localsplus on the DataStack is live).
253+
#[cold]
254+
unsafe fn sync_light_to_materialized(light: *const LightFrame, frame: &Py<Frame>) {
255+
unsafe {
256+
let iframe = (&mut *frame.iframe.get()).as_mut().unwrap();
257+
// Sync lasti and prev_line
258+
let lasti_val = (*light).lasti.load(Relaxed);
259+
iframe.lasti.store(lasti_val, Relaxed);
260+
iframe.prev_line = (*light).prev_line;
261+
// Sync fastlocals (clone current values from light frame)
262+
let nlocalsplus = (*light).nlocalsplus as usize;
263+
let src_ptr = (*light).localsplus_ptr();
264+
let dst = iframe.localsplus.fastlocals_mut();
265+
for (i, slot) in dst.iter_mut().enumerate().take(nlocalsplus) {
266+
let src_val = core::ptr::read(src_ptr.add(i) as *const Option<PyObjectRef>);
267+
if let Some(ref obj) = src_val {
268+
*slot = Some(obj.clone());
269+
} else {
270+
*slot = None;
271+
}
272+
// Don't drop the source — it's still owned by the light frame
273+
core::mem::forget(src_val);
274+
}
275+
}
276+
}
277+
244278
/// Public wrapper for `materialize_light_frame`.
245279
///
246280
/// # Safety

crates/vm/src/vm/mod.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,7 +1717,7 @@ impl VirtualMachine {
17171717
// Inline recursion check (avoids with_recursion closure overhead)
17181718
self.check_recursive_call("")?;
17191719

1720-
// C stack overflow check: amortised over every 64th call to reduce
1720+
// C stack overflow check: amortized over every 64th call to reduce
17211721
// the cost of psm::stack_pointer() on the hot path.
17221722
let depth = self.recursion_depth.get();
17231723
if depth & 63 == 0 && self.check_c_stack_overflow() {
@@ -1730,11 +1730,40 @@ impl VirtualMachine {
17301730
#[cfg(all(not(unix), feature = "threading"))]
17311731
crate::vm::thread::push_thread_frame(FramePtr(NonNull::from(&*frame)));
17321732
// Link frame into the signal-safe frame chain.
1733+
// If a light frame is currently on top, materialize it so that
1734+
// f_back and inspect.stack() see the correct interleaved order
1735+
// (H_new → L_materialized → H_old instead of H_new → H_old).
1736+
let light = crate::vm::thread::get_current_light_frame();
17331737
let old_frame = crate::vm::thread::set_current_frame((&**frame) as *const Frame);
1734-
frame.previous.store(
1735-
old_frame as *mut Frame,
1736-
core::sync::atomic::Ordering::Relaxed,
1737-
);
1738+
let _materialized_back = if !light.is_null() {
1739+
let saved = unsafe { (*light).saved_current_frame };
1740+
if core::ptr::eq(old_frame, saved) {
1741+
// The top light frame belongs to this heavy call level —
1742+
// materialize and use as the predecessor.
1743+
let mat =
1744+
unsafe { crate::frame::materialize_light_frame_pub(light as *mut _, self) };
1745+
let payload = (&**mat) as *const crate::frame::Frame as *mut crate::frame::Frame;
1746+
frame
1747+
.previous
1748+
.store(payload, core::sync::atomic::Ordering::Relaxed);
1749+
// Keep the materialized frame alive via retained_back so
1750+
// f_back can find it even after the light frame's cleanup.
1751+
*frame.retained_back.lock() = Some(mat.clone());
1752+
Some(mat)
1753+
} else {
1754+
frame.previous.store(
1755+
old_frame as *mut crate::frame::Frame,
1756+
core::sync::atomic::Ordering::Relaxed,
1757+
);
1758+
None
1759+
}
1760+
} else {
1761+
frame.previous.store(
1762+
old_frame as *mut crate::frame::Frame,
1763+
core::sync::atomic::Ordering::Relaxed,
1764+
);
1765+
None
1766+
};
17381767
// Save exc_info if this frame does exception handling.
17391768
let save_exc = frame.code.has_exc_handling;
17401769
let saved_exc = if save_exc {

0 commit comments

Comments
 (0)