@@ -2608,6 +2608,7 @@ impl RegVm {
26082608 func,
26092609 & std:: collections:: HashMap :: new ( ) ,
26102610 & self_call_sites,
2611+ & std:: collections:: HashMap :: new ( ) ,
26112612 )
26122613 . and_then ( |( jit_fn, ret, _params, _literals, _precise) | {
26132614 // This scalar fast path returns an `Int`; other return
@@ -2647,6 +2648,135 @@ impl RegVm {
26472648 }
26482649 }
26492650
2651+ /// Native mutual recursion (native-call-ABI slice 4): if `function_id` is part of
2652+ /// a mutually-recursive scalar-`Int` cycle, compile the whole group together
2653+ /// (declare the cycle, then define each) and dispatch the called member natively.
2654+ /// Returns the result on a clean completion, or `None` to fall back to the
2655+ /// interpreter (not eligible, or a deopt incl. the entry depth-cap bail). The
2656+ /// group is compiled once and every member cached.
2657+ #[ cfg( feature = "native-jit" ) ]
2658+ pub ( super ) fn try_native_mutual_recursive_int (
2659+ & mut self ,
2660+ unit : & RegUnit ,
2661+ function_id : usize ,
2662+ caller_base : usize ,
2663+ args : & [ usize ] ,
2664+ ) -> Option < VmValue > {
2665+ let func = unit. functions . get ( function_id) ?;
2666+ if args. len ( ) != func. params {
2667+ return None ;
2668+ }
2669+ let key = Rc :: as_ptr ( func) as usize ;
2670+ let id = {
2671+ let native = self . native . as_mut ( ) ?;
2672+ if native. force_bail
2673+ || native. forced_safepoint . is_some ( )
2674+ || native. force_all_safepoints
2675+ {
2676+ return None ;
2677+ }
2678+ if let Some ( cached) = native. mutual_recursive_native . get ( & key) {
2679+ ( * cached) ?
2680+ } else {
2681+ let group = native_recursive_int_group ( unit, function_id) ;
2682+ let compiled = group. as_ref ( ) . and_then ( |scc| {
2683+ let index_of: std:: collections:: HashMap < usize , u32 > = scc
2684+ . iter ( )
2685+ . enumerate ( )
2686+ . map ( |( i, & fid) | ( fid, i as u32 ) )
2687+ . collect ( ) ;
2688+ let mut jit_funcs = Vec :: with_capacity ( scc. len ( ) ) ;
2689+ for & member in scc {
2690+ let mfunc = unit. functions . get ( member) ?;
2691+ let group_call_sites: std:: collections:: HashMap < usize , u32 > = mfunc
2692+ . code
2693+ . iter ( )
2694+ . enumerate ( )
2695+ . filter_map ( |( ip, instr) | match instr {
2696+ RegInstr :: CallKnown {
2697+ function, mut_args, ..
2698+ } if mut_args. is_empty ( ) && index_of. contains_key ( function) => {
2699+ Some ( ( ip, index_of[ function] ) )
2700+ }
2701+ _ => None ,
2702+ } )
2703+ . collect ( ) ;
2704+ let ( jit_fn, ret, _p, _l, _pr) = translate_to_native_jit_with_calls (
2705+ unit,
2706+ mfunc,
2707+ & std:: collections:: HashMap :: new ( ) ,
2708+ & std:: collections:: HashSet :: new ( ) ,
2709+ & group_call_sites,
2710+ ) ?;
2711+ if ret != NativeTy :: Int {
2712+ return None ;
2713+ }
2714+ jit_funcs. push ( jit_fn) ;
2715+ }
2716+ native. module . compile_recursive_group ( & jit_funcs) . ok ( )
2717+ } ) ;
2718+ match ( group, compiled) {
2719+ ( Some ( scc) , Some ( ids) ) if ids. len ( ) == scc. len ( ) => {
2720+ let mut my_id = None ;
2721+ for ( i, & member) in scc. iter ( ) . enumerate ( ) {
2722+ let mkey = Rc :: as_ptr ( & unit. functions [ member] ) as usize ;
2723+ native. mutual_recursive_native . insert ( mkey, Some ( ids[ i] ) ) ;
2724+ if member == function_id {
2725+ my_id = Some ( ids[ i] ) ;
2726+ }
2727+ }
2728+ my_id?
2729+ }
2730+ ( group, _) => {
2731+ // Cache ineligibility (for the whole detected group, or this key).
2732+ match group {
2733+ Some ( scc) => {
2734+ for member in scc {
2735+ let mkey = Rc :: as_ptr ( & unit. functions [ member] ) as usize ;
2736+ native. mutual_recursive_native . insert ( mkey, None ) ;
2737+ }
2738+ }
2739+ None => {
2740+ native. mutual_recursive_native . insert ( key, None ) ;
2741+ }
2742+ }
2743+ return None ;
2744+ }
2745+ }
2746+ }
2747+ } ;
2748+ let mut int_args = Vec :: with_capacity ( args. len ( ) ) ;
2749+ for & arg in args {
2750+ let VmValue :: Int ( value) = self . reg ( caller_base + arg) else {
2751+ return None ;
2752+ } ;
2753+ int_args. push ( * value) ;
2754+ }
2755+ let lens = vec ! [ 0i64 ; int_args. len( ) ] ;
2756+ let mut heap_tx = JitNativeCallFrame :: begin ( ) ;
2757+ let outcome = {
2758+ let native = self . native . as_ref ( ) ?;
2759+ native
2760+ . module
2761+ . call_with_host_ctx ( id, & int_args, & lens, heap_tx. host_ctx ( ) )
2762+ } ;
2763+ match outcome {
2764+ vm_jit:: NativeOutcome :: Completed ( bits) => {
2765+ heap_tx. commit_scalar_with_writebacks ( & [ ] ) ;
2766+ if let Some ( native) = self . native . as_mut ( )
2767+ && native. collect_stats
2768+ {
2769+ native. stats . native_calls += 1 ;
2770+ }
2771+ Some ( VmValue :: Int ( bits) )
2772+ }
2773+ _ => {
2774+ heap_tx. abort ( ) ;
2775+ None
2776+ }
2777+ }
2778+ }
2779+
26502780 pub ( super ) fn run_jit_self_recursive_int (
26512781 & mut self ,
26522782 unit : & RegUnit ,
@@ -2931,12 +3061,18 @@ fn propagate_same_kind(kinds: &mut [ScalarSlotKind], dst: usize, src: usize) ->
29313061}
29323062
29333063fn compute_self_recursive_int_jit_candidate ( unit : & RegUnit , function_id : usize ) -> bool {
2934- compute_self_recursive_int_jit_candidate_inner ( unit, function_id) . unwrap_or ( false )
3064+ let group: std:: collections:: HashSet < usize > = std:: iter:: once ( function_id) . collect ( ) ;
3065+ compute_recursive_int_member_inner ( unit, function_id, & group) . unwrap_or ( false )
29353066}
29363067
2937- fn compute_self_recursive_int_jit_candidate_inner (
3068+ /// Scalar-`Int` recursion analysis for one member of a recursive `group`: the body
3069+ /// must be all-scalar-`Int`, return `Int`, and every `CallKnown` must target a group
3070+ /// member (self for self-recursion, or any sibling for mutual recursion) with scalar
3071+ /// args and matching arity. `group = {function_id}` is the self-recursive case.
3072+ fn compute_recursive_int_member_inner (
29383073 unit : & RegUnit ,
29393074 function_id : usize ,
3075+ group : & std:: collections:: HashSet < usize > ,
29403076) -> Option < bool > {
29413077 let Some ( func) = unit. functions . get ( function_id) else {
29423078 return Some ( false ) ;
@@ -3011,9 +3147,9 @@ fn compute_self_recursive_int_jit_candidate_inner(
30113147 function,
30123148 args,
30133149 mut_args,
3014- } if * function == function_id
3150+ } if group . contains ( function)
30153151 && mut_args. is_empty ( )
3016- && args . len ( ) == func . params =>
3152+ && unit . functions . get ( * function ) . is_some_and ( |f| f . params == args . len ( ) ) =>
30173153 {
30183154 saw_self_call = true ;
30193155 let mut local_changed =
@@ -3049,6 +3185,59 @@ fn compute_self_recursive_int_jit_candidate_inner(
30493185 )
30503186}
30513187
3188+ /// The mutually-recursive group (call-graph SCC) containing `function_id`, if it is
3189+ /// a cycle of >= 2 all-scalar-`Int` functions whose every `CallKnown` targets a
3190+ /// group member (native-call-ABI slice 4). Returned sorted; `None` for non-cyclic
3191+ /// functions and pure self-recursion (handled by the self-recursive path).
3192+ #[ cfg( feature = "native-jit" ) ]
3193+ fn native_recursive_int_group ( unit : & RegUnit , function_id : usize ) -> Option < Vec < usize > > {
3194+ use std:: collections:: HashSet ;
3195+ let callees = |fid : usize | -> Vec < usize > {
3196+ unit. functions . get ( fid) . map_or_else ( Vec :: new, |f| {
3197+ f. code
3198+ . iter ( )
3199+ . filter_map ( |instr| match instr {
3200+ RegInstr :: CallKnown { function, .. } => Some ( * function) ,
3201+ _ => None ,
3202+ } )
3203+ . collect ( )
3204+ } )
3205+ } ;
3206+ // Forward-reachable from `function_id` via CallKnown edges.
3207+ let mut fwd = HashSet :: new ( ) ;
3208+ let mut stack = vec ! [ function_id] ;
3209+ while let Some ( f) = stack. pop ( ) {
3210+ if fwd. insert ( f) {
3211+ stack. extend ( callees ( f) ) ;
3212+ }
3213+ }
3214+ // Backward-reachable: functions that can transitively reach `function_id`.
3215+ let mut bwd = HashSet :: new ( ) ;
3216+ let mut stack = vec ! [ function_id] ;
3217+ while let Some ( target) = stack. pop ( ) {
3218+ if bwd. insert ( target) {
3219+ for caller in 0 ..unit. functions . len ( ) {
3220+ if callees ( caller) . contains ( & target) {
3221+ stack. push ( caller) ;
3222+ }
3223+ }
3224+ }
3225+ }
3226+ // The SCC is the intersection (mutually reachable with `function_id`).
3227+ let mut scc: Vec < usize > = fwd. intersection ( & bwd) . copied ( ) . collect ( ) ;
3228+ scc. sort_unstable ( ) ;
3229+ if scc. len ( ) < 2 {
3230+ return None ;
3231+ }
3232+ let group: HashSet < usize > = scc. iter ( ) . copied ( ) . collect ( ) ;
3233+ for & member in & scc {
3234+ if !compute_recursive_int_member_inner ( unit, member, & group) . unwrap_or ( false ) {
3235+ return None ;
3236+ }
3237+ }
3238+ Some ( scc)
3239+ }
3240+
30523241fn scalar_reachable_instructions ( code : & [ RegInstr ] ) -> Vec < bool > {
30533242 let mut reachable = vec ! [ false ; code. len( ) ] ;
30543243 let mut stack = vec ! [ 0usize ] ;
0 commit comments