From e18facff3ecbdf24745f7f36689fd6eb798e7a0e Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Sun, 13 Sep 2026 21:18:22 +0900 Subject: [PATCH] Keep escaped generator frames and wrap asyncgen throw (#8697) * Keep escaped generator frames and wrap asyncgen throw Mark gi_frame/cr_frame/ag_frame as escaped so locals survive deallocation, and let frame.clear() drop those locals after owner finalize. Route throw() through finalize_send_result so StopAsyncIteration is wrapped like send(). Add ag_suspended and drop never-started frame locals on close(). Assisted-by: Grok:grok-4.6 * Store escaped on iframe; skip unused cold alloc Keep the escaped flag on InterpreterFrame so mark_escaped and has_escaped do not allocate FrameColdData. Clear optional cold fields only when they exist. Transfer frame owner in close() the same way send/throw already do. Assisted-by: Grok:grok-4.6 * Replace escaped flag with take_ownership gi_frame returns the frame object. Close uses a uniquely-referenced check to clear locals or take_ownership onto a husk. frame.clear() finalizes a generator-owned frame and only clears FRAME_OBJECT frames. close() leaves the generator suspended when it yields on GeneratorExit. Assisted-by: Grok:grok-4.6 * Drop frame mutex; steal frame_obj atomically take_ownership stores None in an atomic slot instead of locking and swapping a husk. gi_code reads the kept executable. Resume exclusivity stays on the running claim. Assisted-by: Grok:grok-4.6 --- Lib/test/test_contextlib_async.py | 1 - Lib/test/test_generators.py | 2 - Lib/test/test_inspect/test_inspect.py | 5 - crates/vm/src/builtins/asyncgenerator.rs | 16 ++-- crates/vm/src/builtins/coroutine.rs | 12 ++- crates/vm/src/builtins/frame.rs | 51 ++++------ crates/vm/src/builtins/generator.rs | 16 ++-- crates/vm/src/coroutine.rs | 115 ++++++++++++++--------- crates/vm/src/frame.rs | 48 +++------- crates/vm/src/stdlib/sys.rs | 2 - 10 files changed, 121 insertions(+), 147 deletions(-) diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 6541043bd..dcd007203 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -130,7 +130,6 @@ class AsyncContextManagerTestCase(unittest.TestCase): raise ZeroDivisionError() self.assertEqual(state, [1, 42, 999]) - @unittest.expectedFailure # TODO: RUSTPYTHON @_async_test async def test_contextmanager_traceback(self): @asynccontextmanager diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 07c1decb4..cf3288b2c 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -302,7 +302,6 @@ class GeneratorTest(unittest.TestCase): self.assertEqual([1, 2], list(i for i in C())) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true def test_close_clears_frame(self): # gh-142766: Test that closing a generator clears its frame class DetectDelete: @@ -721,7 +720,6 @@ class GeneratorCloseTest(unittest.TestCase): # See https://github.com/python/cpython/issues/125723 class GeneratorDeallocTest(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON; frame uses shared Arc, no ownership transfer def test_frame_outlives_generator(self): def g1(): a = 42 diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index cb887a030..765267c91 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -2930,17 +2930,14 @@ class TestGetAsyncGenState(unittest.IsolatedAsyncioTestCase): def _asyncgenstate(self): return inspect.getasyncgenstate(self.asyncgen) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' def test_created(self): self.assertEqual(self._asyncgenstate(), inspect.AGEN_CREATED) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_suspended(self): value = await anext(self.asyncgen) self.assertEqual(self._asyncgenstate(), inspect.AGEN_SUSPENDED) self.assertEqual(value, 0) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_closed_after_exhaustion(self): countdown = 7 with self.assertRaises(StopAsyncIteration): @@ -2949,13 +2946,11 @@ class TestGetAsyncGenState(unittest.IsolatedAsyncioTestCase): self.assertEqual(countdown, 1) self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_closed_after_immediate_exception(self): with self.assertRaises(RuntimeError): await self.asyncgen.athrow(RuntimeError) self.assertEqual(self._asyncgenstate(), inspect.AGEN_CLOSED) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'async_generator' object has no attribute 'ag_suspended' async def test_running(self): async def running_check_asyncgen(): for number in range(5): diff --git a/crates/vm/src/builtins/asyncgenerator.rs b/crates/vm/src/builtins/asyncgenerator.rs index 0ca56e3aa..cc2f9bae0 100644 --- a/crates/vm/src/builtins/asyncgenerator.rs +++ b/crates/vm/src/builtins/asyncgenerator.rs @@ -132,14 +132,14 @@ impl PyAsyncGen { #[pygetset] fn ag_await(&self, _vm: &VirtualMachine) -> Option { - self.inner.frame().yield_from_target() + self.inner.frame_opt().and_then(|f| f.yield_from_target()) } #[pygetset] fn ag_frame(&self, _vm: &VirtualMachine) -> Option { if self.inner.closed() { None } else { - Some(self.inner.frame()) + self.inner.frame_opt() } } #[pygetset] @@ -148,7 +148,11 @@ impl PyAsyncGen { } #[pygetset] fn ag_code(&self, _vm: &VirtualMachine) -> PyRef { - self.inner.frame().iframe().code().to_owned() + self.inner.code() + } + #[pygetset] + fn ag_suspended(&self, _vm: &VirtualMachine) -> bool { + self.inner.suspended() } #[pyclassmethod] @@ -718,8 +722,6 @@ impl PyAnextAwaitable { if let Some(generator) = wrapped.downcast_ref::() && generator .as_coro() - .frame() - .iframe() .code() .flags .contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE) @@ -841,7 +843,9 @@ impl Destructor for PyAsyncGen { impl Drop for PyAsyncGen { fn drop(&mut self) { - self.inner.frame().clear_generator(); + if let Some(frame) = self.inner.frame_opt() { + frame.clear_generator(); + } } } diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 90c35f636..c61103c7b 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -89,14 +89,14 @@ impl PyCoroutine { #[pygetset] fn cr_await(&self, _vm: &VirtualMachine) -> Option { - self.inner.frame().yield_from_target() + self.inner.frame_opt().and_then(|f| f.yield_from_target()) } #[pygetset] fn cr_frame(&self, _vm: &VirtualMachine) -> Option { if self.inner.closed() { None } else { - Some(self.inner.frame()) + self.inner.frame_opt() } } #[pygetset] @@ -105,7 +105,7 @@ impl PyCoroutine { } #[pygetset] fn cr_code(&self, _vm: &VirtualMachine) -> PyRef { - self.inner.frame().iframe().code().to_owned() + self.inner.code() } #[pygetset] fn cr_origin(&self, _vm: &VirtualMachine) -> Option { @@ -179,7 +179,7 @@ impl Destructor for PyCoroutine { if zelf.inner.closed() || zelf.inner.running() { return Ok(()); } - if zelf.inner.frame().lasti() == 0 { + if zelf.inner.frame_opt().is_none_or(|f| f.lasti() == 0) { crate::warn::warn_unawaited_coroutine(zelf.as_object(), &zelf.inner.qualname(), vm); zelf.inner.closed.store(true); return Ok(()); @@ -266,7 +266,9 @@ impl IterNext for PyCoroutineWrapper { impl Drop for PyCoroutine { fn drop(&mut self) { - self.inner.frame().clear_generator(); + if let Some(frame) = self.inner.frame_opt() { + frame.clear_generator(); + } } } diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 34713f6ee..3bc259af0 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -2,7 +2,7 @@ */ -use super::{PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyIntRef, PyStrRef}; +use super::{PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyIntRef, PyStrRef}; use crate::{ Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, class::PyClassImpl, @@ -778,10 +778,8 @@ impl Py { ); match owner { FrameOwner::Generator => { - // Generator frame: check if suspended (lasti > 0 means - // FRAME_SUSPENDED). lasti == 0 means FRAME_CREATED and - // can be cleared. Finalize the owner so a never-started - // coroutine emits its never-awaited warning. + // FRAME_SUSPENDED (lasti > 0) cannot be cleared. FRAME_CREATED + // and finished frames go through the owner finalizer. if self.lasti() != 0 { return Err(vm.new_runtime_error("cannot clear a suspended frame")); } @@ -790,17 +788,16 @@ impl Py { let _ = PyCoroutine::del(coro, vm); } else if let Some(async_gen) = owner.downcast_ref::() { let _ = PyAsyncGen::del(async_gen, vm); + } else if let Some(generator) = owner.downcast_ref::() { + let _ = PyGenerator::del(generator, vm); } } return Ok(()); } FrameOwner::Thread => { - // Thread-owned frame: always executing, cannot clear. return Err(vm.new_runtime_error("cannot clear an executing frame")); } FrameOwner::FrameObject => { - // Check if this materialized frame is backed by a live - // stack-allocated iframe — if so, the frame is executing. if !self.find_live_source_iframe().is_null() { return Err(vm.new_runtime_error("cannot clear an executing frame")); } @@ -822,27 +819,17 @@ impl Py { // Clear the evaluation stack and cell references self.clear_stack_and_cells(); - let cold = self.iframe().cold(); - let temporary_refs = { - let mut guard = cold.temporary_refs.lock(); - core::mem::take(&mut *guard) - }; - let extra_locals = { - let mut guard = cold.f_extra_locals.lock(); - guard.take() - }; - let locals_cache = { - let mut guard = cold.f_locals_cache.lock(); - guard.take() - }; - let overwritten = { - let mut guard = cold.f_overwritten_fast_locals.lock(); - core::mem::take(&mut *guard) - }; - let retained_back = { - let mut guard = cold.retained_back.lock(); - guard.take() - }; + let (temporary_refs, extra_locals, locals_cache, overwritten, retained_back) = + match self.iframe().cold_opt() { + Some(cold) => ( + core::mem::take(&mut *cold.temporary_refs.lock()), + cold.f_extra_locals.lock().take(), + cold.f_locals_cache.lock().take(), + core::mem::take(&mut *cold.f_overwritten_fast_locals.lock()), + cold.retained_back.lock().take(), + ), + None => (Vec::new(), None, None, Vec::new(), None), + }; drop(( fastlocals, temporary_refs, @@ -858,7 +845,6 @@ impl Py { #[pygetset] fn f_locals(&self, vm: &VirtualMachine) -> PyResult { if self.uses_locals_proxy(vm)? { - self.mark_escaped(); let proxy = crate::builtins::FrameLocalsProxy::new(self.to_owned()); Ok(proxy.into_ref(&vm.ctx).into()) } else { @@ -895,7 +881,6 @@ impl Py { // Check retained_back for frames whose callers have returned let retained = self.iframe().cold().retained_back.lock().clone(); if let Some(frame) = retained { - frame.mark_escaped(); return Some(frame); } return None; @@ -911,7 +896,6 @@ impl Py { if core::ptr::eq(cur, prev) { let iframe_ref = unsafe { &*cur }; let fo = iframe_ref.materialize(vm); - fo.mark_escaped(); return Some(fo.to_owned()); } cur = unsafe { (*cur).previous() }; @@ -921,7 +905,6 @@ impl Py { // The caller already returned — check retained_back let retained = self.iframe().cold().retained_back.lock().clone(); if let Some(frame) = retained { - frame.mark_escaped(); return Some(frame); } @@ -936,13 +919,11 @@ impl Py { let prev_ref = unsafe { &*prev }; // Fast path: already materialized. if let Some(fo) = prev_ref.frame_obj() { - fo.mark_escaped(); return Some(fo.to_owned()); } // Slow path: copy the whole chain, linked through retained_back. // SAFETY: the world is stopped, so the owning thread is parked. let fo = unsafe { prev_ref.materialize_detached_chain(vm) }; - fo.mark_escaped(); return Some(fo); } diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index b06a3a45e..9f99a8378 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -74,7 +74,7 @@ impl PyGenerator { if self.inner.closed() { None } else { - Some(self.inner.frame()) + self.inner.frame_opt() } } @@ -85,12 +85,12 @@ impl PyGenerator { #[pygetset] fn gi_code(&self, _vm: &VirtualMachine) -> PyRef { - self.inner.frame().iframe().code().to_owned() + self.inner.code() } #[pygetset] fn gi_yieldfrom(&self, _vm: &VirtualMachine) -> Option { - self.inner.frame().yield_from_target() + self.inner.frame_opt().and_then(|f| f.yield_from_target()) } #[pygetset] @@ -159,12 +159,6 @@ impl Destructor for PyGenerator { if zelf.inner.closed() || zelf.inner.running() { return Ok(()); } - // Generator was never started, just mark as closed - if zelf.inner.frame().lasti() == 0 { - zelf.inner.closed.store(true); - return Ok(()); - } - // Throw GeneratorExit to run finally blocks if let Err(e) = zelf.inner.close(zelf.as_object(), vm) { vm.run_unraisable(e, None, zelf.as_object().to_owned()); } @@ -174,7 +168,9 @@ impl Destructor for PyGenerator { impl Drop for PyGenerator { fn drop(&mut self) { - self.inner.frame().clear_generator(); + if let Some(frame) = self.inner.frame_opt() { + frame.clear_generator(); + } } } diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index 81004f155..1fde7c6f5 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -1,6 +1,6 @@ use crate::{ - AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyStrRef, PyTupleRef}, + AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, + builtins::{PyCode, PyStrRef, PyTupleRef}, common::lock::PyMutex, exceptions::types::PyBaseException, frame::{ExecutionResult, FrameObject, FrameObjectRef, FrameOwner, InterpreterFrame}, @@ -30,10 +30,13 @@ impl ExecutionResult { #[derive(Debug)] pub struct Coro { - frame: FrameObjectRef, + /// Generator iframe, like `gi_iframe`. Cleared to `None` by take_ownership + /// the same way `frame_obj` is stolen from the iframe. + frame: PyAtomicRef>, + /// `f_executable`; survives `_PyFrame_ClearExceptCode`. + code: PyRef, pub closed: AtomicCell, // TODO: https://github.com/RustPython/RustPython/pull/3183#discussion_r720560652 running: AtomicCell, - // code // _weakreflist name: PyMutex, qualname: PyMutex, @@ -42,7 +45,10 @@ pub struct Coro { unsafe impl Traverse for Coro { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { - self.frame.traverse(tracer_fn); + if let Some(frame) = self.frame.deref() { + tracer_fn(frame.as_object()); + } + self.code.traverse(tracer_fn); self.name.traverse(tracer_fn); self.qualname.traverse(tracer_fn); if let Some(exc) = self.exception.deref() { @@ -79,8 +85,10 @@ fn gen_name(jen: &PyObject, vm: &VirtualMachine) -> &'static str { impl Coro { pub fn new(frame: FrameObjectRef, name: PyStrRef, qualname: PyStrRef) -> Self { + let code = frame.iframe().code().to_owned(); Self { - frame, + frame: Some(frame).into(), + code, closed: AtomicCell::new(false), running: AtomicCell::new(false), exception: PyAtomicRef::from(None), @@ -89,20 +97,32 @@ impl Coro { } } - /// Free the finished frame's locals and stack, unless a frame object has - /// escaped (e.g. through an `f_locals` proxy or `sys._getframe`). An - /// escaped frame husk owns its heap-resident locals and must keep them - /// readable after the generator closes. - fn clear_frame_locals_on_close(&self) { - // Keep locals alive if a durable frame reference escaped (e.g. through - // an `f_locals` proxy or `sys._getframe`): that reference now owns the - // heap-resident locals and must keep them readable after close, - // matching `take_ownership`. - if !self.frame.has_escaped() { - self.frame.clear_locals_and_stack(); + /// `_PyFrame_ClearExceptCode`. Clear locals unless another reference + /// still holds the frame object; then take_ownership instead. + fn clear_except_code(&self) { + let Some(frame) = self.frame.deref() else { + return; + }; + if frame.as_object().strong_count() == 1 { + frame.clear_locals_and_stack(); + } else { + self.take_ownership(); } } + /// Steal the iframe's `frame_obj` pointer (`take_ownership`). The live + /// locals stay on that frame object; this generator no longer roots it. + fn take_ownership(&self) { + let Some(frame) = (unsafe { self.frame.swap(None) }) else { + return; + }; + frame.iframe().owner.store( + FrameOwner::FrameObject as i8, + core::sync::atomic::Ordering::Release, + ); + frame.clear_generator(); + } + /// Retire the generator if the frame it just ran came to an end. The claim /// is still held, so a thread waiting for it cannot resume a frame that has /// already finished. @@ -110,14 +130,7 @@ impl Coro { match res { Ok(ExecutionResult::Return(_)) | Err(_) => { self.closed.store(true); - // FrameObject is no longer suspended; allow frame.clear() to succeed. - self.frame.iframe().owner.store( - FrameOwner::FrameObject as i8, - core::sync::atomic::Ordering::Release, - ); - // Completed generators/coroutines should not keep their locals - // alive while the wrapper object itself remains referenced. - self.clear_frame_locals_on_close(); + self.clear_except_code(); } Ok(ExecutionResult::Yield(_)) => {} Ok(ExecutionResult::TailCall) => unreachable!("TailCall in generator/coroutine"), @@ -148,7 +161,8 @@ impl Coro { let gen_exc = unsafe { self.exception.swap(None) }; let exception_ptr = &self.exception as *const PyAtomicRef>; - vm.resume_gen_frame(&self.frame, gen_exc, |f| { + let frame = self.frame(); + vm.resume_gen_frame(&frame, gen_exc, |f| { let result = func(f); // SAFETY: exclusive access guaranteed by the claim let _old = unsafe { (*exception_ptr).swap(vm.current_exception()) }; @@ -168,8 +182,8 @@ impl Coro { if e.fast_isinstance(vm.ctx.exceptions.stop_iteration) { let err = vm.new_runtime_error(format!("{} raised StopIteration", gen_name(jen, vm))); - // PEP 479: chain __context__ as well as __cause__ to match - // CPython, which sets both to the original StopIteration. + // PEP 479: chain __context__ as well as __cause__ to the + // original StopIteration. err.set___context__(Some(e.clone())); err.set___cause__(Some(e)); Err(err) @@ -196,7 +210,7 @@ impl Coro { if self.closed.load() { return Self::send_when_closed(jen, vm); } - let value = if self.frame.lasti() > 0 { + let value = if self.frame_opt().is_some_and(|f| f.lasti() > 0) { Some(vm.ctx.none()) } else { None @@ -243,7 +257,7 @@ impl Coro { if self.closed.load() { return Self::send_when_closed(jen, vm); } - let value = if self.frame.lasti() > 0 { + let value = if self.frame_opt().is_some_and(|f| f.lasti() > 0) { Some(value) } else if !vm.is_none(&value) { return Err(vm.new_type_error(format!( @@ -291,7 +305,7 @@ impl Coro { let result = self.run_claimed(&claim, vm, |f| f.gen_throw(vm, exc_type, exc_val, exc_tb)); self.maybe_close(&result, &claim); drop(claim); - Ok(result?.into_iter_return(vm)) + self.finalize_send_result(result, jen, vm) } pub fn close(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult { @@ -303,9 +317,10 @@ impl Coro { if self.closed.load() { return Ok(vm.ctx.none()); } - // If generator hasn't started (FRAME_CREATED), just mark as closed - if self.frame.lasti() == 0 { + // FRAME_CREATED: mark finished and clear the iframe. + if self.frame_opt().is_none_or(|f| f.lasti() == 0) { self.closed.store(true); + self.clear_except_code(); return Ok(vm.ctx.none()); } let result = self.run_claimed(&claim, vm, |f| { @@ -316,23 +331,27 @@ impl Coro { vm.ctx.none(), ) }); - self.closed.store(true); - // Release frame locals and stack to free references held by the - // closed generator, matching gen_send_ex2 with close_on_completion. - self.clear_frame_locals_on_close(); drop(claim); match result { Ok(ExecutionResult::Yield(_)) => { Err(vm.new_runtime_error(format!("{} ignored GeneratorExit", gen_name(jen, vm)))) } - Err(e) if !is_gen_exit(&e, vm) => Err(e), - Ok(ExecutionResult::Return(value)) => Ok(value), - _ => Ok(vm.ctx.none()), + other => { + self.closed.store(true); + self.clear_except_code(); + match other { + Err(e) if !is_gen_exit(&e, vm) => Err(e), + Ok(ExecutionResult::Return(value)) => Ok(value), + _ => Ok(vm.ctx.none()), + } + } } } pub fn suspended(&self) -> bool { - !self.closed.load() && !self.running.load() && self.frame.lasti() > 0 + !self.closed.load() + && !self.running.load() + && self.frame_opt().is_some_and(|f| f.lasti() > 0) } pub fn running(&self) -> bool { @@ -344,7 +363,15 @@ impl Coro { } pub fn frame(&self) -> FrameObjectRef { - self.frame.clone() + self.frame_opt().expect("generator frame") + } + + pub fn frame_opt(&self) -> Option { + self.frame.to_owned() + } + + pub fn code(&self) -> PyRef { + self.code.clone() } pub fn name(&self) -> PyStrRef { @@ -434,8 +461,6 @@ pub(crate) fn get_awaitable_iter(obj: PyObjectRef, vm: &VirtualMachine) -> PyRes if obj.downcastable::() || obj.downcast_ref::().is_some_and(|g| { g.as_coro() - .frame() - .iframe() .code() .flags .contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE) @@ -450,8 +475,6 @@ pub(crate) fn get_awaitable_iter(obj: PyObjectRef, vm: &VirtualMachine) -> PyRes if result.downcastable::() || result.downcast_ref::().is_some_and(|g| { g.as_coro() - .frame() - .iframe() .code() .flags .contains(crate::bytecode::CodeFlags::ITERABLE_COROUTINE) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 9600a92d9..282b29649 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -820,7 +820,6 @@ pub(crate) struct FrameColdData { /// `PyEval_GetLocals()` C API. It is populated only by that adapter. pub f_locals_cache: PyMutex>, pub f_overwritten_fast_locals: PyMutex>, - pub escaped: atomic::AtomicBool, pub retained_back: PyMutex>, pub pending_stack_pops: PyAtomic, pub pending_unwind_from_stack: PyAtomic, @@ -842,7 +841,6 @@ impl Default for FrameColdData { f_extra_locals: PyMutex::new(None), f_locals_cache: PyMutex::new(None), f_overwritten_fast_locals: PyMutex::new(Vec::new()), - escaped: atomic::AtomicBool::new(false), retained_back: PyMutex::new(None), pending_stack_pops: Default::default(), pending_unwind_from_stack: Default::default(), @@ -1243,7 +1241,6 @@ impl InterpreterFrame { datastack_base: core::ptr::null_mut(), materialized: Radium::new(0), cold: OnceCell::from(Box::new(FrameColdData { - escaped: atomic::AtomicBool::new(true), attached_tid: atomic::AtomicU64::new(current_thread_ident()), ..FrameColdData::default() })), @@ -1323,10 +1320,7 @@ impl InterpreterFrame { owner: atomic::AtomicI8::new(FrameOwner::FrameObject as i8), datastack_base: core::ptr::null_mut(), materialized: Radium::new(0), - cold: OnceCell::from(Box::new(FrameColdData { - escaped: atomic::AtomicBool::new(true), - ..FrameColdData::default() - })), + cold: OnceCell::new(), }; let frame_obj = FrameObject { @@ -1770,18 +1764,13 @@ impl FrameObject { .filter_map(Option::take) .collect::>() }; - let cold = self.iframe().cold(); - let extra_locals = { - let mut guard = cold.f_extra_locals.lock(); - guard.take() - }; - let locals_cache = { - let mut guard = cold.f_locals_cache.lock(); - guard.take() - }; - let overwritten = { - let mut guard = cold.f_overwritten_fast_locals.lock(); - core::mem::take(&mut *guard) + let (extra_locals, locals_cache, overwritten) = match self.iframe().cold_opt() { + Some(cold) => ( + cold.f_extra_locals.lock().take(), + cold.f_locals_cache.lock().take(), + core::mem::take(&mut *cold.f_overwritten_fast_locals.lock()), + ), + None => (None, None, Vec::new()), }; drop((fastlocals, extra_locals, locals_cache, overwritten)); } @@ -1853,21 +1842,6 @@ impl FrameObject { core::ptr::null() } - /// Record that a durable Python-level reference to this frame escaped. - pub(crate) fn mark_escaped(&self) { - 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() - .cold_opt() - .is_some_and(|c| c.escaped.load(atomic::Ordering::Acquire)) - } - pub fn lasti(&self) -> u32 { self.iframe().lasti.load(Relaxed) } @@ -4152,7 +4126,11 @@ impl ExecutingFrame<'_> { // Check if coroutine is already being awaited if let Some(coro) = iter.downcast_ref::() - && coro.as_coro().frame().yield_from_target().is_some() + && coro + .as_coro() + .frame_opt() + .and_then(|f| f.yield_from_target()) + .is_some() { return Err(vm.new_runtime_error("coroutine is being awaited already")); } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 166f9d829..eec2c1b26 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -990,8 +990,6 @@ pub mod sys { let offset = offset.into_option().unwrap_or(0); let frame_ref = crate::frame::frame_at_offset(offset, vm) .ok_or_else(|| vm.new_value_error("call stack is not deep enough"))?; - frame_ref.mark_escaped(); - if let Ok(audit) = vm.sys_module.get_attr("audit", vm) { audit.call((vm.ctx.new_str("sys._getframe"), frame_ref.to_owned()), vm)?; }