@@ -33,6 +33,7 @@ use crate::AnyCanonical;
3333use crate :: ArrayRef ;
3434use crate :: Canonical ;
3535use crate :: IntoArray ;
36+ use crate :: dtype:: DType ;
3637use crate :: matcher:: Matcher ;
3738use crate :: memory:: HostAllocatorRef ;
3839use crate :: memory:: MemorySessionExt ;
@@ -107,22 +108,21 @@ impl ArrayRef {
107108 /// maximum (default 128, override with `VORTEX_MAX_ITERATIONS`).
108109 pub fn execute_until < M : Matcher > ( self , ctx : & mut ExecutionCtx ) -> VortexResult < ArrayRef > {
109110 let mut current = self . optimize ( ) ?;
110- // Stack frames: (parent, slot_idx, done_predicate_for_slot)
111- let mut stack: Vec < ( ArrayRef , usize , DonePredicate ) > = Vec :: new ( ) ;
111+ let mut stack: Vec < StackFrame > = Vec :: new ( ) ;
112112
113113 for _ in 0 ..max_iterations ( ) {
114114 // Step 1: done / canonical — splice back into stacked parent or return.
115115 let is_done = stack
116116 . last ( )
117- . map_or ( M :: matches as DonePredicate , |frame| frame. 2 ) ;
117+ . map_or ( M :: matches as DonePredicate , |frame| frame. done ) ;
118118 if is_done ( & current) || AnyCanonical :: matches ( & current) {
119119 match stack. pop ( ) {
120120 None => {
121121 ctx. log ( format_args ! ( "-> {}" , current) ) ;
122122 return Ok ( current) ;
123123 }
124- Some ( ( parent , slot_idx , _ ) ) => {
125- current = parent . with_slot ( slot_idx , current) ?. optimize ( ) ?;
124+ Some ( frame ) => {
125+ current = frame . put_back ( current) ?. optimize ( ) ?;
126126 continue ;
127127 }
128128 }
@@ -139,8 +139,8 @@ impl ArrayRef {
139139 current, rewritten
140140 ) ) ;
141141 current = rewritten. optimize ( ) ?;
142- if let Some ( ( parent , slot_idx , _ ) ) = stack. pop ( ) {
143- current = parent . with_slot ( slot_idx , current) ?. optimize ( ) ?;
142+ if let Some ( frame ) = stack. pop ( ) {
143+ current = frame . put_back ( current) ?. optimize ( ) ?;
144144 }
145145 continue ;
146146 }
@@ -150,14 +150,15 @@ impl ArrayRef {
150150 let ( array, step) = result. into_parts ( ) ;
151151 match step {
152152 ExecutionStep :: ExecuteSlot ( i, done) => {
153- let child = array . slots ( ) [ i ]
154- . clone ( )
155- . vortex_expect ( "ExecuteSlot index in bounds" ) ;
153+ // SAFETY: we record the child's dtype and len, and assert they are preserved
154+ // when the slot is put back via `put_slot_unchecked`.
155+ let ( parent , child ) = unsafe { array . take_slot_unchecked ( i ) } ? ;
156156 ctx. log ( format_args ! (
157157 "ExecuteSlot({i}): pushing {}, focusing on {}" ,
158- array , child
158+ parent , child
159159 ) ) ;
160- stack. push ( ( array, i, done) ) ;
160+ let frame = StackFrame :: new ( parent, i, done, & child) ;
161+ stack. push ( frame) ;
161162 current = child. optimize ( ) ?;
162163 }
163164 ExecutionStep :: Done => {
@@ -174,6 +175,49 @@ impl ArrayRef {
174175 }
175176}
176177
178+ /// A stack frame for the iterative executor, tracking the parent array whose slot is being
179+ /// executed and the original child's dtype/len for validation on put-back.
180+ struct StackFrame {
181+ parent : ArrayRef ,
182+ slot_idx : usize ,
183+ done : DonePredicate ,
184+ original_dtype : DType ,
185+ original_len : usize ,
186+ }
187+
188+ impl StackFrame {
189+ fn new ( parent : ArrayRef , slot_idx : usize , done : DonePredicate , child : & ArrayRef ) -> Self {
190+ Self {
191+ parent,
192+ slot_idx,
193+ done,
194+ original_dtype : child. dtype ( ) . clone ( ) ,
195+ original_len : child. len ( ) ,
196+ }
197+ }
198+
199+ fn put_back ( self , replacement : ArrayRef ) -> VortexResult < ArrayRef > {
200+ debug_assert_eq ! (
201+ replacement. dtype( ) ,
202+ & self . original_dtype,
203+ "slot {} dtype changed from {} to {} during execution" ,
204+ self . slot_idx,
205+ self . original_dtype,
206+ replacement. dtype( )
207+ ) ;
208+ debug_assert_eq ! (
209+ replacement. len( ) ,
210+ self . original_len,
211+ "slot {} len changed from {} to {} during execution" ,
212+ self . slot_idx,
213+ self . original_len,
214+ replacement. len( )
215+ ) ;
216+ // SAFETY: we assert above that dtype and len are preserved.
217+ unsafe { self . parent . put_slot_unchecked ( self . slot_idx , replacement) }
218+ }
219+ }
220+
177221/// Execution context for batch CPU compute.
178222#[ derive( Debug , Clone ) ]
179223pub struct ExecutionCtx {
0 commit comments