@@ -53,6 +53,8 @@ struct FrameState {
5353 // We need 1 stack per frame
5454 /// The main data frame of the stack machine
5555 stack : BoxVec < Option < PyObjectRef > > ,
56+ /// Cell and free variable references (cellvars + freevars).
57+ cells_frees : Box < [ PyCellRef ] > ,
5658 /// index of last instruction ran
5759 #[ cfg( feature = "threading" ) ]
5860 lasti : u32 ,
@@ -93,7 +95,6 @@ pub struct Frame {
9395 pub func_obj : Option < PyObjectRef > ,
9496
9597 pub fastlocals : PyMutex < Box < [ Option < PyObjectRef > ] > > ,
96- pub ( crate ) cells_frees : Box < [ PyCellRef ] > ,
9798 pub locals : ArgMapping ,
9899 pub globals : PyDictRef ,
99100 pub builtins : PyObjectRef ,
@@ -135,6 +136,7 @@ impl PyPayload for Frame {
135136unsafe impl Traverse for FrameState {
136137 fn traverse ( & self , tracer_fn : & mut TraverseFn < ' _ > ) {
137138 self . stack . traverse ( tracer_fn) ;
139+ self . cells_frees . traverse ( tracer_fn) ;
138140 }
139141}
140142
@@ -143,7 +145,6 @@ unsafe impl Traverse for Frame {
143145 self . code . traverse ( tracer_fn) ;
144146 self . func_obj . traverse ( tracer_fn) ;
145147 self . fastlocals . traverse ( tracer_fn) ;
146- self . cells_frees . traverse ( tracer_fn) ;
147148 self . locals . traverse ( tracer_fn) ;
148149 self . globals . traverse ( tracer_fn) ;
149150 self . builtins . traverse ( tracer_fn) ;
@@ -193,13 +194,13 @@ impl Frame {
193194
194195 let state = FrameState {
195196 stack : BoxVec :: new ( code. max_stackdepth as usize ) ,
197+ cells_frees,
196198 #[ cfg( feature = "threading" ) ]
197199 lasti : 0 ,
198200 } ;
199201
200202 Self {
201203 fastlocals : PyMutex :: new ( fastlocals_vec. into_boxed_slice ( ) ) ,
202- cells_frees,
203204 locals : scope. locals ,
204205 globals : scope. globals ,
205206 builtins,
@@ -218,21 +219,39 @@ impl Frame {
218219 }
219220 }
220221
221- /// Clear the evaluation stack. Used by frame.clear() to break reference cycles.
222- pub ( crate ) fn clear_value_stack ( & self ) {
223- self . state . lock ( ) . stack . clear ( ) ;
222+ /// Clear the evaluation stack and drop all cell/free variable references.
223+ pub ( crate ) fn clear_stack_and_cells ( & self ) {
224+ let mut state = self . state . lock ( ) ;
225+ state. stack . clear ( ) ;
226+ let _old = core:: mem:: take ( & mut state. cells_frees ) ;
224227 }
225228
226229 /// Clear locals and stack after generator/coroutine close.
227230 /// Releases references held by the frame, matching _PyFrame_ClearLocals.
228231 pub ( crate ) fn clear_locals_and_stack ( & self ) {
229- self . state . lock ( ) . stack . clear ( ) ;
232+ self . clear_stack_and_cells ( ) ;
230233 let mut fastlocals = self . fastlocals . lock ( ) ;
231234 for slot in fastlocals. iter_mut ( ) {
232235 * slot = None ;
233236 }
234237 }
235238
239+ /// Get cell contents by cell index. Reads through fastlocals (no state lock needed).
240+ pub ( crate ) fn get_cell_contents ( & self , cell_idx : usize ) -> Option < PyObjectRef > {
241+ let nlocals = self . code . varnames . len ( ) ;
242+ let fastlocals = self . fastlocals . lock ( ) ;
243+ fastlocals
244+ . get ( nlocals + cell_idx)
245+ . and_then ( |slot| slot. as_ref ( ) )
246+ . and_then ( |obj| obj. downcast_ref :: < PyCell > ( ) )
247+ . and_then ( |cell| cell. get ( ) )
248+ }
249+
250+ /// Set cell contents by cell index. Only safe to call before frame execution starts.
251+ pub ( crate ) fn set_cell_contents ( & self , cell_idx : usize , value : Option < PyObjectRef > ) {
252+ self . state . lock ( ) . cells_frees [ cell_idx] . set ( value) ;
253+ }
254+
236255 /// Store a borrowed back-reference to the owning generator/coroutine.
237256 /// The caller must ensure the generator outlives the frame.
238257 pub fn set_generator ( & self , generator : & PyObject ) {
@@ -306,23 +325,25 @@ impl Frame {
306325 }
307326 }
308327 if !code. cellvars . is_empty ( ) || !code. freevars . is_empty ( ) {
309- let map_to_dict = |keys : & [ & PyStrInterned ] , values : & [ PyCellRef ] | {
310- for ( & k, v) in zip ( keys, values) {
311- if let Some ( value) = v. get ( ) {
312- locals. mapping ( ) . ass_subscript ( k, Some ( value) , vm) ?;
313- } else {
314- match locals. mapping ( ) . ass_subscript ( k, None , vm) {
315- Ok ( ( ) ) => { }
316- Err ( e) if e. fast_isinstance ( vm. ctx . exceptions . key_error ) => { }
317- Err ( e) => return Err ( e) ,
318- }
319- }
328+ // Access cells through fastlocals to avoid locking state
329+ // (state may be held by with_exec during frame execution).
330+ for ( i, & k) in code. cellvars . iter ( ) . enumerate ( ) {
331+ let cell_value = self . get_cell_contents ( i) ;
332+ match locals. mapping ( ) . ass_subscript ( k, cell_value, vm) {
333+ Ok ( ( ) ) => { }
334+ Err ( e) if e. fast_isinstance ( vm. ctx . exceptions . key_error ) => { }
335+ Err ( e) => return Err ( e) ,
320336 }
321- Ok ( ( ) )
322- } ;
323- map_to_dict ( & code. cellvars , & self . cells_frees ) ?;
337+ }
324338 if code. flags . contains ( bytecode:: CodeFlags :: OPTIMIZED ) {
325- map_to_dict ( & code. freevars , & self . cells_frees [ code. cellvars . len ( ) ..] ) ?;
339+ for ( i, & k) in code. freevars . iter ( ) . enumerate ( ) {
340+ let cell_value = self . get_cell_contents ( code. cellvars . len ( ) + i) ;
341+ match locals. mapping ( ) . ass_subscript ( k, cell_value, vm) {
342+ Ok ( ( ) ) => { }
343+ Err ( e) if e. fast_isinstance ( vm. ctx . exceptions . key_error ) => { }
344+ Err ( e) => return Err ( e) ,
345+ }
346+ }
326347 }
327348 }
328349 Ok ( locals. clone ( ) )
@@ -336,7 +357,6 @@ impl Py<Frame> {
336357 let exec = ExecutingFrame {
337358 code : & self . code ,
338359 fastlocals : & self . fastlocals ,
339- cells_frees : & self . cells_frees ,
340360 locals : & self . locals ,
341361 globals : & self . globals ,
342362 builtins : & self . builtins ,
@@ -382,7 +402,6 @@ impl Py<Frame> {
382402 let exec = ExecutingFrame {
383403 code : & self . code ,
384404 fastlocals : & self . fastlocals ,
385- cells_frees : & self . cells_frees ,
386405 locals : & self . locals ,
387406 globals : & self . globals ,
388407 builtins : & self . builtins ,
@@ -417,7 +436,6 @@ impl Py<Frame> {
417436struct ExecutingFrame < ' a > {
418437 code : & ' a PyRef < PyCode > ,
419438 fastlocals : & ' a PyMutex < Box < [ Option < PyObjectRef > ] > > ,
420- cells_frees : & ' a [ PyCellRef ] ,
421439 locals : & ' a ArgMapping ,
422440 globals : & ' a PyDictRef ,
423441 builtins : & ' a PyObjectRef ,
@@ -1021,7 +1039,7 @@ impl ExecutingFrame<'_> {
10211039 }
10221040 Instruction :: DeleteAttr { idx } => self . delete_attr ( vm, idx. get ( arg) ) ,
10231041 Instruction :: DeleteDeref ( i) => {
1024- self . cells_frees [ i. get ( arg) as usize ] . set ( None ) ;
1042+ self . state . cells_frees [ i. get ( arg) as usize ] . set ( None ) ;
10251043 Ok ( None )
10261044 }
10271045 Instruction :: DeleteFast ( idx) => {
@@ -1446,7 +1464,7 @@ impl ExecutingFrame<'_> {
14461464 } ;
14471465 self . push_value ( match value {
14481466 Some ( v) => v,
1449- None => self . cells_frees [ i]
1467+ None => self . state . cells_frees [ i]
14501468 . get ( )
14511469 . ok_or_else ( || self . unbound_cell_exception ( i, vm) ) ?,
14521470 } ) ;
@@ -1503,7 +1521,7 @@ impl ExecutingFrame<'_> {
15031521 }
15041522 Instruction :: LoadDeref ( i) => {
15051523 let idx = i. get ( arg) as usize ;
1506- let x = self . cells_frees [ idx]
1524+ let x = self . state . cells_frees [ idx]
15071525 . get ( )
15081526 . ok_or_else ( || self . unbound_cell_exception ( idx, vm) ) ?;
15091527 self . push_value ( x) ;
@@ -2093,7 +2111,7 @@ impl ExecutingFrame<'_> {
20932111 Instruction :: StoreAttr { idx } => self . store_attr ( vm, idx. get ( arg) ) ,
20942112 Instruction :: StoreDeref ( i) => {
20952113 let value = self . pop_value ( ) ;
2096- self . cells_frees [ i. get ( arg) as usize ] . set ( Some ( value) ) ;
2114+ self . state . cells_frees [ i. get ( arg) as usize ] . set ( Some ( value) ) ;
20972115 Ok ( None )
20982116 }
20992117 Instruction :: StoreFast ( idx) => {
0 commit comments