Skip to content

Commit f08933b

Browse files
authored
Split InterpreterFrame from FrameObject for stack-allocated execution (#8354)
* Add LightFrame for unobserved Python-to-Python calls Allocate a lightweight LightFrame header on the DataStack instead of a full Frame PyObject for specialized exact-args call paths. The frame is materialized lazily only when observed (sys._getframe, traceback, tracing). Key changes: - LightFrame struct with borrowed pointers and no refcount overhead - FrameSource enum (Heavy/Light) replacing the object field on ExecutingFrame - CURRENT_LIGHT_FRAME TLS for the light frame chain - Interleaved heavy/light frame walking in frame_at_offset_vm and current_thread_frame_vm - invoke_light_slots on PyFunction for the fast call path Call overhead reduced from ~92ns to ~58ns (~37% reduction). fib(28) improved from ~152ms to ~111ms (~27% reduction). Assisted-by: Claude * Streamline with_frame: inline recursion check, skip tracing dispatch - Inline check_recursive_call instead of wrapping in with_recursion closure - Amortize C stack overflow check to every 64th call depth - Skip dispatch_traced_frame when use_tracing is false (hot path) - Add recursion_depth_increment/decrement helpers Assisted-by: Claude * Address review findings for LightFrame correctness - Fix materialize_light_frame previous pointer: use payload pointer (&**frame) instead of Py<Frame> pointer, matching the chain convention - Fix predecessor ref management: use retained_back instead of mem::forget to keep materialized predecessors alive - Fix materialized ref reclaim: use FrameRef::from_raw to drop the leaked keeper ref without double-incrementing - Use PyAtomic<u32> for LightFrame.lasti instead of AtomicU32, removing the unsound cast on non-threading targets - Add amortized C stack overflow check to light frame path - Add scopeguard to light frame execution for panic safety - Remove unnecessary Send/Sync impls from LightFrame - Make check_c_stack_overflow pub(crate) for light frame path access Assisted-by: Claude * 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 * Unify frame chain: replace dual-chain with single FrameChainPtr Replace the dual-chain frame management (CURRENT_FRAME AtomicPtr<Frame> + CURRENT_LIGHT_FRAME Cell<*const LightFrame>) with a single unified chain using FrameChainPtr, a tagged pointer where bit 0 distinguishes heavy (*const Frame) from light (*const LightFrame) entries. Changes: - Add FrameChainPtr type with tagged pointer encoding - Change LightFrame: replace previous_light + saved_current_frame with single previous: FrameChainPtr field - Change InterpreterFrame.previous from AtomicPtr<Frame> to PyAtomic<usize> - Change CURRENT_FRAME TLS from AtomicPtr<Frame> to AtomicUsize - Remove CURRENT_LIGHT_FRAME TLS entirely - Simplify all chain walkers (frame_at_offset, frame_at_offset_vm, find_owned_chain_frame, for_each_current_frame, current_thread_frame) - Remove light-frame materialization block from with_frame - Update f_back to dispatch on FrameChainPtr tags - Update faulthandler and gc_state to walk unified chain - Adaptive C stack check: every call in debug, every 16th in release Assisted-by: Claude * Fix light frame materialization: current_frame, f_back, and GC tracking - current_frame() now uses current_thread_frame_vm() to materialize light frames, fixing super()/compile()/exec() when called from light frame context - materialize_light_frame stores retained_back for heavy predecessors too, so f_back resolves after the caller returns - Escaped materialized frames (traceback, sys._getframe) are GC-tracked at cleanup so reference cycles are collectible - Restore pre-existing @expectedfailure markers for test_frame proxy tests Assisted-by: Claude * Limit light frame materialization to super() only current_frame() returns heavy-only again to avoid unnecessary materialization that extends object lifetimes. super() init uses current_thread_frame_vm() directly since it must inspect the light frame's code and freevars to find __class__. Assisted-by: Claude * Fix remaining CI failures: C stack check, GC tracking threshold, expectedFailure - Check C stack overflow every call in light frame path (not every 16th) to prevent stack overflow on CI workers with smaller stacks - Fix GC tracking threshold: escaped materialized frame detection uses strong_count > 1 (was > 2, missed frames referenced only by traceback in the result) - current_frame() materializes light frames so warnings, compile(), and other callers see the correct frame - Mark test_futures2 PyFutureTests.test_task_exc_handler_correct_context as expectedFailure (pre-existing on main: PyTask __del__ timing) Assisted-by: Claude * Avoid materialization in hot paths: add light-frame-aware accessors current_frame() stays heavy-only to avoid costly materialization on every call. Instead, add current_globals(), current_code(), and current_builtins() that read directly from light frame raw pointers without creating frame PyObjects. Callers migrated: - PyFunction::new: current_builtins() for fallback builtins lookup - compile()/eval(): current_code() for future feature flags - type.__new__: current_globals() for __module__ detection - typevar/typing: current_globals() for caller module name - _io: current_code() for source path check - super(): current_thread_frame_vm() for __class__ cell access - warnings.warn: current_thread_frame_vm() for stack level walk - import: current_globals() for module resolution Assisted-by: Claude * Fix current_locals() to include light frames current_locals() must materialize light frames so locals() builtin returns the correct scope. This fixes test_zipimport doctest and other tests that rely on locals() in light frame context. Assisted-by: Claude * Fix clippy, profile tracing, resume_gen_frame, and faulthandler issues - Fix clippy: collapsible_if in materialize_light_frame, map_unwrap_or in import - Fix profile breakage: use current_thread_frame_vm in trace_event_inner to materialize light frames, preventing frame identity mismatch when profiling is enabled inside a light frame (e.g. profile.Profile.runctx) - Disable light frames inside trace/profile callbacks (tracing_is_suppressed) - Fix resume_gen_frame: skip light frames in previous chain to avoid dangling pointers after generator suspend - Fix faulthandler: emit '<no Python frame>' when chain has only light frames Assisted-by: Claude * Fix materialized frame refcount: transfer localsplus ownership on exit Remove @expectedfailure from test_futures2: the underlying issue was that materialized light frames cloned localsplus values (refcount +1 each), keeping objects alive until the next GC cycle collection. Fix: after run_light_frame returns, transfer ownership of localsplus values from the light frame to the materialized frame, replacing the clones. This eliminates the extra refcount, allowing objects to be GC'd immediately when their last reference is dropped. Also stabilize materialized frame's localsplus on the heap in sync_materialized_on_exit before GC tracking — the data-stack backing will be popped imminently. Assisted-by: Claude * Fix rustfmt, increase stack margin, address review comments - Fix rustfmt formatting issues (lint CI failure) - Increase STACK_MARGIN_BYTES from 2048 to 3072 words to prevent C stack overflow in deep recursion tests on macOS CI (test_functools SIGSEGV) - Skip dispatch_traced_frame when use_tracing is false (review comment) - Fix post-fork top_frame initialization: walk past light frames to find nearest heavy frame (review comment) Assisted-by: Claude * Remove redundant test override in test_futures2 The override only called super() without any marker, making it a no-op that prek flags as a redundant test patch. Assisted-by: Claude * Fix test_pdb and test_functools CI failures - Revert with_frame dispatch_traced_frame skip: tracing can be enabled mid-execution (pdb.set_trace), so Return events must always be checked. This fixes 3 test_pdb doctest failures across all platforms. - Check C stack overflow on every with_frame call instead of every 64th: the previous sampling approach missed overflows between checks when native stack frames are large (invoke_light_slots + lru_cache recursion). - Add #[inline(never)] to invoke_light_slots to prevent the optimizer from merging its stack frame into callers. - Add early C stack check before DataStack allocation in invoke_light_slots. Assisted-by: Claude * Rename Frame to FrameObject, FrameRef to FrameObjectRef Prepare for _PyInterpreterFrame/PyFrameObject separation by renaming the PyObject-backed frame type to FrameObject. Python-visible name ("frame") is unchanged. Assisted-by: Claude * Remove Deref<Target = InterpreterFrame> from FrameObject Replace implicit field access through Deref with explicit iframe()/iframe_ref()/iframe_mut() calls. This decouples FrameObject from InterpreterFrame field layout, enabling independent modification of InterpreterFrame in future commits. Assisted-by: Claude * Move InterpreterFrame identity fields to raw pointers InterpreterFrame's code/globals/builtins/func_obj fields are now borrowed raw pointers instead of owned PyRef/PyObjectRef. Ownership of these references is anchored in FrameObject's new owned_code, owned_globals, owned_builtins, owned_func_obj fields. This aligns InterpreterFrame's layout with LightFrame's existing raw-pointer pattern, enabling future DataStack-based allocation without PyObject heap allocation. - Add code()/globals()/builtins()/func_obj() accessor methods - Add FrameObject::new_ref() for combined create+allocate+patch - Update ExecutingFrame to use &Py<PyCode> / &Py<PyDict> / &PyObject - Update monitoring.rs functions to take &Py<PyCode> instead of &PyRef<PyCode> - Add Send+Sync impls for InterpreterFrame (raw pointers are !Send by default) Assisted-by: Claude * Remove LightFrame, FrameChainPtr, and dual-frame-type infrastructure Delete LightFrame struct, FrameChainPtr tagged pointer, FrameSource enum, and all supporting functions: materialize_light_frame, sync_light_to_materialized, transfer_localsplus_to_materialized, sync_materialized_on_exit, run_light_frame, invoke_light_slots body, LocalsPlus::from_datastack_raw. All call paths now use a single frame type (FrameObject wrapping InterpreterFrame). The frame chain is a simple *const FrameObject linked list through InterpreterFrame.previous. invoke_light_slots delegates to invoke_exact_args_slots. ExecutingFrame.frame_source replaced with ExecutingFrame.frame. Frame chain walking simplified throughout (no more light/heavy dispatch). Net: -724 lines of dual-frame-type complexity. Assisted-by: Claude * Clean up invoke_light_slots and _vm function variants - Replace invoke_light_slots calls with invoke_exact_args_slots - Delete the invoke_light_slots delegate method - Replace current_thread_frame_vm/frame_at_offset_vm with their non-_vm equivalents (no more light frame materialization needed) - Remove stale light-frame comment in callable.rs Assisted-by: Claude * Fix CI lint issues - Move github context expressions from run blocks to env blocks (zizmor/template-injection) - Add workflow-level permissions: {} to lib-deps-check.yaml - Suppress zizmor excessive-permissions for lib-deps-check.yaml (pull_request_target is required for PR comments) - Apply rustfmt formatting Assisted-by: Claude * Stack-allocate InterpreterFrame for non-generator function calls - Extract InterpreterFrame::new() from FrameObject::new() - Change frame chain from *const FrameObject to *const InterpreterFrame - Add InterpreterFrame.materialized field for lazy FrameObject creation - Add vm.with_iframe()/run_frame_fast() for stack-allocated frame execution - Move ExecutingFrame.frame to iframe: *const InterpreterFrame - Implement lazy materialize() for on-demand FrameObject creation - Update invoke_with_locals/invoke_prepared_exact_args to skip heap allocation - Update frame chain walking in faulthandler, gc_state, builtins/frame - super() reads InterpreterFrame directly without materialization Assisted-by: Claude * Remove unnecessary refcount and atomic ops from fast call path - Use raw pointer for func_obj instead of cloning PyObjectRef (saves 2 atomic RMW) - Skip owner AcqRel swap for stack-allocated frames (always Thread-owned) Assisted-by: Claude * Change trace field to Option<PyObjectRef>, remove vm from InterpreterFrame::new - trace: PyMutex<PyObjectRef> -> PyMutex<Option<PyObjectRef>> - Eliminates vm.ctx.none() refcount inc/dec per frame init - Remove vm parameter from InterpreterFrame::new() (no longer needed) - f_trace getter returns None when trace is unset - f_trace setter stores None instead of the None singleton Assisted-by: Claude * Store current frame pointer on VirtualMachine for fast lookup - Add current_frame_ptr Cell<usize> field to VirtualMachine - Hot path (with_iframe) reads/writes vm.current_frame_ptr instead of TLS - TLS CURRENT_FRAME still maintained for signal-safe traceback walking - Use set_current_frame_nosave() to skip cross-thread top_frame update - super(), frame_at_offset, current_thread_frame_materialize use vm Cell Assisted-by: Claude * Apply rustfmt Assisted-by: Claude * Fix clippy use_self warnings, add inline hints for hot path Assisted-by: Claude * Remove code refcount clone from fast call path Defer code.to_owned() to the generator/coroutine path only. On the fast path, use &Py<PyCode> directly — the code object is alive via the PyFunction on the caller's stack. Assisted-by: Claude * Remove VM current_frame_ptr Cell (fix SIGSEGV in threading builds) The Cell<usize> on VirtualMachine could desynchronize from the TLS CURRENT_FRAME in nested VM scenarios (enter_vm called with different VMs on the same thread). Revert to TLS-only frame chain management. Keep set_current_frame_nosave() for the stack-frame fast path: it skips cross-thread top_frame publication but still does the TLS swap. Fix clippy not_unsafe_ptr_arg_deref in set_current_frame. Fix unnecessary Result wrapper in make_generator_or_coro. Assisted-by: Claude * Fix CI failures: GC frame assertion, faulthandler Radium import, dead code warning - Fix SIGSEGV in threading builds: GC debug assertion walked frame chain via top_frame, which is not updated by set_current_frame_nosave used by stack-allocated iframes. Walk CURRENT_FRAME TLS chain instead. - Add InterpreterFrame::get_lasti() accessor, remove direct Radium import from faulthandler.rs (fixes unused import warning). - Add cfg_attr allow(dead_code) for from_payload_ptr without threading (fixes WASM build warning). Assisted-by: Claude * Fix materialized frame: copy localsplus, clear stale previous pointers - materialize_slow: copy fastlocals snapshot to heap instead of empty localsplus (fixes index-out-of-bounds panic in locals()/f_locals) - materialize_slow: set previous to null (stack iframes become dangling) - with_frame/resume_gen_frame: clear iframe.previous on pop (prevents dangling pointers to freed stack iframes) - f_back: walk TLS CURRENT_FRAME chain instead of top_frame for cross-thread lookup (top_frame is stale for stack-allocated iframes) - check_locals_access: match materialized frames by comparing the Py<FrameObject> address stored in the stack iframe's materialized field Assisted-by: Claude * Fix Windows clippy: gate unix-only imports and variables FrameObject, Py imports and top_iframe variable are only used in unix+threading cfg blocks. Gate them properly for non-unix builds. Assisted-by: Claude * Fall back to FrameObject when tracing; fix cross-thread frame access - invoke_with_locals: fall back to heap-allocated FrameObject path when use_tracing is active, so trace/profile callbacks fire correctly. This fixes test_trace, test_bdb, test_monitoring regressions. - set_current_frame: publish top_iframe alongside top_frame in ThreadSlot so cross-thread readers (sys._current_frames) can materialize stack-allocated frames from other threads under stop-the-world. - get_all_current_frames: use TLS CURRENT_FRAME for current thread, fall back to top_iframe materialization for other threads. - Use set_current_frame (not nosave) in with_iframe for proper cross-thread visibility. Assisted-by: Claude * Apply rustfmt Assisted-by: Claude * Fix Windows: gate unix-only top_iframe_ptr variable Assisted-by: Claude * Fix frame chain: f_back, retained_back, owner, faulthandler - Set materialized frame owner to FrameObject (not Thread) so frame.clear() works on traceback frames - Fix f_back for materialized frames: walk TLS chain to find source iframe and materialize its previous frame - Fix faulthandler dump_traceback to use dump_live_frames (reads live lasti from stack iframes instead of stale materialized copies) - Add retained_back propagation in with_frame cleanup: when a frame escapes, materialize caller and mark it escaped for chain propagation - Fix release_datastack_frame: don't overwrite retained_back if already set by with_frame cleanup Assisted-by: Claude * Apply rustfmt Assisted-by: Claude * Refine with_frame retained_back: use frame_obj instead of materialize Avoid materializing caller frames in with_frame cleanup to prevent creating localsplus snapshots that keep extra refcounts. Only use existing FrameObjects (frame_obj) for retained_back. Also keep previous pointer alive in release_datastack_frame since the caller is still executing at that point. Assisted-by: Claude * Fix f_lineno: use Cell<u32> for prev_line, update on every instruction prev_line was only updated when tracing was active, causing f_lineno to return stale values when observed mid-CALL (e.g. from warnings.warn or sys._getframe). Changed prev_line from u32 to Cell<u32> for interior mutability so it can be read safely while ExecutingFrame holds a shared reference, and update it on every instruction so f_lineno always returns the correct line. Assisted-by: Claude * Fix f_lineno for materialized frames: read live prev_line from TLS chain Materialized FrameObjects have a snapshot of prev_line from materialize time. When f_lineno is called on a materialized frame that is still executing (its source iframe is on the TLS chain), walk the TLS chain to find the source iframe and read its live prev_line. This fixes lineno tracking in warnings, gettext, and other modules that read frame line numbers during function calls. Also fixed the pointer comparison: use from_payload_ptr to convert FrameObject payload address to Py<FrameObject> address for matching against the materialized field. Assisted-by: Claude * Sync live frame state for materialized frames - Add find_live_source_iframe() to walk TLS chain and find the live source InterpreterFrame for a materialized FrameObject - f_lasti: read live lasti from source iframe when available - f_lineno: use read_volatile for live prev_line from source iframe - sync_visible_locals_to_mapping: read live localsplus from source iframe - framelocalsproxy_getval: read live localsplus from source iframe - with_iframe cleanup: sync localsplus, prev_line, lasti to materialized FrameObject when the frame returns - sync_fastlocals_from: new LocalsPlus method to update fastlocals Fixes test_inspect (stale f_locals, stale lineno in positions), test_listcomps (stale iteration variable in locals()). Assisted-by: Claude * Fix retained_back: use materialize_chain and strong_count check - Add materialize_chain() that creates a lightweight FrameObject with empty localsplus for f_back chain building only. This avoids cloning local variables which would create extra refcounts (fixes test_memoryview). - with_frame cleanup: use strong_count > 1 instead of escaped flag to determine if retained_back is needed, ensuring chain propagation. - with_iframe cleanup: use materialize_chain for retained_back to avoid extra refcounts from localsplus snapshots. Fixes test_memoryview (refcount from retained_back chain) and test_traceback.test_extract_stack (f_back chain depth). Assisted-by: Claude * Fix cross-thread f_back chain for sys._current_frames/exceptions - get_all_current_frames: materialize entire frame chain and link retained_back during stop-the-world for other threads - f_back: use stop-the-world to safely materialize cross-thread frame chains on unix when prev iframe is on another thread - f_back: check frame_obj() before STW for fast path Fixes test_sys.test_current_frames and test_sys.test_current_exceptions. Assisted-by: Claude * Fix monitoring LINE events and faulthandler thread dump - Skip all instrumented opcodes in bytecode loop prev_line update to avoid defeating InstrumentedLine de-duplication - Add prev_line update in execute_instrumented for non-RESUME/non-LINE instrumented opcodes - update_events_mask: walk TLS iframe chain to re-instrument all frames including stack-allocated ones (fixes missing LINE events for inline frames already past RESUME) - faulthandler: use top_iframe fallback for stack-allocated frames in dump_all_threads and watchdog_thread - faulthandler: use dump_live_frames for current thread dump Fixes test_monitoring LINE/CALL tests, test_faulthandler thread dumps. Assisted-by: Claude * Fix clippy: remove duplicate Radium import, use previous() helper Assisted-by: Claude * Fix materialized frame issues: GC tracking, live locals, f_trace propagation - Track materialized FrameObjects in GC so cycle collection works (fixes __del__ not called at exit for exception traceback cycles) - materialize_chain returns owned PyRef without temporary_refs to avoid defeating GC cycle detection from non-GC-tracked storage - framelocalsproxy_setval writes to live source iframe when available (fixes f_locals proxy writes not affecting executing frame) - f_lineno uses lasti-based line for non-executing frames instead of prev_line (fixes PEP 626 wrong line after exception unwind) - f_trace/f_trace_lines/f_trace_opcodes setters propagate to live source iframe (fixes pdb set_trace not working through materialized frames) - frame.clear() checks find_live_source_iframe to reject clearing a frame that is backed by a live stack-allocated iframe - Fix clippy warnings and unused imports in faulthandler - Remove unsafe top_iframe access from faulthandler watchdog thread Assisted-by: Claude * Fix faulthandler crashes, GC tracking timing, and test_generators - Use top_iframe instead of top_frame for frame chain walking in faulthandler (dump_all_threads and watchdog), avoiding stale or GC-cleared FrameObject pointer dereference - Defer GC tracking of materialized FrameObjects to with_iframe cleanup (after set_current_frame restores old chain), preventing premature collection while temporary_refs still holds the only ref - Add FrameObject::try_iframe() for safe access to potentially GC-cleared frames - Remove expectedFailure for test_exhausted_generator_frame_cycle (now passes with GC tracking fix) - Add 'noalias' to cspell dictionary Assisted-by: Claude * Fix Windows frame dump and remove dead code - Publish top_iframe on all platforms (not just unix) so Windows faulthandler and sys._current_frames can see stack-allocated frames - Use top_iframe instead of frames Mutex in Windows faulthandler dump and watchdog paths - Use top_iframe in non-unix get_all_current_frames for sys._current_frames - Remove unused dump_traceback_thread_chain (replaced by top_iframe walk) - Mark test_pdb_await_support as expected failure Assisted-by: Claude * Fix ThreadSlot init on all platforms, skip test_pdb_await_support - Add top_iframe to ThreadSlot initializer in init_thread_slot_if_needed (fixes wasm32, Windows compile errors) - Skip test_pdb_await_support on RustPython (async pdb exception callback receives None exc argument) Assisted-by: Claude * Remove dead faulthandler code (dump_frame_from_ref, dump_traceback_thread_frames, try_iframe) These functions are no longer called after switching all frame chain walking to use top_iframe directly. Assisted-by: Claude * Fix Windows sys._current_frames: materialize full frame chain The non-unix get_all_current_frames path was only materializing the top iframe without linking retained_back, so f_back chain walking could not find deeper frames like f123() in test_sys tests. Replicate the full chain materialization from the unix path. Assisted-by: Claude * Use STW-based cross-thread f_back on all platforms Replace the non-unix frames-mutex fallback with the same stop-the-world iframe chain materialization used on unix. Fixes test_current_exceptions on Windows where stack-allocated iframes were not found in the FrameObject-only frames list. Assisted-by: Claude * Fix set_f_lineno to write to live iframe, use STW for cross-thread access - set_f_lineno: write lasti/pending_stack_pops to the live source iframe instead of the materialized copy so debugger jumps take effect on stack-allocated frames - f_back cross-thread: enter STW before dereferencing the prev pointer to prevent use-after-free if the owning thread returns - get_all_current_frames (non-unix): add STW protection when walking cross-thread iframe chains, matching the unix path Assisted-by: Claude * Guard current_location() against lasti==0, always capture retained_back - current_location(): return first_line_number instead of panicking when lasti is 0 (before first instruction executes) - with_frame retained_back: materialize the caller iframe when needed so f_back always resolves for escaped FrameObjects Assisted-by: Claude * Fix format, revert eager retained_back materialization Remove double blank line in frame.rs (lint failure). Revert with_frame retained_back to only use already-materialized caller FrameObjects — eagerly materializing the caller added refcounts on local variables, preventing timely deallocation and causing test_io, test_memoryview, and test_futures failures. Assisted-by: Claude * Remove rustpython-unicode-isolation-issue.md from gitignore Assisted-by: Claude * Remove test_pdb_await_support skip: async pdb now works correctly Assisted-by: Claude * Fix STOP_ITERATION monitoring: wrap value in StopIteration instance fire_stop_iteration was passing the raw iterator return value to callbacks, but the STOP_ITERATION event callback signature expects a StopIteration exception instance. Wrap non-StopIteration values in a new StopIteration(value), matching PyMonitoring_FireStopIterationEvent. This fixes test_pdb_await_support where bdb's exception_callback received None instead of a StopIteration instance. Assisted-by: Claude
1 parent 04c3ecf commit f08933b

39 files changed

Lines changed: 2157 additions & 946 deletions

.cspell.dict/rust-more.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ modpow
5050
msvc
5151
muldiv
5252
nanos
53+
noalias
5354
nonoverlapping
5455
objclass
5556
peekable

.github/workflows/cron-ci.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ jobs:
8585
if: ${{ github.event_name != 'pull_request' }}
8686
env:
8787
SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }}
88-
GITHUB_ACTOR: ${{ github.actor }}
8988
run: |
9089
echo "$SSHKEY" >~/github_key
9190
chmod 600 ~/github_key
@@ -95,7 +94,7 @@ jobs:
9594
cd website
9695
cp ../extra_tests/cpython_tests_results.json ./_data/regrtests_results.json
9796
git add ./_data/regrtests_results.json
98-
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update regression test results" --author="$GITHUB_ACTOR"; then
97+
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update regression test results"; then
9998
git push
10099
fi
101100
@@ -127,7 +126,6 @@ jobs:
127126
if: ${{ github.event_name != 'pull_request' }}
128127
env:
129128
SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }}
130-
GITHUB_ACTOR: ${{ github.actor }}
131129
run: |
132130
echo "$SSHKEY" >~/github_key
133131
chmod 600 ~/github_key
@@ -158,7 +156,7 @@ jobs:
158156
}
159157
EOF
160158
git add -A
161-
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update what is left results" --author="$GITHUB_ACTOR"; then
159+
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update what is left results"; then
162160
git push
163161
fi
164162
@@ -204,6 +202,8 @@ jobs:
204202
if: ${{ github.event_name != 'pull_request' }}
205203
env:
206204
SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }}
205+
COMMIT_SHA: ${{ github.sha }}
206+
REF_NAME: ${{ github.ref_name }}
207207
run: |
208208
echo "$SSHKEY" >~/github_key
209209
chmod 600 ~/github_key
@@ -215,8 +215,8 @@ jobs:
215215
cp -r ../target/criterion ./assets/criterion
216216
printf '{\n "generated_at": "%s",\n "rustpython_commit": "%s",\n "rustpython_ref": "%s"\n}\n' \
217217
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
218-
"${{ github.sha }}" \
219-
"${{ github.ref_name }}" > ./_data/criterion-metadata.json
218+
"$COMMIT_SHA" \
219+
"$REF_NAME" > ./_data/criterion-metadata.json
220220
git add ./assets/criterion ./_data/criterion-metadata.json
221221
if git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update benchmark results"; then
222222
git push

.github/workflows/lib-deps-check.yaml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
paths:
77
- "Lib/**"
88

9+
permissions: {}
10+
911
concurrency:
1012
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }}
1113
cancel-in-progress: true
@@ -26,13 +28,17 @@ jobs:
2628
persist-credentials: false
2729

2830
- name: Fetch PR head
31+
env:
32+
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
2933
run: |
30-
git fetch origin ${{ github.event.pull_request.head.sha }}
34+
git fetch origin "$PR_HEAD_SHA"
3135
3236
- name: Checkout PR Lib files
37+
env:
38+
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
3339
run: |
3440
# Checkout only Lib/ directory from PR head for accurate comparison
35-
git checkout ${{ github.event.pull_request.head.sha }} -- Lib/
41+
git checkout "$PR_HEAD_SHA" -- Lib/
3642
3743
- name: Get target CPython version
3844
id: cpython-version
@@ -51,14 +57,17 @@ jobs:
5157

5258
- name: Get changed Lib files
5359
id: all-changed-files
60+
env:
61+
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
62+
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
5463
run: |
5564
# Get the list of changed files under Lib/
5665
{
5766
echo 'changed<<EOF'
5867
59-
git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- 'Lib/*.py' 'Lib/**/*.py'
68+
git diff --name-only "$PR_BASE_SHA" "$PR_HEAD_SHA" -- 'Lib/*.py' 'Lib/**/*.py'
6069
61-
echo 'EOF'
70+
echo 'EOF'
6271
} >> "$GITHUB_OUTPUT"
6372
6473
- name: Parse changed files

.github/zizmor.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
rules:
2+
excessive-permissions:
3+
ignore:
4+
# pull_request_target is needed to post PR comments with pull-requests: write.
5+
# Workflow-level permissions: {} restricts defaults; only the job has write access.
6+
- lib-deps-check.yaml:3
27
unpinned-uses:
38
config:
49
policies:

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ Lib/site-packages/*
2929
Lib/test/data/*
3030
!Lib/test/data/README
3131
cpython/
32-
.claude/scheduled_tasks.lock
32+
.claude/
3333
docs/superpowers/

Lib/test/test_generators.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ def gen():
134134
self.assertEqual(len(resurrected), 1)
135135
self.assertIsInstance(resurrected[0].gi_code, types.CodeType)
136136

137-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <frame object at 0xb4000073269f09e0> is not None
138137
def test_exhausted_generator_frame_cycle(self):
139138
def g():
140139
yield

crates/capi/src/ceval.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub extern "C" fn PyEval_GetBuiltins() -> *mut PyObject {
5858
with_vm(|vm| {
5959
vm.current_frame().map_or_else(
6060
|| vm.builtins.as_object().as_raw(),
61-
|frame| frame.builtins.as_object().as_raw(),
61+
|frame| frame.iframe().builtins().as_raw(),
6262
)
6363
})
6464
}
@@ -78,7 +78,7 @@ pub extern "C" fn PyEval_GetFrameBuiltins() -> *mut PyObject {
7878
with_vm(|vm| {
7979
vm.current_frame().map_or_else(
8080
|| vm.builtins.as_object().to_owned(),
81-
|frame| frame.builtins.as_object().to_owned(),
81+
|frame| frame.iframe().builtins().to_owned(),
8282
)
8383
})
8484
}
@@ -87,7 +87,15 @@ pub extern "C" fn PyEval_GetFrameBuiltins() -> *mut PyObject {
8787
pub extern "C" fn PyEval_GetFrameGlobals() -> *mut PyObject {
8888
with_vm(|vm| {
8989
vm.current_frame()
90-
.map(|frame| frame.globals.as_object().to_owned().into_raw().as_ptr())
90+
.map(|frame| {
91+
frame
92+
.iframe()
93+
.globals()
94+
.as_object()
95+
.to_owned()
96+
.into_raw()
97+
.as_ptr()
98+
})
9199
.unwrap_or_default()
92100
})
93101
}
@@ -107,7 +115,7 @@ pub extern "C" fn PyEval_GetFrameLocals() -> *mut PyObject {
107115
pub extern "C" fn PyEval_GetGlobals() -> *mut PyObject {
108116
with_vm(|vm| {
109117
vm.current_frame()
110-
.map(|frame| frame.globals.as_object().as_raw())
118+
.map(|frame| frame.iframe().globals().as_object().as_raw())
111119
.unwrap_or_default()
112120
})
113121
}
@@ -119,7 +127,7 @@ pub extern "C" fn PyEval_GetLocals() -> *mut PyObject {
119127
return Ok(core::ptr::null_mut());
120128
};
121129
let _ = frame.locals(vm)?;
122-
Ok(frame.locals.as_object(vm).as_raw().cast_mut())
130+
Ok(frame.iframe().locals.as_object(vm).as_raw().cast_mut())
123131
})
124132
}
125133

crates/capi/src/pyframe.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use crate::pystate::with_vm;
22
use core::ffi::c_int;
33
use rustpython_vm::Py;
44
use rustpython_vm::builtins::PyCode;
5-
use rustpython_vm::frame::Frame;
5+
use rustpython_vm::frame::FrameObject;
66

7-
pub type PyFrameObject = Py<Frame>;
7+
pub type PyFrameObject = Py<FrameObject>;
88
pub type PyCodeObject = Py<PyCode>;
99

1010
#[unsafe(no_mangle)]

0 commit comments

Comments
 (0)