Split InterpreterFrame from FrameObject for stack-allocated execution - #8354
Merged
youknowone merged 58 commits intoJul 31, 2026
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Split
InterpreterFrame(execution state) fromFrameObject(Python-visibleframeobject) so that normal function calls stack-allocate only anInterpreterFrameon the Rust stack. A fullFrameObjectis created lazily viamaterialize()only when Python code observes the frame (e.g.sys._getframe(), traceback creation,sys.settrace()).This mirrors CPython 3.11+ where
_PyInterpreterFramelives on the C stack andPyFrameObjectis allocated on demand.Architecture
InterpreterFrame—#[repr(C)]struct on the Rust stack insidewith_iframe. Contains code, globals, builtins, localsplus, lasti, trace state, and apreviouspointer forming the TLS frame chain.FrameObject—PyObjectwrapper created bymaterialize()when needed. Holds an owned copy ofInterpreterFrameand is linked viamaterialized/find_live_source_iframe()for bidirectional access.with_iframe— new fast path for regular function calls. No heap allocation, no refcount, no freelist.with_frame— existing path for generators, coroutines,exec(),eval()that require a durableFrameObject.Key design decisions
current_code()and free-threading safety:current_code()reads the topmostInterpreterFramefrom thread-localCURRENT_FRAMEand borrows itscodepointer. This is safe because: (1)CURRENT_FRAMEis per-thread TLS — no cross-thread access; (2) thecodepointer borrows from thePyFunctionon the caller's stack, which is alive while the frame executes; (3).to_owned()increments the refcount before returning, producing an independentPyRef<PyCode>.Cross-thread frame access:
f_back,sys._current_frames(), andsys._current_exceptions()use stop-the-world (STW) to safely materialize cross-thread iframe chains. STW is entered before dereferencing any cross-thread pointer to prevent use-after-free races. The non-unix path now uses STW identically to unix, replacing the previousframesMutex fallback.set_f_lineno(debugger jump): Writeslasti,pending_stack_pops, andpending_unwind_from_stackto the live source iframe viafind_live_source_iframe(), not the materialized copy, so pdbjumpcommands take effect on stack-allocated frames.GC tracking timing: Materialized
FrameObjects are tracked in GC only atwith_iframecleanup afterset_current_framerestores the old chain. This prevents premature collection while the frame is still executing.retained_back: Only captures already-materialized callers to avoid adding refcounts on local variables (which would delay__del__/ResourceWarning). For non-materialized callers,f_backresolves via the TLS chain while executing, or returnsNoneafter return.Performance improvements
Zero heap allocation for normal function calls (no
FrameObject, no freelist, no refcount)Amortized C stack overflow check (every 8th recursion depth)
Panic-safe recursion depth via
scopeguardinwith_frameBenchmark results (Apple M-series, release build)
Test fixes included
test_sys(Windows): full frame chain materialization withretained_backin non-unixget_all_current_framestest_current_exceptions(Windows): STW-based cross-threadf_backon all platformstest_frame:frame.clear()rejects live frames,f_localsproxy writes to live iframetest_traceback: deferred GC tracking for materialized framestest_generators: removed@expectedFailurefor frame/GC cycle teststest_pdb:f_tracepropagation to live source iframetest_faulthandler: usetop_iframefor all frame chain walkingAddresses youknowone#40
Summary by CodeRabbit
Performance
Bug Fixes
sys._getframebehavior.