Skip to content

Commit 968372a

Browse files
committed
Add datastack-backed FastLocals for non-generator frames
Introduce FastLocalsData enum with Heap and DataStack variants so non-generator/coroutine frames allocate localsplus on the VM datastack instead of the heap. Includes materialize_to_heap for migration when needed (e.g. generator suspension).
1 parent d464119 commit 968372a

File tree

2 files changed

+122
-11
lines changed

2 files changed

+122
-11
lines changed

crates/vm/src/builtins/function.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,10 @@ impl PyFunction {
419419
.zip(&*code.varnames)
420420
.skip(code.arg_count as usize)
421421
.take(code.kwonlyarg_count as usize)
422-
.filter(|(slot, _)| slot.is_none())
423422
{
423+
if slot.is_some() {
424+
continue;
425+
}
424426
if let Some(defaults) = &get_defaults!().1
425427
&& let Some(default) = defaults.get_item_opt(&**kwarg, vm)?
426428
{
@@ -563,7 +565,6 @@ impl Py<PyFunction> {
563565
let is_gen = code.flags.contains(bytecode::CodeFlags::GENERATOR);
564566
let is_coro = code.flags.contains(bytecode::CodeFlags::COROUTINE);
565567
let use_datastack = !(is_gen || is_coro);
566-
567568
// Construct frame:
568569
let frame = Frame::new(
569570
code,

crates/vm/src/frame.rs

Lines changed: 119 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,16 @@ impl FrameOwner {
112112
/// if acquired, the frame is not executing and access is exclusive; if not,
113113
/// the caller is on the same thread as `with_exec()` (trace callback) and
114114
/// access is safe because frame execution is single-threaded.
115+
enum FastLocalsData {
116+
Heap(Box<[Option<PyObjectRef>]>),
117+
DataStack {
118+
ptr: *mut Option<PyObjectRef>,
119+
len: usize,
120+
},
121+
}
122+
115123
pub struct FastLocals {
116-
inner: UnsafeCell<Box<[Option<PyObjectRef>]>>,
124+
inner: UnsafeCell<FastLocalsData>,
117125
}
118126

119127
// SAFETY: Frame execution is serialized by the state mutex.
@@ -125,31 +133,96 @@ unsafe impl Sync for FastLocals {}
125133
impl FastLocals {
126134
fn new(data: Box<[Option<PyObjectRef>]>) -> Self {
127135
Self {
128-
inner: UnsafeCell::new(data),
136+
inner: UnsafeCell::new(FastLocalsData::Heap(data)),
137+
}
138+
}
139+
140+
fn new_on_datastack(len: usize, vm: &VirtualMachine) -> Self {
141+
let byte_size = len
142+
.checked_mul(core::mem::size_of::<Option<PyObjectRef>>())
143+
.expect("FastLocals byte size overflow");
144+
let ptr = vm.datastack_push(byte_size) as *mut Option<PyObjectRef>;
145+
for i in 0..len {
146+
// SAFETY: `ptr` points to `len` contiguous slots from this thread's datastack.
147+
unsafe { ptr.add(i).write(None) };
148+
}
149+
Self {
150+
inner: UnsafeCell::new(FastLocalsData::DataStack { ptr, len }),
151+
}
152+
}
153+
154+
/// Migrate data-stack-backed fastlocals to the heap and return the
155+
/// datastack base pointer for `VirtualMachine::datastack_pop`.
156+
///
157+
/// # Safety
158+
/// Caller must ensure exclusive access and pop the returned base pointer
159+
/// in LIFO order on the owning VM.
160+
pub unsafe fn materialize_to_heap(&self) -> Option<*mut u8> {
161+
let inner = unsafe { &mut *self.inner.get() };
162+
let (ptr, len) = match inner {
163+
FastLocalsData::Heap(_) => return None,
164+
FastLocalsData::DataStack { ptr, len } => (*ptr, *len),
165+
};
166+
let base = ptr as *mut u8;
167+
let mut data = Vec::with_capacity(len);
168+
for i in 0..len {
169+
// SAFETY: `ptr` is valid for `len` initialized slots.
170+
data.push(unsafe { ptr.add(i).read() });
129171
}
172+
*inner = FastLocalsData::Heap(data.into_boxed_slice());
173+
Some(base)
130174
}
131175

132176
/// # Safety
133177
/// Caller must ensure exclusive access (frame state locked or frame
134178
/// not executing).
135179
#[inline(always)]
136180
pub unsafe fn borrow(&self) -> &[Option<PyObjectRef>] {
137-
unsafe { &*self.inner.get() }
181+
match unsafe { &*self.inner.get() } {
182+
FastLocalsData::Heap(data) => data,
183+
FastLocalsData::DataStack { ptr, len } => {
184+
// SAFETY: `ptr` points to `len` initialized slots.
185+
unsafe { core::slice::from_raw_parts(*ptr, *len) }
186+
}
187+
}
138188
}
139189

140190
/// # Safety
141191
/// Caller must ensure exclusive mutable access.
142192
#[inline(always)]
143193
#[allow(clippy::mut_from_ref)]
144194
pub unsafe fn borrow_mut(&self) -> &mut [Option<PyObjectRef>] {
145-
unsafe { &mut *self.inner.get() }
195+
match unsafe { &mut *self.inner.get() } {
196+
FastLocalsData::Heap(data) => data,
197+
FastLocalsData::DataStack { ptr, len } => {
198+
// SAFETY: `ptr` points to `len` initialized slots.
199+
unsafe { core::slice::from_raw_parts_mut(*ptr, *len) }
200+
}
201+
}
202+
}
203+
}
204+
205+
impl Drop for FastLocals {
206+
fn drop(&mut self) {
207+
let inner = unsafe { &mut *self.inner.get() };
208+
if let FastLocalsData::DataStack { ptr, len } = inner {
209+
for i in 0..*len {
210+
// SAFETY: `ptr` points to `len` initialized slots.
211+
unsafe { core::ptr::drop_in_place(ptr.add(i)) };
212+
}
213+
}
146214
}
147215
}
148216

149217
unsafe impl Traverse for FastLocals {
150218
fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
151219
// SAFETY: GC runs on the same thread; no concurrent mutation.
152-
let data = unsafe { &*self.inner.get() };
220+
let data: &[Option<PyObjectRef>] = match unsafe { &*self.inner.get() } {
221+
FastLocalsData::Heap(data) => data,
222+
FastLocalsData::DataStack { ptr, len } => unsafe {
223+
core::slice::from_raw_parts(*ptr, *len)
224+
},
225+
};
153226
data.traverse(traverse_fn);
154227
}
155228
}
@@ -322,6 +395,7 @@ impl Frame {
322395
builtins: PyObjectRef,
323396
closure: &[PyCellRef],
324397
func_obj: Option<PyObjectRef>,
398+
use_datastack: bool,
325399
vm: &VirtualMachine,
326400
) -> Self {
327401
let nlocals = code.varnames.len();
@@ -336,11 +410,19 @@ impl Frame {
336410

337411
// Extend fastlocals to include varnames + cellvars + freevars (localsplus)
338412
let total_locals = nlocals + num_cells + nfrees;
339-
let mut fastlocals_vec: Vec<Option<PyObjectRef>> = vec![None; total_locals];
413+
let fastlocals = if use_datastack {
414+
FastLocals::new_on_datastack(total_locals, vm)
415+
} else {
416+
FastLocals::new(vec![None; total_locals].into_boxed_slice())
417+
};
340418

341419
// Store cell objects at cellvars and freevars positions
342-
for (i, cell) in cells_frees.iter().enumerate() {
343-
fastlocals_vec[nlocals + i] = Some(cell.clone().into());
420+
{
421+
// SAFETY: Frame is under construction and not visible to others yet.
422+
let slots = unsafe { fastlocals.borrow_mut() };
423+
for (i, cell) in cells_frees.iter().enumerate() {
424+
slots[nlocals + i] = Some(cell.clone().into());
425+
}
344426
}
345427

346428
let state = FrameState {
@@ -350,7 +432,7 @@ impl Frame {
350432
};
351433

352434
Self {
353-
fastlocals: FastLocals::new(fastlocals_vec.into_boxed_slice()),
435+
fastlocals,
354436
locals: match scope.locals {
355437
Some(locals) => FrameLocals::with_locals(locals),
356438
None if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) => FrameLocals::lazy(),
@@ -377,6 +459,34 @@ impl Frame {
377459
}
378460
}
379461

462+
/// Access fastlocals immutably.
463+
///
464+
/// # Safety
465+
/// Caller must ensure no concurrent mutable access.
466+
#[inline(always)]
467+
pub unsafe fn fastlocals(&self) -> &[Option<PyObjectRef>] {
468+
unsafe { self.fastlocals.borrow() }
469+
}
470+
471+
/// Access fastlocals mutably.
472+
///
473+
/// # Safety
474+
/// Caller must ensure exclusive access.
475+
#[inline(always)]
476+
#[allow(clippy::mut_from_ref)]
477+
pub unsafe fn fastlocals_mut(&self) -> &mut [Option<PyObjectRef>] {
478+
unsafe { self.fastlocals.borrow_mut() }
479+
}
480+
481+
/// Migrate data-stack-backed fastlocals to heap storage and return the
482+
/// datastack base pointer for `VirtualMachine::datastack_pop`.
483+
///
484+
/// # Safety
485+
/// Caller must ensure frame is not executing.
486+
pub(crate) unsafe fn materialize_localsplus(&self) -> Option<*mut u8> {
487+
unsafe { self.fastlocals.materialize_to_heap() }
488+
}
489+
380490
/// Clear evaluation stack and state-owned cell/free references.
381491
/// For full local/cell cleanup, call `clear_locals_and_stack()`.
382492
pub(crate) fn clear_stack_and_cells(&self) {

0 commit comments

Comments
 (0)