@@ -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+
115123pub 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 {}
125133impl 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
149217unsafe 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