Skip to content

Commit 24938c4

Browse files
authored
Handle alias values in SafepointSpiller::rewrite_use (#13228)
The `rewrite_use` method of the safepoint spiller was not checking for value aliases, and therefore some uses of needs-stack-map values would not be reloaded from their associated stack slot. Note that, because the liveness analysis *does* correctly analyze alias values and will always correctly spill them at safepoints, this could not result in any bug with non-moving collectors (where reloading after safepoints is unnecessary), like those that Wasmtime has today. However, with the introduction of a moving collector in #13107, this lack-of-reload bug in the safepoint spiller does trigger bugs in Wasmtime (and, reassuringly, our testing and fuzzing infrastructure finds it ~immediately). Uses of a non-reloaded GC reference are stale because the object they previously pointed to moved but the non-reloaded GC reference was not updated to point to the object's new location, resulting in use-after-move bugs. The fix for the safepoint spiller is simple: resolve aliases before rewriting uses. This commit additionally sprinkles some debug assertions throughout all the safepoint spiller code to double check that we have already resolved aliases and are no longer dealing with alias values in, e.g., our current set of live values in the liveness analysis.
1 parent ad9bc5b commit 24938c4

2 files changed

Lines changed: 170 additions & 23 deletions

File tree

cranelift/codegen/src/ir/dfg.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,14 @@ impl DataFlowGraph {
372372
self.value_is_valid(value) && !matches!(self.values[value].into(), ValueData::Alias { .. })
373373
}
374374

375+
/// Is the given value an alias?
376+
pub fn value_is_alias(&self, v: Value) -> bool {
377+
match ValueData::from(self.values[v]) {
378+
ValueData::Alias { .. } => true,
379+
ValueData::Inst { .. } | ValueData::Param { .. } | ValueData::Union { .. } => false,
380+
}
381+
}
382+
375383
/// Get the type of a value.
376384
pub fn value_type(&self, v: Value) -> Type {
377385
self.values[v].ty()

cranelift/frontend/src/frontend/safepoints.rs

Lines changed: 162 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,8 @@ impl LivenessAnalysis {
367367
}
368368

369369
/// Process a value's definition, removing it from the currently-live set.
370-
fn process_def(&mut self, val: ir::Value) {
370+
fn process_def(&mut self, func: &Function, val: ir::Value) {
371+
debug_assert!(!func.dfg.value_is_alias(val));
371372
if self.currently_live.remove(&val) {
372373
log::trace!("liveness: defining {val:?}, removing it from the live set");
373374
}
@@ -385,6 +386,7 @@ impl LivenessAnalysis {
385386
// Keep order deterministic since we add stack map entries in this
386387
// order.
387388
live.sort();
389+
debug_assert!(live.iter().all(|v| !func.dfg.value_is_alias(*v)));
388390

389391
self.live_across_any_safepoint.extend(live.iter().copied());
390392
self.safepoints.insert(inst, live);
@@ -393,6 +395,7 @@ impl LivenessAnalysis {
393395
/// Process a use of a needs-stack-map value, inserting it into the
394396
/// currently-live set.
395397
fn process_use(&mut self, func: &Function, inst: Inst, val: Value) {
398+
debug_assert!(!func.dfg.value_is_alias(val));
396399
if self.currently_live.insert(val) {
397400
log::trace!(
398401
"liveness: found use of {val:?}, marking it live: {inst:?}: {}",
@@ -423,7 +426,7 @@ impl LivenessAnalysis {
423426
while let Some(inst) = option_inst {
424427
// Process any needs-stack-map values defined by this instruction.
425428
for val in func.dfg.inst_results(inst) {
426-
self.process_def(*val);
429+
self.process_def(func, *val);
427430
}
428431

429432
// If this instruction is a safepoint and we've been asked to record
@@ -447,7 +450,7 @@ impl LivenessAnalysis {
447450
// After we've processed this block's instructions, remove its
448451
// parameters from the live set. This is part of step (1).
449452
for val in func.dfg.block_params(block) {
450-
self.process_def(*val);
453+
self.process_def(func, *val);
451454
}
452455
}
453456

@@ -482,6 +485,11 @@ impl LivenessAnalysis {
482485
let successor_index = usize::try_from(successor_index).unwrap();
483486
self.live_outs[block_index].extend(self.live_ins[successor_index].iter().copied());
484487
}
488+
debug_assert!(
489+
self.live_outs[block_index]
490+
.iter()
491+
.all(|v| !func.dfg.value_is_alias(*v))
492+
);
485493

486494
// Process the block to compute its live-in set, but do not record
487495
// safepoints yet, as we haven't yet processed loop back edges (see
@@ -491,6 +499,11 @@ impl LivenessAnalysis {
491499
// The live-in set for a block is the set of values that are still
492500
// live after the block's instructions have been processed.
493501
self.live_ins[block_index].extend(self.currently_live.iter().copied());
502+
debug_assert!(
503+
self.live_ins[block_index]
504+
.iter()
505+
.all(|v| !func.dfg.value_is_alias(*v))
506+
);
494507

495508
// If the live-in set changed, then we need to revisit all this
496509
// block's predecessors.
@@ -552,6 +565,7 @@ impl StackSlots {
552565
}
553566

554567
fn get_or_create_stack_slot(&mut self, func: &mut Function, val: ir::Value) -> ir::StackSlot {
568+
debug_assert!(!func.dfg.value_is_alias(val));
555569
*self.stack_slots.entry(val).or_insert_with(|| {
556570
log::trace!("rewriting: {val:?} needs a stack slot");
557571
let ty = func.dfg.value_type(val);
@@ -631,6 +645,7 @@ impl SafepointSpiller {
631645
///
632646
/// The given cursor must point just after this value's definition.
633647
fn rewrite_def(&mut self, pos: &mut FuncCursor<'_>, val: ir::Value) {
648+
debug_assert!(!pos.func.dfg.value_is_alias(val));
634649
if let Some(slot) = self.stack_slots.get(val) {
635650
let i = pos.ins().stack_store(val, slot, 0);
636651
log::trace!(
@@ -664,6 +679,8 @@ impl SafepointSpiller {
664679
.expect("should only call `rewrite_safepoint` on safepoint instructions");
665680

666681
for val in live {
682+
debug_assert!(!func.dfg.value_is_alias(*val));
683+
667684
// Get or create the stack slot for this live needs-stack-map value.
668685
let slot = self.stack_slots.get_or_create_stack_slot(func, *val);
669686

@@ -692,6 +709,7 @@ impl SafepointSpiller {
692709
/// The given cursor must point just before the use of the value that we are
693710
/// replacing.
694711
fn rewrite_use(&mut self, pos: &mut FuncCursor<'_>, val: &mut ir::Value) -> bool {
712+
debug_assert!(!pos.func.dfg.value_is_alias(*val));
695713
if !self.liveness.live_across_any_safepoint.contains(*val) {
696714
return false;
697715
}
@@ -763,6 +781,7 @@ impl SafepointSpiller {
763781
vals.extend(pos.func.dfg.inst_values(inst));
764782
let mut replaced_any = false;
765783
for val in &mut vals {
784+
*val = pos.func.dfg.resolve_aliases(*val);
766785
replaced_any |= self.rewrite_use(&mut pos, val);
767786
}
768787
if replaced_any {
@@ -1846,13 +1865,15 @@ block0(v0: i32):
18461865
v2 -> v1
18471866
v4 -> v1
18481867
stack_store v1, ss0 ; v1 = 42
1849-
v13 = stack_load.i32 ss0
1850-
call fn0(v13), stack_map=[i32 @ ss0+0]
1868+
v17 = stack_load.i32 ss0
1869+
call fn0(v17), stack_map=[i32 @ ss0+0]
18511870
brif v0, block1, block2
18521871
18531872
block1:
1854-
call fn0(v2), stack_map=[i32 @ ss0+0] ; v2 = 42
1855-
call fn0(v2) ; v2 = 42
1873+
v12 = stack_load.i32 ss0
1874+
call fn0(v12), stack_map=[i32 @ ss0+0]
1875+
v11 = stack_load.i32 ss0
1876+
call fn0(v11)
18561877
v3 = iconst.i32 36
18571878
stack_store v3, ss0 ; v3 = 36
18581879
v10 = stack_load.i32 ss0
@@ -1861,14 +1882,16 @@ block1:
18611882
jump block3(v9)
18621883
18631884
block2:
1864-
call fn0(v4), stack_map=[i32 @ ss0+0] ; v4 = 42
1865-
call fn0(v4) ; v4 = 42
1885+
v16 = stack_load.i32 ss0
1886+
call fn0(v16), stack_map=[i32 @ ss0+0]
1887+
v15 = stack_load.i32 ss0
1888+
call fn0(v15)
18661889
v5 = iconst.i32 36
18671890
stack_store v5, ss1 ; v5 = 36
1868-
v12 = stack_load.i32 ss1
1869-
call fn0(v12), stack_map=[i32 @ ss1+0]
1870-
v11 = stack_load.i32 ss1
1871-
jump block3(v11)
1891+
v14 = stack_load.i32 ss1
1892+
call fn0(v14), stack_map=[i32 @ ss1+0]
1893+
v13 = stack_load.i32 ss1
1894+
jump block3(v13)
18721895
18731896
block3(v6: i32):
18741897
stack_store v6, ss0
@@ -2731,38 +2754,44 @@ block3:
27312754
jump block2(v7)
27322755
27332756
block4:
2734-
v26 = stack_load.i32 ss1
2735-
jump block5(v26)
2757+
v32 = stack_load.i32 ss1
2758+
jump block5(v32)
27362759
27372760
block5(v20: i32):
27382761
v19 -> v20
27392762
stack_store v20, ss2
27402763
v9 = iconst.i32 0
2741-
v10 = icmp.i32 eq v8, v9 ; v9 = 0
2764+
v31 = stack_load.i32 ss0
2765+
v10 = icmp eq v31, v9 ; v9 = 0
27422766
brif v10, block8(v9), block6 ; v9 = 0
27432767
27442768
block6:
2745-
v11 = call fn3(v8), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2769+
v30 = stack_load.i32 ss0
2770+
v11 = call fn3(v30), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
27462771
v12 = iconst.i32 -1091584273
27472772
v13 = icmp eq v11, v12 ; v12 = -1091584273
27482773
v14 = iconst.i32 1
27492774
brif v13, block8(v14), block7 ; v14 = 1
27502775
27512776
block7:
2752-
v15 = call fn4(v8, v12), stack_map=[i32 @ ss0+0, i32 @ ss2+0] ; v12 = -1091584273
2777+
v29 = stack_load.i32 ss0
2778+
v15 = call fn4(v29, v12), stack_map=[i32 @ ss0+0, i32 @ ss2+0] ; v12 = -1091584273
27532779
jump block8(v15)
27542780
27552781
block8(v16: i32):
27562782
trapz v16, user1
2757-
call fn5(v8), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2783+
v28 = stack_load.i32 ss0
2784+
call fn5(v28), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
27582785
v17 = call fn6(), stack_map=[i32 @ ss0+0, i32 @ ss2+0]
2759-
brif v17, block5(v19), block9
2786+
v27 = stack_load.i32 ss2
2787+
brif v17, block5(v27), block9
27602788
27612789
block9:
2762-
v25 = stack_load.i32 ss2
2763-
call fn7(v25), stack_map=[i32 @ ss2+0]
2790+
v26 = stack_load.i32 ss2
2791+
call fn7(v26), stack_map=[i32 @ ss2+0]
27642792
v23 = call fn8(), stack_map=[i32 @ ss2+0]
2765-
brif v23, block10, block1(v19)
2793+
v25 = stack_load.i32 ss2
2794+
brif v23, block10, block1(v25)
27662795
27672796
block10:
27682797
return
@@ -2876,4 +2905,114 @@ block10:
28762905
"try_call should have stack_map entry for v0 (spilled to ss0), got:\n{output}"
28772906
);
28782907
}
2908+
2909+
#[test]
2910+
fn rewrite_uses_of_alias_values() {
2911+
let _ = env_logger::try_init();
2912+
2913+
let mut sig = Signature::new(CallConv::SystemV);
2914+
sig.params.push(AbiParam::new(ir::types::I32));
2915+
let mut fn_ctx = FunctionBuilderContext::new();
2916+
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
2917+
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
2918+
2919+
// fn0: () -> i32 (returns a gc ref)
2920+
let name0 = builder
2921+
.func
2922+
.declare_imported_user_function(ir::UserExternalName {
2923+
namespace: 0,
2924+
index: 0,
2925+
});
2926+
let mut sig = Signature::new(CallConv::SystemV);
2927+
sig.returns.push(AbiParam::new(ir::types::I32));
2928+
let signature = builder.func.import_signature(sig);
2929+
let fn0 = builder.import_function(ir::ExtFuncData {
2930+
name: ir::ExternalName::user(name0),
2931+
signature,
2932+
colocated: true,
2933+
patchable: false,
2934+
});
2935+
2936+
// fn1: (i32) -> () (consumes a gc ref)
2937+
let name1 = builder
2938+
.func
2939+
.declare_imported_user_function(ir::UserExternalName {
2940+
namespace: 0,
2941+
index: 1,
2942+
});
2943+
let mut sig = Signature::new(CallConv::SystemV);
2944+
sig.params.push(AbiParam::new(ir::types::I32));
2945+
let signature = builder.func.import_signature(sig);
2946+
let fn1 = builder.import_function(ir::ExtFuncData {
2947+
name: ir::ExternalName::user(name1),
2948+
signature,
2949+
colocated: true,
2950+
patchable: false,
2951+
});
2952+
2953+
let var = builder.declare_var(ir::types::I32);
2954+
builder.declare_var_needs_stack_map(var);
2955+
2956+
let block0 = builder.create_block();
2957+
let block1 = builder.create_block();
2958+
let block2 = builder.create_block();
2959+
2960+
builder.append_block_params_for_function_params(block0);
2961+
builder.switch_to_block(block0);
2962+
let v0 = builder.func.dfg.block_params(block0)[0];
2963+
let inst = builder.ins().call(fn0, &[]);
2964+
let v1 = builder.func.dfg.first_result(inst);
2965+
builder.def_var(var, v1);
2966+
builder.ins().brif(v0, block1, &[], block2, &[]);
2967+
2968+
builder.switch_to_block(block1);
2969+
builder.def_var(var, v1);
2970+
builder.ins().jump(block2, &[]);
2971+
2972+
builder.switch_to_block(block2);
2973+
let v2 = builder.use_var(var);
2974+
builder.ins().call(fn1, &[v2]);
2975+
let v3 = builder.use_var(var);
2976+
builder.ins().call(fn1, &[v3]);
2977+
builder.ins().return_(&[]);
2978+
2979+
builder.seal_all_blocks();
2980+
2981+
builder.finalize();
2982+
2983+
// The SSA construction / `Variable` infrastructure makes this value
2984+
// into an alias. But it is also an alias of a value that needs
2985+
// inclusion in stack maps, so we should reload it from the stack before
2986+
// it is passed into `fn1` in the disassembly below, rather than pass
2987+
// the original or aliased value directly.
2988+
assert!(func.dfg.value_is_alias(v3));
2989+
assert_eq_output!(
2990+
func.display().to_string(),
2991+
r#"
2992+
function %sample(i32) system_v {
2993+
ss0 = explicit_slot 4, align = 4
2994+
sig0 = () -> i32 system_v
2995+
sig1 = (i32) system_v
2996+
fn0 = colocated u0:0 sig0
2997+
fn1 = colocated u0:1 sig1
2998+
2999+
block0(v0: i32):
3000+
v1 = call fn0()
3001+
v2 -> v1
3002+
stack_store v1, ss0
3003+
brif v0, block1, block2
3004+
3005+
block1:
3006+
jump block2
3007+
3008+
block2:
3009+
v4 = stack_load.i32 ss0
3010+
call fn1(v4), stack_map=[i32 @ ss0+0]
3011+
v3 = stack_load.i32 ss0
3012+
call fn1(v3)
3013+
return
3014+
}
3015+
"#,
3016+
);
3017+
}
28793018
}

0 commit comments

Comments
 (0)