@@ -1050,16 +1050,19 @@ impl<'a> FB<'a> {
10501050 /// entry: key exprs (trap per input row), ProbeRange -> [lo, hi)
10511051 /// (NULL keys force an EMPTY range — a NULL never matches),
10521052 /// jump header(lo, hi, any=false)
1053- /// header(i, end, any): i < end ? body(i, end, any) : after(any)
1054- /// body: ProbeRead lanes; residual AND WHERE (3VL) gate BEFORE any
1055- /// store (the continue path must be store-free); pass ->
1056- /// store_blk(lanes.., i+1, end) -> stores -> emit.to header
1057- /// (any=true); fail -> header(i+1, end, any)
1053+ /// header(i, end, any): i < end ? body : after(any)
1054+ /// body: ProbeRead lanes; the RESIDUAL gates match-ness (and the
1055+ /// `any` flag); WHERE only gates emission (DuckDB joins
1056+ /// first, filters second); pass -> stores -> emit.to header
1057+ /// (any=true); fail -> continue.
10581058 /// after: INNER -> skip. LEFT -> any ? skip : null-extension row
10591059 /// (default lanes, hit=false) gated by WHERE, then emit.
10601060 ///
1061- /// The engine's documented emission order falls out: probe rows in
1062- /// input order, matches contiguous in build INSERTION order.
1061+ /// Predicate/expression emission may SPLIT blocks (CASE machinery), so
1062+ /// the loop state and probe lanes ride the LIVE stack (auto-rebound
1063+ /// across splits) with the invariant: the probe lanes are always the
1064+ /// LAST `nd` live entries; the per-block probe cache is re-seeded from
1065+ /// them before every emission that can reference join columns.
10631066 fn lower_many_loop (
10641067 & mut self ,
10651068 exprs : & [ ( String , SExpr ) ] ,
@@ -1071,9 +1074,10 @@ impl<'a> FB<'a> {
10711074 let residual = self . joins [ 0 ] . residual . clone ( ) ;
10721075 let keys_expr = self . joins [ 0 ] . keys . clone ( ) ;
10731076 let dst_tys: Vec < Ty > = self . val_flat_tys ( j) ;
1077+ let nd = dst_tys. len ( ) ;
10741078
10751079 // entry: keys + range.
1076- let mut live = Vec :: new ( ) ;
1080+ let mut live: Live = Vec :: new ( ) ;
10771081 let mut keys_valid: Option < Value > = None ;
10781082 let mut key_vals = Vec :: with_capacity ( keys_expr. len ( ) ) ;
10791083 for key in & keys_expr {
@@ -1140,93 +1144,99 @@ impl<'a> FB<'a> {
11401144 else_args : vec ! [ h_any] ,
11411145 } ) ;
11421146
1143- // body: lanes + gate.
1147+ // body: lanes + the two gates, everything riding live as
1148+ // [inext, end, any, dsts...].
11441149 self . switch ( body) ;
11451150 let ( b_i, b_end, b_any) = ( bp[ 0 ] , bp[ 1 ] , bp[ 2 ] ) ;
1146- let dsts: Vec < Value > = ( 0 ..dst_tys . len ( ) ) . map ( |_| self . fresh ( ) ) . collect ( ) ;
1151+ let dsts: Vec < Value > = ( 0 ..nd ) . map ( |_| self . fresh ( ) ) . collect ( ) ;
11471152 self . inst ( Inst :: ProbeRead {
11481153 static_id : j,
11491154 idx : b_i,
11501155 dsts : dsts. clone ( ) ,
11511156 } ) ;
1152- let t1 = self . const_i1 ( true ) ;
1153- self . blocks [ self . cur ] . probes . insert ( j, ( t1, dsts. clone ( ) ) ) ;
11541157 let one = self . const_lit ( Lit :: I64 ( 1 ) ) ;
11551158 let inext = self . bin ( BinOp :: Iadd , b_i, one) ;
1156- // Gate 1 — the RESIDUAL decides match-ness (and therefore `any`);
1157- // a residual-failed candidate continues with `any` UNCHANGED.
1158- let mut match_tys = dst_tys. clone ( ) ;
1159- match_tys. push ( Ty :: I64 ) ; // inext
1160- match_tys. push ( Ty :: I64 ) ; // end
1159+ let mut live: Live = Vec :: new ( ) ;
1160+ live. push ( ( Lane { flag : None , val : inext } , Ty :: I64 ) ) ;
1161+ live. push ( ( Lane { flag : None , val : b_end } , Ty :: I64 ) ) ;
1162+ live. push ( ( Lane { flag : None , val : b_any } , Ty :: I1 ) ) ;
1163+ for ( & d, & ty) in dsts. iter ( ) . zip ( dst_tys. iter ( ) ) {
1164+ live. push ( ( Lane { flag : None , val : d } , ty) ) ;
1165+ }
1166+ let rv = match & residual {
1167+ None => None ,
1168+ Some ( res) => {
1169+ self . reseed_many ( j, true , & live, nd) ;
1170+ let rl = self . emit ( res, & mut live) ?;
1171+ Some ( self . truthy ( rl) )
1172+ }
1173+ } ;
1174+ let vals: Vec < Value > = live. iter ( ) . map ( |( l, _) | l. val ) . collect ( ) ;
1175+ let ( r_inext, r_end, r_any) = ( vals[ 0 ] , vals[ 1 ] , vals[ 2 ] ) ;
1176+ let r_dsts: Vec < Value > = vals[ 3 ..] . to_vec ( ) ;
1177+ live. clear ( ) ;
1178+ let mut match_tys = vec ! [ Ty :: I64 , Ty :: I64 ] ;
1179+ match_tys. extend ( dst_tys. iter ( ) . copied ( ) ) ;
11611180 let ( matched_blk, mp) = self . create_block ( & match_tys) ;
1162- let mut match_args: Vec < Value > = dsts. clone ( ) ;
1163- match_args. push ( inext) ;
1164- match_args. push ( b_end) ;
1165- match & residual {
1181+ let mut match_args = vec ! [ r_inext, r_end] ;
1182+ match_args. extend ( r_dsts. iter ( ) . copied ( ) ) ;
1183+ match rv {
11661184 None => self . term ( Term :: Jump {
11671185 to : BlockId ( matched_blk as u32 ) ,
11681186 args : match_args,
11691187 } ) ,
1170- Some ( res) => {
1171- let mut live = Vec :: new ( ) ;
1172- let rl = self . emit ( res, & mut live) ?;
1173- let rv = self . truthy ( rl) ;
1174- self . term ( Term :: Brif {
1175- cond : rv,
1176- then_to : BlockId ( matched_blk as u32 ) ,
1177- then_args : match_args,
1178- else_to : BlockId ( header as u32 ) ,
1179- else_args : vec ! [ inext, b_end, b_any] ,
1180- } ) ;
1181- }
1188+ Some ( cond) => self . term ( Term :: Brif {
1189+ cond,
1190+ then_to : BlockId ( matched_blk as u32 ) ,
1191+ then_args : match_args,
1192+ else_to : BlockId ( header as u32 ) ,
1193+ else_args : vec ! [ r_inext, r_end, r_any] ,
1194+ } ) ,
11821195 }
11831196
1184- // matched_blk: `any` is TRUE from here on, WHERE only gates the
1185- // emission (DuckDB joins first, filters second — a match killed by
1186- // WHERE still suppresses the LEFT null-extension).
1197+ // matched_blk: `any` is TRUE from here on; WHERE gates emission
1198+ // only. live = [inext, end, dsts...].
11871199 self . switch ( matched_blk) ;
1188- let m_dsts: Vec < Value > = mp[ ..dst_tys. len ( ) ] . to_vec ( ) ;
1189- let ( m_inext, m_end) = ( mp[ dst_tys. len ( ) ] , mp[ dst_tys. len ( ) + 1 ] ) ;
1190- let tm = self . const_i1 ( true ) ;
1191- self . blocks [ self . cur ] . probes . insert ( j, ( tm, m_dsts. clone ( ) ) ) ;
1192- let mut store_tys = dst_tys. clone ( ) ;
1193- store_tys. push ( Ty :: I64 ) ; // inext
1194- store_tys. push ( Ty :: I64 ) ; // end
1195- let ( store_blk, sp) = self . create_block ( & store_tys) ;
1196- let mut store_args: Vec < Value > = m_dsts. clone ( ) ;
1197- store_args. push ( m_inext) ;
1198- store_args. push ( m_end) ;
1199- match filter_pred {
1200- None => self . term ( Term :: Jump {
1201- to : BlockId ( store_blk as u32 ) ,
1202- args : store_args,
1203- } ) ,
1204- Some ( pred) => {
1205- let mut live = Vec :: new ( ) ;
1206- let pl = self . emit ( pred, & mut live) ?;
1207- let pv = self . truthy ( pl) ;
1208- let tt = self . const_i1 ( true ) ;
1209- self . term ( Term :: Brif {
1210- cond : pv,
1211- then_to : BlockId ( store_blk as u32 ) ,
1212- then_args : store_args,
1213- else_to : BlockId ( header as u32 ) ,
1214- else_args : vec ! [ m_inext, m_end, tt] ,
1215- } ) ;
1200+ let mut live: Live = Vec :: new ( ) ;
1201+ live. push ( ( Lane { flag : None , val : mp[ 0 ] } , Ty :: I64 ) ) ;
1202+ live. push ( ( Lane { flag : None , val : mp[ 1 ] } , Ty :: I64 ) ) ;
1203+ for ( & d, & ty) in mp[ 2 ..] . iter ( ) . zip ( dst_tys. iter ( ) ) {
1204+ live. push ( ( Lane { flag : None , val : d } , ty) ) ;
1205+ }
1206+ if let Some ( pred) = filter_pred {
1207+ self . reseed_many ( j, true , & live, nd) ;
1208+ let pl = self . emit ( pred, & mut live) ?;
1209+ let pv = self . truthy ( pl) ;
1210+ let vals: Vec < Value > = live. iter ( ) . map ( |( l, _) | l. val ) . collect ( ) ;
1211+ let ( keep, kp) = {
1212+ let mut tys = vec ! [ Ty :: I64 , Ty :: I64 ] ;
1213+ tys. extend ( dst_tys. iter ( ) . copied ( ) ) ;
1214+ self . create_block ( & tys)
1215+ } ;
1216+ let mut keep_args = vec ! [ vals[ 0 ] , vals[ 1 ] ] ;
1217+ keep_args. extend ( vals[ 2 ..] . iter ( ) . copied ( ) ) ;
1218+ let tt = self . const_i1 ( true ) ;
1219+ self . term ( Term :: Brif {
1220+ cond : pv,
1221+ then_to : BlockId ( keep as u32 ) ,
1222+ then_args : keep_args,
1223+ else_to : BlockId ( header as u32 ) ,
1224+ else_args : vec ! [ vals[ 0 ] , vals[ 1 ] , tt] ,
1225+ } ) ;
1226+ self . switch ( keep) ;
1227+ live. clear ( ) ;
1228+ live. push ( ( Lane { flag : None , val : kp[ 0 ] } , Ty :: I64 ) ) ;
1229+ live. push ( ( Lane { flag : None , val : kp[ 1 ] } , Ty :: I64 ) ) ;
1230+ for ( & d, & ty) in kp[ 2 ..] . iter ( ) . zip ( dst_tys. iter ( ) ) {
1231+ live. push ( ( Lane { flag : None , val : d } , ty) ) ;
12161232 }
12171233 }
1218-
1219- // store_blk: seeded lanes -> stores -> emit.to header(any=true).
1220- self . switch ( store_blk) ;
1221- let s_dsts: Vec < Value > = sp[ ..dst_tys. len ( ) ] . to_vec ( ) ;
1222- let ( s_inext, s_end) = ( sp[ dst_tys. len ( ) ] , sp[ dst_tys. len ( ) + 1 ] ) ;
1223- let t2 = self . const_i1 ( true ) ;
1224- self . blocks [ self . cur ] . probes . insert ( j, ( t2, s_dsts) ) ;
1225- self . store_out_row ( exprs, out_cols) ?;
1234+ self . store_out_row ( exprs, out_cols, Some ( ( j, true ) ) , & mut live, nd) ?;
1235+ let vals: Vec < Value > = live. iter ( ) . map ( |( l, _) | l. val ) . collect ( ) ;
12261236 let t3 = self . const_i1 ( true ) ;
12271237 self . term ( Term :: EmitTo {
12281238 to : BlockId ( header as u32 ) ,
1229- args : vec ! [ s_inext , s_end , t3] ,
1239+ args : vec ! [ vals [ 0 ] , vals [ 1 ] , t3] ,
12301240 } ) ;
12311241
12321242 // after: LEFT null-extension or skip.
@@ -1243,32 +1253,36 @@ impl<'a> FB<'a> {
12431253 else_args : vec ! [ ] ,
12441254 } ) ;
12451255 self . switch ( miss) ;
1256+ // live = [dsts(defaults)...] only.
1257+ let mut live: Live = Vec :: new ( ) ;
1258+ for & ty in dst_tys. iter ( ) {
1259+ let d = self . default_of ( ty) ;
1260+ live. push ( ( Lane { flag : None , val : d } , ty) ) ;
1261+ }
12461262 if let Some ( pred) = filter_pred {
12471263 // WHERE sees the null-extended row too (measured).
1248- let fmiss = self . const_i1 ( false ) ;
1249- let defaults: Vec < Value > =
1250- dst_tys. iter ( ) . map ( |& ty| self . default_of ( ty) ) . collect ( ) ;
1251- self . blocks [ self . cur ] . probes . insert ( j, ( fmiss, defaults) ) ;
1252- let mut live = Vec :: new ( ) ;
1264+ self . reseed_many ( j, false , & live, nd) ;
12531265 let pl = self . emit ( pred, & mut live) ?;
12541266 let pv = self . truthy ( pl) ;
1255- let ( keep, _) = self . create_block ( & [ ] ) ;
1267+ let vals: Vec < Value > = live. iter ( ) . map ( |( l, _) | l. val ) . collect ( ) ;
1268+ let ( keep, kp) = self . create_block ( & dst_tys) ;
12561269 let ( drop, _) = self . create_block ( & [ ] ) ;
12571270 self . term ( Term :: Brif {
12581271 cond : pv,
12591272 then_to : BlockId ( keep as u32 ) ,
1260- then_args : vec ! [ ] ,
1273+ then_args : vals ,
12611274 else_to : BlockId ( drop as u32 ) ,
12621275 else_args : vec ! [ ] ,
12631276 } ) ;
12641277 self . switch ( drop) ;
12651278 self . term ( Term :: Skip ) ;
12661279 self . switch ( keep) ;
1280+ live. clear ( ) ;
1281+ for ( & d, & ty) in kp. iter ( ) . zip ( dst_tys. iter ( ) ) {
1282+ live. push ( ( Lane { flag : None , val : d } , ty) ) ;
1283+ }
12671284 }
1268- let fmiss2 = self . const_i1 ( false ) ;
1269- let defaults2: Vec < Value > = dst_tys. iter ( ) . map ( |& ty| self . default_of ( ty) ) . collect ( ) ;
1270- self . blocks [ self . cur ] . probes . insert ( j, ( fmiss2, defaults2) ) ;
1271- self . store_out_row ( exprs, out_cols) ?;
1285+ self . store_out_row ( exprs, out_cols, Some ( ( j, false ) ) , & mut live, nd) ?;
12721286 self . term ( Term :: Emit ) ;
12731287 self . switch ( done) ;
12741288 self . term ( Term :: Skip ) ;
@@ -1278,17 +1292,32 @@ impl<'a> FB<'a> {
12781292 Ok ( ( ) )
12791293 }
12801294
1281- /// Emit every output expression and store it (the shared tail of both
1282- /// lowerings) — the current block's caches must already be seeded.
1295+ /// Re-seed the CURRENT block's probe cache for many-join `j` from the
1296+ /// live stack's trailing `nd` lanes (the invariant of the loop
1297+ /// lowering) with a fresh hit constant.
1298+ fn reseed_many ( & mut self , j : u32 , hit : bool , live : & Live , nd : usize ) {
1299+ let h = self . const_i1 ( hit) ;
1300+ let ds: Vec < Value > = live[ live. len ( ) - nd..] . iter ( ) . map ( |( l, _) | l. val ) . collect ( ) ;
1301+ self . blocks [ self . cur ] . probes . insert ( j, ( h, ds) ) ;
1302+ }
1303+
1304+ /// Emit every output expression and store it. Under a many-join
1305+ /// (`seed` present) the probe cache is re-seeded from `live`'s
1306+ /// trailing `nd` lanes before EVERY expression — emission may split
1307+ /// blocks, and each new block starts with an empty cache.
12831308 fn store_out_row (
12841309 & mut self ,
12851310 exprs : & [ ( String , SExpr ) ] ,
12861311 out_cols : & [ Col ] ,
1312+ seed : Option < ( u32 , bool ) > ,
1313+ live : & mut Live ,
1314+ nd : usize ,
12871315 ) -> Result < ( ) , PrepareError > {
1288- let mut live = Vec :: new ( ) ;
12891316 for ( ci, ( _, e) ) in exprs. iter ( ) . enumerate ( ) {
1290- debug_assert ! ( live. is_empty( ) ) ;
1291- let lane = self . emit ( e, & mut live) ?;
1317+ if let Some ( ( j, hit) ) = seed {
1318+ self . reseed_many ( j, hit, live, nd) ;
1319+ }
1320+ let lane = self . emit ( e, live) ?;
12921321 let col = ci as u32 ;
12931322 if out_cols[ ci] . ty . nullable {
12941323 let flag = match lane. flag {
0 commit comments