@@ -36,8 +36,8 @@ use super::super::ir::{
3636 BinOp , CmpPred , Inst , Lit , NumOp1 , Program , RoundMode , StaticTy , StrOp1 , Term , TrimSide , Ty ,
3737} ;
3838use super :: interp:: {
39- self , apply_ord, col_valid, substr_range_ok, substr_window, CompileError , DuckF64 , InterpFn ,
40- PreparedStatic ,
39+ self , apply_ord, col_valid, substr_range_ok, substr_window, trim_bounds , CompileError , DuckF64 ,
40+ InterpFn , PreparedStatic ,
4141} ;
4242use super :: { casemap, Arena , Batch , ColData , KeyBits , OutCol , RunState , ScalarVal , StrRef , Trap } ;
4343
@@ -72,16 +72,6 @@ impl Cx {
7272 fn statics ( & self ) -> & [ PreparedStatic ] {
7373 unsafe { std:: slice:: from_raw_parts ( self . statics , self . statics_len ) }
7474 }
75- fn get ( & mut self , off : i64 , len : i64 ) -> String {
76- // Owned copy: several helpers read one span and push another; a
77- // borrow across arena mutation is not expressible here.
78- self . arena ( )
79- . get ( StrRef {
80- off : off as usize ,
81- len : len as usize ,
82- } )
83- . to_string ( )
84- }
8575 fn set_trap ( & mut self , msg : String ) {
8676 self . trap_flag = 1 ;
8777 self . trap = Some ( Trap ( msg) ) ;
@@ -218,8 +208,18 @@ extern "C" fn h_fcmp(a: f64, b: f64, pred: i64) -> u8 {
218208
219209extern "C" fn h_scmp ( p : * mut Cx , ao : i64 , al : i64 , bo : i64 , bl : i64 , pred : i64 ) -> u8 {
220210 let c = unsafe { cx ( p) } ;
221- let ( a, b) = ( c. get ( ao, al) , c. get ( bo, bl) ) ;
222- apply_ord ( decode_pred ( pred) , a. as_str ( ) . cmp ( b. as_str ( ) ) ) as u8
211+ // Comparison only reads — both spans borrow the arena immutably.
212+ let arena = unsafe { & * c. arena } ;
213+ let a = arena. get ( span ( ao, al) ) ;
214+ let b = arena. get ( span ( bo, bl) ) ;
215+ apply_ord ( decode_pred ( pred) , a. cmp ( b) ) as u8
216+ }
217+
218+ fn span ( off : i64 , len : i64 ) -> StrRef {
219+ StrRef {
220+ off : off as usize ,
221+ len : len as usize ,
222+ }
223223}
224224
225225fn decode_pred ( p : i64 ) -> CmpPred {
@@ -257,21 +257,21 @@ extern "C" fn h_ftoi(p: *mut Cx, x: f64, round: i64) -> i64 {
257257
258258extern "C" fn h_itos ( p : * mut Cx , v : i64 , len_out : * mut i64 ) -> i64 {
259259 let c = unsafe { cx ( p) } ;
260- let r = c. arena ( ) . push_str ( & format ! ( "{v}" ) ) ;
260+ let r = c. arena ( ) . push_fmt ( format_args ! ( "{v}" ) ) ;
261261 unsafe { * len_out = r. len as i64 } ;
262262 r. off as i64
263263}
264264
265265extern "C" fn h_ftos ( p : * mut Cx , v : f64 , len_out : * mut i64 ) -> i64 {
266266 let c = unsafe { cx ( p) } ;
267- let r = c. arena ( ) . push_str ( & format ! ( "{}" , DuckF64 ( v) ) ) ;
267+ let r = c. arena ( ) . push_fmt ( format_args ! ( "{}" , DuckF64 ( v) ) ) ;
268268 unsafe { * len_out = r. len as i64 } ;
269269 r. off as i64
270270}
271271
272272extern "C" fn h_stoi ( p : * mut Cx , off : i64 , len : i64 , valid_out : * mut u8 ) -> i64 {
273273 let c = unsafe { cx ( p) } ;
274- let s = c . get ( off, len) ;
274+ let s = unsafe { & * c . arena } . get ( span ( off, len) ) ;
275275 match s. trim_ascii ( ) . parse :: < i64 > ( ) {
276276 Ok ( v) => {
277277 unsafe { * valid_out = 1 } ;
@@ -286,7 +286,7 @@ extern "C" fn h_stoi(p: *mut Cx, off: i64, len: i64, valid_out: *mut u8) -> i64
286286
287287extern "C" fn h_stof ( p : * mut Cx , off : i64 , len : i64 , valid_out : * mut u8 ) -> f64 {
288288 let c = unsafe { cx ( p) } ;
289- let s = c . get ( off, len) ;
289+ let s = unsafe { & * c . arena } . get ( span ( off, len) ) ;
290290 match s. trim_ascii ( ) . parse :: < f64 > ( ) {
291291 Ok ( v) => {
292292 unsafe { * valid_out = 1 } ;
@@ -317,13 +317,12 @@ extern "C" fn h_sconcat(p: *mut Cx, ao: i64, al: i64, bo: i64, bl: i64, len_out:
317317
318318extern "C" fn h_scase ( p : * mut Cx , off : i64 , len : i64 , upper : i64 , len_out : * mut i64 ) -> i64 {
319319 let c = unsafe { cx ( p) } ;
320- let s = c. get ( off, len) ;
321- let out: String = if upper != 0 {
322- s. chars ( ) . map ( casemap:: simple_upper) . collect ( )
320+ let map: fn ( char ) -> char = if upper != 0 {
321+ casemap:: simple_upper
323322 } else {
324- s . chars ( ) . map ( casemap:: simple_lower) . collect ( )
323+ casemap:: simple_lower
325324 } ;
326- let r = c. arena ( ) . push_str ( & out ) ;
325+ let r = c. arena ( ) . case_map ( span ( off , len ) , map ) ;
327326 unsafe { * len_out = r. len as i64 } ;
328327 r. off as i64
329328}
@@ -338,18 +337,16 @@ extern "C" fn h_strim(
338337 len_out : * mut i64 ,
339338) -> i64 {
340339 let c = unsafe { cx ( p) } ;
341- let ( s, set_s) = ( c. get ( ao, al) , c. get ( co, cl) ) ;
342- let set: Vec < char > = set_s. chars ( ) . collect ( ) ;
343- let hit = |ch : char | set. contains ( & ch) ;
344- let t = match side {
345- 0 => s. trim_matches ( hit) ,
346- 1 => s. trim_start_matches ( hit) ,
347- _ => s. trim_end_matches ( hit) ,
348- }
349- . to_string ( ) ;
350- let r = c. arena ( ) . push_str ( & t) ;
351- unsafe { * len_out = r. len as i64 } ;
352- r. off as i64
340+ let arena = unsafe { & * c. arena } ;
341+ let side = match side {
342+ 0 => TrimSide :: Both ,
343+ 1 => TrimSide :: Lead ,
344+ _ => TrimSide :: Trail ,
345+ } ;
346+ let rng = trim_bounds ( arena. get ( span ( ao, al) ) , arena. get ( span ( co, cl) ) , side) ;
347+ // The trimmed value is a subview of the input span — no copy.
348+ unsafe { * len_out = ( rng. end - rng. start ) as i64 } ;
349+ ( ao as usize + rng. start ) as i64
353350}
354351
355352extern "C" fn h_ssubstr (
@@ -372,11 +369,15 @@ extern "C" fn h_ssubstr(
372369 unsafe { * len_out = 0 } ;
373370 return 0 ;
374371 }
375- let s = c. get ( ao, al) ;
376- let out = substr_window ( & s, start, ( has_len != 0 ) . then_some ( len) ) ;
377- let r = c. arena ( ) . push_str ( & out) ;
378- unsafe { * len_out = r. len as i64 } ;
379- r. off as i64
372+ let arena = unsafe { & * c. arena } ;
373+ let rng = substr_window (
374+ arena. get ( span ( ao, al) ) ,
375+ start,
376+ ( has_len != 0 ) . then_some ( len) ,
377+ ) ;
378+ // The window is a subview of the input span — no copy.
379+ unsafe { * len_out = ( rng. end - rng. start ) as i64 } ;
380+ ( ao as usize + rng. start ) as i64
380381}
381382
382383macro_rules! store_h {
@@ -449,25 +450,26 @@ extern "C" fn h_probe(
449450 let c = unsafe { cx ( p) } ;
450451 let desc = unsafe { & * desc } ;
451452 let keys = unsafe { std:: slice:: from_raw_parts ( keys, desc. key_tys . len ( ) ) } ;
452- let PreparedStatic :: Map { entries } = & c. statics ( ) [ desc. static_id ] else {
453+ // SAFETY: statics and arena are disjoint allocations behind separate
454+ // raw pointers; independent references let str values append to the
455+ // arena while the matched entry stays borrowed — no clones, no
456+ // per-call Vec (the steady state is allocation-free).
457+ let statics = unsafe { std:: slice:: from_raw_parts ( c. statics , c. statics_len ) } ;
458+ let arena = unsafe { & mut * c. arena } ;
459+ let PreparedStatic :: Map { entries } = & statics[ desc. static_id ] else {
453460 unreachable ! ( "static kind checked at compile" ) ;
454461 } ;
455462
456463 // Mirrors interp::cmp_key: canonical f64 bits, arena strings by byte
457- // order. The arena is only read here, never written, so the borrow is
458- // fine to hold across the search.
459- let arena = unsafe { & * c. arena } ;
464+ // order. The search only reads the arena.
460465 let cmp = |stored : & [ KeyBits ] | -> std:: cmp:: Ordering {
461466 for ( kb, cell) in stored. iter ( ) . zip ( keys. iter ( ) ) {
462467 let ord = match kb {
463468 KeyBits :: I1 ( s) => s. cmp ( & ( cell[ 0 ] != 0 ) ) ,
464469 KeyBits :: I64 ( s) => s. cmp ( & ( cell[ 0 ] as i64 ) ) ,
465470 KeyBits :: F64 ( s) => s. cmp ( & super :: canon_f64_bits ( f64:: from_bits ( cell[ 0 ] ) ) ) ,
466471 KeyBits :: Str ( s) => {
467- let v = arena. get ( StrRef {
468- off : cell[ 0 ] as usize ,
469- len : cell[ 1 ] as usize ,
470- } ) ;
472+ let v = arena. get ( span ( cell[ 0 ] as i64 , cell[ 1 ] as i64 ) ) ;
471473 s. as_str ( ) . cmp ( v)
472474 }
473475 } ;
@@ -477,35 +479,39 @@ extern "C" fn h_probe(
477479 }
478480 std:: cmp:: Ordering :: Equal
479481 } ;
480-
481482 let found = entries. binary_search_by ( |( k, _) | cmp ( k) ) . ok ( ) ;
482- let hit = found. is_some ( ) ;
483- let values: Vec < ScalarVal > = match found {
484- Some ( idx) => entries[ idx] . 1 . clone ( ) ,
485- None => desc
486- . val_tys
487- . iter ( )
488- . map ( |ty| match ty {
489- Ty :: I1 => ScalarVal :: I1 ( false ) ,
490- Ty :: I64 => ScalarVal :: I64 ( 0 ) ,
491- Ty :: F64 => ScalarVal :: F64 ( 0.0 ) ,
492- Ty :: Str => ScalarVal :: Str ( String :: new ( ) ) ,
493- } )
494- . collect ( ) ,
495- } ;
496- for ( i, v) in values. into_iter ( ) . enumerate ( ) {
497- let cell = match v {
498- ScalarVal :: I1 ( b) => [ b as u64 , 0 ] ,
499- ScalarVal :: I64 ( x) => [ x as u64 , 0 ] ,
500- ScalarVal :: F64 ( f) => [ f. to_bits ( ) , 0 ] ,
501- ScalarVal :: Str ( s) => {
502- let r = c. arena ( ) . push_str ( & s) ;
503- [ r. off as u64 , r. len as u64 ]
483+
484+ match found {
485+ Some ( idx) => {
486+ for ( i, v) in entries[ idx] . 1 . iter ( ) . enumerate ( ) {
487+ let cell: Cell = match v {
488+ ScalarVal :: I1 ( b) => [ * b as u64 , 0 ] ,
489+ ScalarVal :: I64 ( x) => [ * x as u64 , 0 ] ,
490+ ScalarVal :: F64 ( f) => [ f. to_bits ( ) , 0 ] ,
491+ ScalarVal :: Str ( s) => {
492+ let r = arena. push_str ( s) ;
493+ [ r. off as u64 , r. len as u64 ]
494+ }
495+ } ;
496+ unsafe { * outs. add ( i) = cell } ;
504497 }
505- } ;
506- unsafe { * outs. add ( i) = cell } ;
498+ 1
499+ }
500+ None => {
501+ for ( i, ty) in desc. val_tys . iter ( ) . enumerate ( ) {
502+ let cell: Cell = match ty {
503+ Ty :: I1 | Ty :: I64 => [ 0 , 0 ] ,
504+ Ty :: F64 => [ 0f64 . to_bits ( ) , 0 ] ,
505+ Ty :: Str => {
506+ let r = arena. push_str ( "" ) ;
507+ [ r. off as u64 , r. len as u64 ]
508+ }
509+ } ;
510+ unsafe { * outs. add ( i) = cell } ;
511+ }
512+ 0
513+ }
507514 }
508- hit as u8
509515}
510516
511517// ------------------------------------------------------------ the JIT'd fn --
0 commit comments