Skip to content

Commit d70a517

Browse files
authored
Print stack maps for try_call[_indirect] CLIF instructions (#12891)
* Print stack maps for `try_call[_indirect]` CLIF instructions The safepoint liveness analysis already correctly records stack maps for these instructions, but the display omission hid this from view. * cargo fmt
1 parent 2283e84 commit d70a517

2 files changed

Lines changed: 111 additions & 2 deletions

File tree

cranelift/codegen/src/write.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,8 @@ pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt
499499
func_ref,
500500
DisplayValues(args.as_slice(pool)),
501501
exception_tables[exception].display(pool),
502-
)
502+
)?;
503+
write_user_stack_map_entries(w, dfg, inst)
503504
}
504505
TryCallIndirect {
505506
ref args,
@@ -513,7 +514,8 @@ pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt
513514
args[0],
514515
DisplayValues(&args[1..]),
515516
exception_tables[exception].display(pool),
516-
)
517+
)?;
518+
write_user_stack_map_entries(w, dfg, inst)
517519
}
518520
FuncAddr { func_ref, .. } => write!(w, " {func_ref}"),
519521
StackLoad {

cranelift/frontend/src/frontend/safepoints.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,7 @@ impl SafepointSpiller {
795795
mod 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

Comments
 (0)