@@ -795,6 +795,7 @@ impl SafepointSpiller {
795795mod tests {
796796 use super :: * ;
797797 use alloc:: string:: ToString ;
798+ use cranelift_codegen:: ir:: { BlockCall , ExceptionTableData } ;
798799 use cranelift_codegen:: isa:: CallConv ;
799800
800801 #[ test]
@@ -2769,4 +2770,110 @@ block10:
27692770 "# ,
27702771 ) ;
27712772 }
2773+
2774+ #[ test]
2775+ fn needs_stack_map_across_try_call ( ) {
2776+ let _ = env_logger:: try_init ( ) ;
2777+
2778+ // Test that values needing stack maps get stack_map entries on
2779+ // try_call instructions, not just regular call instructions.
2780+ //
2781+ // block0:
2782+ // v0 = call fn0() ;; returns a gc ref
2783+ // v1 = call fn0() ;; returns another gc ref
2784+ // try_call fn0(), sig0, block1(), [default: block2()]
2785+ // ;; v0 and v1 should be in the stack map here
2786+ // block1:
2787+ // call fn1(v0) ;; uses v0, v1 is dead
2788+ // return
2789+ // block2(v2: i64):
2790+ // return
2791+
2792+ let sig = Signature :: new ( CallConv :: SystemV ) ;
2793+
2794+ let mut fn_ctx = FunctionBuilderContext :: new ( ) ;
2795+ let mut func = Function :: with_name_signature ( ir:: UserFuncName :: testcase ( "sample" ) , sig) ;
2796+ let mut builder = FunctionBuilder :: new ( & mut func, & mut fn_ctx) ;
2797+
2798+ // fn0: () -> i32 (returns a gc ref)
2799+ let name0 = builder
2800+ . func
2801+ . declare_imported_user_function ( ir:: UserExternalName {
2802+ namespace : 0 ,
2803+ index : 0 ,
2804+ } ) ;
2805+ let mut sig0 = Signature :: new ( CallConv :: SystemV ) ;
2806+ sig0. returns . push ( AbiParam :: new ( ir:: types:: I32 ) ) ;
2807+ let signature0 = builder. func . import_signature ( sig0) ;
2808+ let func_ref0 = builder. import_function ( ir:: ExtFuncData {
2809+ name : ir:: ExternalName :: user ( name0) ,
2810+ signature : signature0,
2811+ colocated : true ,
2812+ patchable : false ,
2813+ } ) ;
2814+
2815+ // fn1: (i32) -> () (consumes a gc ref)
2816+ let name1 = builder
2817+ . func
2818+ . declare_imported_user_function ( ir:: UserExternalName {
2819+ namespace : 0 ,
2820+ index : 1 ,
2821+ } ) ;
2822+ let mut sig1 = Signature :: new ( CallConv :: SystemV ) ;
2823+ sig1. params . push ( AbiParam :: new ( ir:: types:: I32 ) ) ;
2824+ let signature1 = builder. func . import_signature ( sig1) ;
2825+ let func_ref1 = builder. import_function ( ir:: ExtFuncData {
2826+ name : ir:: ExternalName :: user ( name1) ,
2827+ signature : signature1,
2828+ colocated : true ,
2829+ patchable : false ,
2830+ } ) ;
2831+
2832+ let block0 = builder. create_block ( ) ;
2833+ let block1 = builder. create_block ( ) ;
2834+ let block2 = builder. create_block ( ) ;
2835+
2836+ builder. switch_to_block ( block0) ;
2837+
2838+ // v0 = call fn0() -- a gc ref that's live across the try_call
2839+ let call0 = builder. ins ( ) . call ( func_ref0, & [ ] ) ;
2840+ let v0 = builder. func . dfg . inst_results ( call0) [ 0 ] ;
2841+ builder. declare_value_needs_stack_map ( v0) ;
2842+
2843+ // v1 = call fn0() -- another gc ref, also live across the try_call
2844+ let call1 = builder. ins ( ) . call ( func_ref0, & [ ] ) ;
2845+ let v1 = builder. func . dfg . inst_results ( call1) [ 0 ] ;
2846+ builder. declare_value_needs_stack_map ( v1) ;
2847+
2848+ // try_call fn0() -> block1, exception -> block2
2849+ let normal_return = BlockCall :: new ( block1, [ ] , & mut builder. func . dfg . value_lists ) ;
2850+ let exception_table = builder
2851+ . func
2852+ . dfg
2853+ . exception_tables
2854+ . push ( ExceptionTableData :: new ( signature0, normal_return, [ ] ) ) ;
2855+ builder. ins ( ) . try_call ( func_ref0, & [ ] , exception_table) ;
2856+
2857+ // block1: use v0 (so it's live across the try_call)
2858+ builder. switch_to_block ( block1) ;
2859+ builder. ins ( ) . call ( func_ref1, & [ v0] ) ;
2860+ builder. ins ( ) . return_ ( & [ ] ) ;
2861+
2862+ // block2: exception handler
2863+ builder. switch_to_block ( block2) ;
2864+ builder. ins ( ) . return_ ( & [ ] ) ;
2865+
2866+ builder. seal_all_blocks ( ) ;
2867+ builder. finalize ( ) ;
2868+
2869+ // The try_call should have a stack_map with v0 (and v1 should NOT
2870+ // be in it since v1 is not used after the try_call).
2871+ // The second call (which produces v1) should have v0 in its stack_map
2872+ // since v0 is live across that call too.
2873+ let output = func. display ( ) . to_string ( ) ;
2874+ assert ! (
2875+ output. contains( "try_call fn0(), sig0, block1, [], stack_map=[i32 @ ss0+0]" ) ,
2876+ "try_call should have stack_map entry for v0 (spilled to ss0), got:\n {output}"
2877+ ) ;
2878+ }
27722879}
0 commit comments