@@ -59,6 +59,8 @@ struct Cx {
5959 statics_len : usize ,
6060 regexes : * const CompiledRe ,
6161 regexes_len : usize ,
62+ externs : * const super :: ExternImpl ,
63+ externs_len : usize ,
6264 trap : Option < Trap > ,
6365}
6466
@@ -96,6 +98,14 @@ struct ProbeDesc {
9698 val_tys : Vec < Ty > ,
9799}
98100
101+ /// Per-ecall-site descriptor owned by the `CraneliftFn`; the JIT'd code
102+ /// passes its absolute address to `h_extern`.
103+ struct ExternDesc {
104+ ext_id : usize ,
105+ param_tys : Vec < Ty > ,
106+ ret_tys : Vec < Ty > ,
107+ }
108+
99109/// One 16-byte scratch cell: scalar payload in `[0]` (f64 as bits, bool as
100110/// 0/1), strings as (offset, length).
101111type Cell = [ u64 ; 2 ] ;
@@ -901,6 +911,57 @@ extern "C" fn h_probe(
901911 }
902912}
903913
914+ /// Extern (UDF) call: gather `Option<ScalarVal>` args from the arg cells,
915+ /// delegate to the SAME [`interp::call_extern`] the interpreter uses, and
916+ /// write the whole-call validity plus (validity, payload) pairs to the out
917+ /// cells. A callable error or declaration violation sets the trap flag.
918+ extern "C" fn h_extern ( p : * mut Cx , desc : * const ExternDesc , args : * const Cell , outs : * mut Cell ) {
919+ let c = unsafe { cx ( p) } ;
920+ let desc = unsafe { & * desc } ;
921+ let args = unsafe { std:: slice:: from_raw_parts ( args, 2 * desc. param_tys . len ( ) ) } ;
922+ let mut a = Vec :: with_capacity ( desc. param_tys . len ( ) ) ;
923+ {
924+ let arena = unsafe { & * c. arena } ;
925+ for ( j, & ty) in desc. param_tys . iter ( ) . enumerate ( ) {
926+ let valid = args[ 2 * j] [ 0 ] != 0 ;
927+ let cell = & args[ 2 * j + 1 ] ;
928+ a. push ( if valid {
929+ Some ( match ty {
930+ Ty :: I1 => ScalarVal :: I1 ( cell[ 0 ] != 0 ) ,
931+ Ty :: I64 => ScalarVal :: I64 ( cell[ 0 ] as i64 ) ,
932+ Ty :: F64 => ScalarVal :: F64 ( f64:: from_bits ( cell[ 0 ] ) ) ,
933+ Ty :: Str => ScalarVal :: Str (
934+ arena. get ( span ( cell[ 0 ] as i64 , cell[ 1 ] as i64 ) ) . to_string ( ) ,
935+ ) ,
936+ } )
937+ } else {
938+ None
939+ } ) ;
940+ }
941+ }
942+ let imps = unsafe { std:: slice:: from_raw_parts ( c. externs , c. externs_len ) } ;
943+ match interp:: call_extern ( & imps[ desc. ext_id ] , & desc. ret_tys , & a) {
944+ Err ( t) => c. set_trap ( t. 0 ) ,
945+ Ok ( ( whole, comps) ) => {
946+ unsafe { * outs = [ whole as u64 , 0 ] } ;
947+ let arena = c. arena ( ) ;
948+ for ( j, ( f, v) ) in comps. iter ( ) . enumerate ( ) {
949+ unsafe { * outs. add ( 1 + 2 * j) = [ * f as u64 , 0 ] } ;
950+ let cell: Cell = match v {
951+ ScalarVal :: I1 ( x) => [ * x as u64 , 0 ] ,
952+ ScalarVal :: I64 ( x) => [ * x as u64 , 0 ] ,
953+ ScalarVal :: F64 ( x) => [ x. to_bits ( ) , 0 ] ,
954+ ScalarVal :: Str ( s) => {
955+ let r = arena. push_str ( s) ;
956+ [ r. off as u64 , r. len as u64 ]
957+ }
958+ } ;
959+ unsafe { * outs. add ( 2 + 2 * j) = cell } ;
960+ }
961+ }
962+ }
963+ }
964+
904965// ------------------------------------------------------------ the JIT'd fn --
905966
906967type RowFn = extern "C" fn ( * mut Cx ) -> i64 ;
@@ -916,6 +977,7 @@ pub struct CraneliftFn {
916977 /// Owned backing for absolute addresses baked into the code.
917978 _const_strs : Vec < Box < str > > ,
918979 _probe_descs : Vec < Box < ProbeDesc > > ,
980+ _extern_descs : Vec < Box < ExternDesc > > ,
919981}
920982
921983/// A JIT'd value: scalars are one CLIF value, strings are (offset, length).
@@ -950,6 +1012,16 @@ fn clif_ty(ty: Ty) -> types::Type {
9501012}
9511013
9521014pub fn compile ( p : & Program , statics : Vec < super :: StaticData > ) -> Result < CraneliftFn , CompileError > {
1015+ compile_ext ( p, statics, Vec :: new ( ) )
1016+ }
1017+
1018+ /// [`compile`] plus the extern (UDF) implementations — one per program
1019+ /// `extern @N`, validated by the interpreter compile below.
1020+ pub fn compile_ext (
1021+ p : & Program ,
1022+ statics : Vec < super :: StaticData > ,
1023+ externs : Vec < super :: ExternImpl > ,
1024+ ) -> Result < CraneliftFn , CompileError > {
9531025 // Stage-B multiplicity programs (EmitTo loops / multimap probes) are
9541026 // interpreter-only for now: rejecting here makes the caller's existing
9551027 // interp fallback the documented 'many' path. The match arms below can
@@ -970,8 +1042,8 @@ pub fn compile(p: &Program, statics: Vec<super::StaticData>) -> Result<Cranelift
9701042 ) ) ;
9711043 }
9721044 // The interpreter compile also runs verify + prepare_statics; its
973- // prepared statics are the ones the helpers read.
974- let interp = interp:: compile ( p, statics) ?;
1045+ // prepared statics (and extern impls) are the ones the helpers read.
1046+ let interp = interp:: compile_ext ( p, statics, externs ) ?;
9751047
9761048 let mut flags = settings:: builder ( ) ;
9771049 flags. set ( "use_colocated_libcalls" , "false" ) . unwrap ( ) ;
@@ -1006,14 +1078,24 @@ pub fn compile(p: &Program, statics: Vec<super::StaticData>) -> Result<Cranelift
10061078
10071079 let mut const_strs: Vec < Box < str > > = Vec :: new ( ) ;
10081080 let mut probe_descs: Vec < Box < ProbeDesc > > = Vec :: new ( ) ;
1081+ let mut extern_descs: Vec < Box < ExternDesc > > = Vec :: new ( ) ;
10091082 let mut trap_msgs: Vec < String > = Vec :: new ( ) ;
10101083
10111084 {
10121085 let mut fbc = FunctionBuilderContext :: new ( ) ;
10131086 let mut b = FunctionBuilder :: new ( & mut ctx. func , & mut fbc) ;
10141087
10151088 // Scratch stack slots: one 16-byte out-cell for helper out-params,
1016- // plus room for the widest probe's keys and values.
1089+ // plus room for the widest probe's keys and values — and the widest
1090+ // ecall's arg cells (2 per param) and out cells (1 + 2 per return),
1091+ // which share the same scratch slots.
1092+ let ext_args = p. externs . iter ( ) . map ( |e| 2 * e. params . len ( ) ) . max ( ) . unwrap_or ( 0 ) ;
1093+ let ext_outs = p
1094+ . externs
1095+ . iter ( )
1096+ . map ( |e| 1 + 2 * e. rets . len ( ) )
1097+ . max ( )
1098+ . unwrap_or ( 0 ) ;
10171099 let max_keys = p
10181100 . statics
10191101 . iter ( )
@@ -1023,6 +1105,7 @@ pub fn compile(p: &Program, statics: Vec<super::StaticData>) -> Result<Cranelift
10231105 } )
10241106 . max ( )
10251107 . unwrap_or ( 1 )
1108+ . max ( ext_args)
10261109 . max ( 1 ) ;
10271110 let max_vals = p
10281111 . statics
@@ -1033,6 +1116,7 @@ pub fn compile(p: &Program, statics: Vec<super::StaticData>) -> Result<Cranelift
10331116 } )
10341117 . max ( )
10351118 . unwrap_or ( 1 )
1119+ . max ( ext_outs)
10361120 . max ( 1 ) ;
10371121 let slot_out =
10381122 b. create_sized_stack_slot ( StackSlotData :: new ( StackSlotKind :: ExplicitSlot , 16 , 3 ) ) ;
@@ -1104,6 +1188,7 @@ pub fn compile(p: &Program, statics: Vec<super::StaticData>) -> Result<Cranelift
11041188 slot_vals,
11051189 & mut const_strs,
11061190 & mut probe_descs,
1191+ & mut extern_descs,
11071192 trap_exit,
11081193 ) ;
11091194 }
@@ -1200,6 +1285,7 @@ pub fn compile(p: &Program, statics: Vec<super::StaticData>) -> Result<Cranelift
12001285 regexes,
12011286 _const_strs : const_strs,
12021287 _probe_descs : probe_descs,
1288+ _extern_descs : extern_descs,
12031289 } )
12041290}
12051291
@@ -1220,6 +1306,7 @@ impl CraneliftFn {
12201306 interp:: reserve_out ( & mut st. out , input. rows ) ;
12211307
12221308 let statics = self . interp . statics ( ) ;
1309+ let externs = self . interp . externs ( ) ;
12231310 let mut emitted = 0usize ;
12241311 for row in 0 ..input. rows {
12251312 let mut cx = Cx {
@@ -1233,6 +1320,8 @@ impl CraneliftFn {
12331320 statics_len : statics. len ( ) ,
12341321 regexes : self . regexes . as_ptr ( ) ,
12351322 regexes_len : self . regexes . len ( ) ,
1323+ externs : externs. as_ptr ( ) ,
1324+ externs_len : externs. len ( ) ,
12361325 trap : None ,
12371326 } ;
12381327 match ( self . row_fn ) ( & mut cx) {
@@ -1263,6 +1352,7 @@ fn translate_inst(
12631352 slot_vals : cranelift_codegen:: ir:: StackSlot ,
12641353 const_strs : & mut Vec < Box < str > > ,
12651354 probe_descs : & mut Vec < Box < ProbeDesc > > ,
1355+ extern_descs : & mut Vec < Box < ExternDesc > > ,
12661356 trap_exit : cranelift_codegen:: ir:: Block ,
12671357) {
12681358 // After any fallible helper: check trap_flag (offset 0 of Cx) and bail.
@@ -1278,6 +1368,74 @@ fn translate_inst(
12781368 Inst :: ProbeRange { .. } | Inst :: ProbeRead { .. } => {
12791369 unreachable ! ( "multiplicity programs are rejected before codegen" )
12801370 }
1371+ Inst :: ExternCall { ext, dsts, args } => {
1372+ let spec = & p. externs [ * ext as usize ] ;
1373+ // Write the arg cells: (flag, payload) per param, one cell each.
1374+ // Payloads under a false flag are never read by the helper.
1375+ for ( i, a) in args. iter ( ) . enumerate ( ) {
1376+ let base = ( 16 * i) as i32 ;
1377+ match vals[ & a. 0 ] {
1378+ V :: S ( v) => {
1379+ let is_flag = i % 2 == 0 ;
1380+ let as64 = if is_flag {
1381+ b. ins ( ) . uextend ( types:: I64 , v)
1382+ } else {
1383+ match spec. params [ i / 2 ] {
1384+ Ty :: I1 => b. ins ( ) . uextend ( types:: I64 , v) ,
1385+ Ty :: I64 => v,
1386+ Ty :: F64 => b. ins ( ) . bitcast ( types:: I64 , MemFlags :: new ( ) , v) ,
1387+ Ty :: Str => unreachable ! ( "str payload is a two-i64 V::Str" ) ,
1388+ }
1389+ } ;
1390+ b. ins ( ) . stack_store ( as64, slot_keys, base) ;
1391+ }
1392+ V :: Str ( o, l) => {
1393+ b. ins ( ) . stack_store ( o, slot_keys, base) ;
1394+ b. ins ( ) . stack_store ( l, slot_keys, base + 8 ) ;
1395+ }
1396+ }
1397+ }
1398+ let desc = Box :: new ( ExternDesc {
1399+ ext_id : * ext as usize ,
1400+ param_tys : spec. params . clone ( ) ,
1401+ ret_tys : spec. rets . clone ( ) ,
1402+ } ) ;
1403+ let desc_ptr = & * desc as * const ExternDesc as i64 ;
1404+ extern_descs. push ( desc) ;
1405+ let dp = icon ( b, desc_ptr) ;
1406+ let ap = b. ins ( ) . stack_addr ( types:: I64 , slot_keys, 0 ) ;
1407+ let op = b. ins ( ) . stack_addr ( types:: I64 , slot_vals, 0 ) ;
1408+ call_h ( b, module, "h_extern" , & [ cxp, dp, ap, op] ) ;
1409+ trap_check ( b) ;
1410+ // Read the out cells: whole-call validity, then (flag, payload)
1411+ // per return.
1412+ for ( i, d) in dsts. iter ( ) . enumerate ( ) {
1413+ let base = ( 16 * i) as i32 ;
1414+ let is_flag = i == 0 || i % 2 == 1 ;
1415+ let v = if is_flag {
1416+ let x = b. ins ( ) . stack_load ( types:: I64 , slot_vals, base) ;
1417+ V :: S ( b. ins ( ) . ireduce ( types:: I8 , x) )
1418+ } else {
1419+ match spec. rets [ i / 2 - 1 ] {
1420+ Ty :: I1 => {
1421+ let x = b. ins ( ) . stack_load ( types:: I64 , slot_vals, base) ;
1422+ V :: S ( b. ins ( ) . ireduce ( types:: I8 , x) )
1423+ }
1424+ Ty :: I64 => V :: S ( b. ins ( ) . stack_load ( types:: I64 , slot_vals, base) ) ,
1425+ Ty :: F64 => {
1426+ let x = b. ins ( ) . stack_load ( types:: I64 , slot_vals, base) ;
1427+ V :: S ( b. ins ( ) . bitcast ( types:: F64 , MemFlags :: new ( ) , x) )
1428+ }
1429+ Ty :: Str => {
1430+ let o = b. ins ( ) . stack_load ( types:: I64 , slot_vals, base) ;
1431+ let l = b. ins ( ) . stack_load ( types:: I64 , slot_vals, base + 8 ) ;
1432+ V :: Str ( o, l)
1433+ }
1434+ }
1435+ } ;
1436+ vals. insert ( d. 0 , v) ;
1437+ }
1438+ }
12811439 Inst :: Const { dst, lit } => {
12821440 let v = match lit {
12831441 Lit :: I1 ( x) => V :: S ( b. ins ( ) . iconst ( types:: I8 , * x as i64 ) ) ,
@@ -1968,6 +2126,7 @@ const HELPERS: &[(&str, *const u8)] = &[
19682126 ( "h_store_str" , h_store_str as * const u8 ) ,
19692127 ( "h_sload" , h_sload as * const u8 ) ,
19702128 ( "h_probe" , h_probe as * const u8 ) ,
2129+ ( "h_extern" , h_extern as * const u8 ) ,
19712130 ( "h_ln" , h_ln as * const u8 ) ,
19722131 ( "h_log2" , h_log2 as * const u8 ) ,
19732132 ( "h_log10" , h_log10 as * const u8 ) ,
@@ -2059,6 +2218,7 @@ fn helper_sig(name: &str, sig: &mut cranelift_codegen::ir::Signature, ptr: types
20592218 "h_store_str" => ( & [ ptr, I64 , I8 , I64 , I64 ] , None ) ,
20602219 "h_sload" => ( & [ ptr, I64 , I64 , I64 ] , None ) ,
20612220 "h_probe" => ( & [ ptr, I64 , I64 , I64 ] , Some ( I8 ) ) ,
2221+ "h_extern" => ( & [ ptr, I64 , I64 , I64 ] , None ) ,
20622222 _ => unreachable ! ( "unknown helper {name}" ) ,
20632223 } ;
20642224 for p in params {
0 commit comments