Skip to content

Implement PyStackRef borrowed stack references for LOAD_FAST_BORROW#7290

Merged
youknowone merged 3 commits intoRustPython:mainfrom
youknowone:worktree-pystackref
Mar 1, 2026
Merged

Implement PyStackRef borrowed stack references for LOAD_FAST_BORROW#7290
youknowone merged 3 commits intoRustPython:mainfrom
youknowone:worktree-pystackref

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Mar 1, 2026

Summary by CodeRabbit

  • Refactor

    • Redesigned VM stack representation and stack handling to improve execution performance, memory behavior, and runtime stability.
    • Added traversal support for the new stack representation to ensure correct resource management.
  • Chores

    • Updated spell/dictionary with three new recognized tokens.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Adds a tagged stack-reference type PyStackRef, replaces frame stack slots to store PyStackRef everywhere, introduces new stack push/pop helpers and accessors, exposes PyStackRef publicly, adds a Traverse impl for it, and adds three cspell dictionary entries.

Changes

Cohort / File(s) Summary
PyStackRef type
crates/vm/src/object/core.rs
Introduces public PyStackRef (tagged pointer owned vs borrowed). Adds constructors (new_owned, new_borrowed), accessors (is_borrowed, as_object), conversion (to_pyobj), promote, Drop, Deref, Clone, Debug, Send/Sync cfg, and size assertions.
Frame stack refactor
crates/vm/src/frame.rs
Changes FrameState.stack to BoxVec<Option<PyStackRef>>. Replaces PyObjectRef stack usage with PyStackRef, updates many instruction handlers, and adds stack APIs: push_stackref_opt, push_value_opt, push_value, push_borrowed (unsafe), pop_stackref_opt, pop_stackref, pop_value_opt, pop_value, top_value, nth_value, and expect_unchecked.
Traversal support
crates/vm/src/object/traverse.rs
Imports PyStackRef and adds unsafe impl Traverse for PyStackRef that delegates traversal to as_object().
Public exports
crates/vm/src/lib.rs
Re-exports PyStackRef from the object module.
Dictionary
.cspell.dict/cpython.txt
Adds three new words: fastlocal, fastlocals, stackref.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop where bits and pointers play,
A tiny tag shows owned or stray,
I push and pop with gentle tread,
Keeper or borrow — both well-led,
A carrot for each stackref bed. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing PyStackRef type to support borrowed stack references for the LOAD_FAST_BORROW operation, which is directly reflected in the changeset across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin worktree-pystackref

@youknowone youknowone force-pushed the worktree-pystackref branch 2 times, most recently from 5c3dd36 to 936d118 Compare March 1, 2026 11:44
@youknowone youknowone marked this pull request as ready for review March 1, 2026 12:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/vm/src/frame.rs (2)

2637-2650: ⚠️ Potential issue | 🟡 Minor

Mirror the borrowed-stack yield invariant in instrumented mode.

YieldValue enforces a debug invariant against borrowed refs on stack, but InstrumentedYieldValue currently skips that check.

Proposed parity fix
             Instruction::InstrumentedYieldValue => {
+                debug_assert!(
+                    self.state
+                        .stack
+                        .iter()
+                        .flatten()
+                        .all(|sr| !sr.is_borrowed()),
+                    "borrowed refs on stack at yield point"
+                );
                 let value = self.pop_value();
                 if self.monitoring_mask & monitoring::EVENT_PY_YIELD != 0 {
                     let offset = (self.lasti() - 1) * 2;
                     monitoring::fire_py_yield(vm, self.code, offset, &value)?;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/frame.rs` around lines 2637 - 2650, InstrumentedYieldValue
currently skips the debug invariant that YieldValue enforces against borrowed
refs on the stack; after calling self.pop_value() in
Instruction::InstrumentedYieldValue you should run the same borrowed-stack
check/assert used by YieldValue (i.e., the debug assertion that ensures the
popped value contains no borrowed refs) before calling
monitoring::fire_py_yield, before any wrapping for COROUTINE, and before
returning ExecutionResult::Yield; reuse the same helper/assert or inline the
same check so behavior is identical to YieldValue (refer to pop_value,
monitoring::fire_py_yield, lasti, code.flags / bytecode::CodeFlags::COROUTINE,
PyAsyncGenWrappedValue, and ExecutionResult::Yield).

1814-1828: ⚠️ Potential issue | 🟠 Major

LOAD_FAST_BORROW is still an owned-clone fallback.

This path currently clones from fastlocals and calls push_value, so borrowed-stack behavior is not actually active yet. The in-code TODO also flags known corruption concerns that should be resolved (or explicitly scoped out) before merge.

If helpful, I can draft a follow-up issue with a concrete rollout plan (guarded enablement + invariants + tests) for re-enabling true borrowed refs safely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/frame.rs` around lines 1814 - 1828, The handler labeled for
LOAD_FAST_BORROW is still cloning and pushing an owned value (using
self.fastlocals.lock()[idx].clone() and self.push_value), so either implement
true borrowed semantics or make the fallback explicit: implement a push_borrowed
helper (e.g., self.push_borrowed_ref(idx) or similar) that pushes a borrowed
reference onto the stack without cloning and use it here, ensuring you access
self.fastlocals without cloning the slot; otherwise, stop pretending borrowed
behavior is active by routing this opcode to the existing owned LOAD_FAST logic
(or renaming the handler) and update the TODO/comment accordingly; refer to the
LOAD_FAST_BORROW usage, self.fastlocals.lock(), push_value, and
self.code.varnames to locate and change the code.
🧹 Nitpick comments (1)
crates/vm/src/object/core.rs (1)

1404-1406: Remove decorative section separators.

Please replace the banner-style separator comments with a normal short comment/doc-comment.

Proposed cleanup
-// ---------------------------------------------------------------------------
-// PyStackRef – tagged stack reference (owned or borrowed)
-// ---------------------------------------------------------------------------
+// PyStackRef: tagged stack reference (owned or borrowed)
As per coding guidelines: "Do not add decorative section separators (e.g. `// -----------`, `// ===`, `/* *** */`). Use `///` doc-comments or short `//` comments only when they add value".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/object/core.rs` around lines 1404 - 1406, Replace the
decorative banner comment block above the PyStackRef declaration with a concise
comment or doc-comment: remove the multi-line separator lines and change the
text "// PyStackRef – tagged stack reference (owned or borrowed)" into a single
short comment (e.g. "// PyStackRef — tagged stack reference (owned or
borrowed)") or a proper doc-comment ("/// PyStackRef — tagged stack reference
(owned or borrowed)") placed immediately above the PyStackRef type/struct so it
stays documented without decorative separators.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/vm/src/frame.rs`:
- Around line 2637-2650: InstrumentedYieldValue currently skips the debug
invariant that YieldValue enforces against borrowed refs on the stack; after
calling self.pop_value() in Instruction::InstrumentedYieldValue you should run
the same borrowed-stack check/assert used by YieldValue (i.e., the debug
assertion that ensures the popped value contains no borrowed refs) before
calling monitoring::fire_py_yield, before any wrapping for COROUTINE, and before
returning ExecutionResult::Yield; reuse the same helper/assert or inline the
same check so behavior is identical to YieldValue (refer to pop_value,
monitoring::fire_py_yield, lasti, code.flags / bytecode::CodeFlags::COROUTINE,
PyAsyncGenWrappedValue, and ExecutionResult::Yield).
- Around line 1814-1828: The handler labeled for LOAD_FAST_BORROW is still
cloning and pushing an owned value (using self.fastlocals.lock()[idx].clone()
and self.push_value), so either implement true borrowed semantics or make the
fallback explicit: implement a push_borrowed helper (e.g.,
self.push_borrowed_ref(idx) or similar) that pushes a borrowed reference onto
the stack without cloning and use it here, ensuring you access self.fastlocals
without cloning the slot; otherwise, stop pretending borrowed behavior is active
by routing this opcode to the existing owned LOAD_FAST logic (or renaming the
handler) and update the TODO/comment accordingly; refer to the LOAD_FAST_BORROW
usage, self.fastlocals.lock(), push_value, and self.code.varnames to locate and
change the code.

---

Nitpick comments:
In `@crates/vm/src/object/core.rs`:
- Around line 1404-1406: Replace the decorative banner comment block above the
PyStackRef declaration with a concise comment or doc-comment: remove the
multi-line separator lines and change the text "// PyStackRef – tagged stack
reference (owned or borrowed)" into a single short comment (e.g. "// PyStackRef
— tagged stack reference (owned or borrowed)") or a proper doc-comment ("///
PyStackRef — tagged stack reference (owned or borrowed)") placed immediately
above the PyStackRef type/struct so it stays documented without decorative
separators.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 367aff9 and 936d118.

📒 Files selected for processing (5)
  • .cspell.dict/cpython.txt
  • crates/vm/src/frame.rs
  • crates/vm/src/lib.rs
  • crates/vm/src/object/core.rs
  • crates/vm/src/object/traverse.rs

…wed refs

- Change PyStackRef.bits from usize to NonZeroUsize so Option<PyStackRef>
  has the same size as Option<PyObjectRef> via niche optimization
- Revert LoadFastBorrow to use clone instead of actual borrowed refs
  to avoid borrowed refs remaining on stack at yield points
- Add static size assertions for Option<PyStackRef>
- Add stackref, fastlocal to cspell dictionary
- Remove debug eprintln statements
- Fix clippy warning for unused push_borrowed
@youknowone youknowone force-pushed the worktree-pystackref branch from 936d118 to 5c25fc7 Compare March 1, 2026 12:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/vm/src/object/core.rs (1)

1404-1406: Remove decorative separator comments in this module section.

Please replace this separator with a normal doc-comment header (///) or a short value-adding comment to match repo standards.

As per coding guidelines: "Do not add decorative section separators (e.g. // -----------, // ===, /* *** */). Use /// doc-comments or short // comments only when they add value".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/object/core.rs` around lines 1404 - 1406, Replace the
decorative separator comment above the PyStackRef section with a value-adding
doc or inline comment: change the multi-line decorative block to a short ///
doc-comment describing the PyStackRef type and its purpose (e.g., "///
PyStackRef: a tagged stack reference that may be owned or borrowed") or a
concise // comment that explains why this section exists; update the comment
immediately adjacent to the PyStackRef symbol so the module follows the repo's
non-decorative comment guideline.
crates/vm/src/frame.rs (1)

4096-4106: push_borrowed is dead code; the borrowed fast-path is disabled pending resolution of stack lifetime issues at yield/exception points.

The push_borrowed helper (line 4113) is defined but never called. Both LOAD_FAST_BORROW (line 1870) and LoadFastBorrowLoadFastBorrow (line 1887) fall back to .clone() instead. The code comment at line 1868 explicitly notes this is intentional: the optimization is available but disabled until stack lifetime issues are resolved. Either wire it in when lifetime issues are fixed, or remove it to avoid drift.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/frame.rs`:
- Around line 2501-2508: The debug_assert ensuring no borrowed refs on the stack
is only present in the Instruction::YieldValue suspend path but must also run
for Instruction::InstrumentedYieldValue to maintain the suspend invariant; add
the same check (or factor into a small helper like
assert_no_borrowed_stack(&self.state) and call it) before the frame suspension
in the InstrumentedYieldValue handling so that
self.state.stack.iter().flatten().all(|sr| !sr.is_borrowed()) is asserted in
both yield code paths.

---

Nitpick comments:
In `@crates/vm/src/object/core.rs`:
- Around line 1404-1406: Replace the decorative separator comment above the
PyStackRef section with a value-adding doc or inline comment: change the
multi-line decorative block to a short /// doc-comment describing the PyStackRef
type and its purpose (e.g., "/// PyStackRef: a tagged stack reference that may
be owned or borrowed") or a concise // comment that explains why this section
exists; update the comment immediately adjacent to the PyStackRef symbol so the
module follows the repo's non-decorative comment guideline.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 936d118 and 5c25fc7.

📒 Files selected for processing (5)
  • .cspell.dict/cpython.txt
  • crates/vm/src/frame.rs
  • crates/vm/src/lib.rs
  • crates/vm/src/object/core.rs
  • crates/vm/src/object/traverse.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • .cspell.dict/cpython.txt
  • crates/vm/src/lib.rs
  • crates/vm/src/object/traverse.rs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/vm/src/frame.rs (1)

1284-1286: Potential issue when borrowed refs are enabled: cloning propagates the borrow tag.

When push_borrowed() is eventually enabled, cloning an Option<PyStackRef> in the Copy instruction will propagate the borrowed tag, resulting in multiple borrowed refs to the same object on the stack. This is safe only if the compiler guarantees all copies are consumed within the same basic block before the source object can be invalidated.

Consider adding a comment noting this invariant, or alternatively promoting borrowed refs to owned when copying:

// Example: promote borrowed to owned on copy to avoid lifetime issues
let value = self.state.stack[stack_len - idx]
    .as_ref()
    .map(|sr| PyStackRef::new_owned(sr.to_pyobj()));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/frame.rs` around lines 1284 - 1286, The Copy instruction
currently clones an Option<PyStackRef> and will propagate any borrowed tag (via
self.state.stack[...] .clone()) leading to multiple borrowed refs; either add a
clear comment in the Copy handling documenting the invariant that all copies
must be consumed within the same basic block if borrowed refs are allowed, or
change the copy behavior to promote borrowed refs to owned before pushing (i.e.,
when reading from self.state.stack use as_ref().map(|sr|
PyStackRef::new_owned(sr.to_pyobj())) to produce an owned PyStackRef and then
call push_stackref_opt with that owned value to avoid creating multiple borrowed
refs; reference the Copy handling, self.state.stack, push_stackref_opt,
push_borrowed, and PyStackRef symbols when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/vm/src/frame.rs`:
- Around line 1284-1286: The Copy instruction currently clones an
Option<PyStackRef> and will propagate any borrowed tag (via
self.state.stack[...] .clone()) leading to multiple borrowed refs; either add a
clear comment in the Copy handling documenting the invariant that all copies
must be consumed within the same basic block if borrowed refs are allowed, or
change the copy behavior to promote borrowed refs to owned before pushing (i.e.,
when reading from self.state.stack use as_ref().map(|sr|
PyStackRef::new_owned(sr.to_pyobj())) to produce an owned PyStackRef and then
call push_stackref_opt with that owned value to avoid creating multiple borrowed
refs; reference the Copy handling, self.state.stack, push_stackref_opt,
push_borrowed, and PyStackRef symbols when making the change.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c25fc7 and e6b1810.

📒 Files selected for processing (2)
  • crates/vm/src/frame.rs
  • crates/vm/src/object/core.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/vm/src/object/core.rs

@youknowone youknowone merged commit 5eadae8 into RustPython:main Mar 1, 2026
12 of 13 checks passed
@youknowone youknowone deleted the worktree-pystackref branch March 1, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant