use super::*;
use crate::{HashMap, HashSet};
use core::ops::{Index, IndexMut};
#[derive(Clone, Copy)]
#[repr(u8)]
enum SlotSize {
Size8 = 0,
Size16 = 1,
Size32 = 2,
Size64 = 3,
Size128 = 4,
}
const SLOT_SIZE_LEN: usize = 5;
impl TryFrom<ir::Type> for SlotSize {
type Error = &'static str;
fn try_from(ty: ir::Type) -> Result<Self, Self::Error> {
Self::new(ty.bytes()).ok_or("type is not supported in stack maps")
}
}
impl SlotSize {
fn new(bytes: u32) -> Option<Self> {
match bytes {
1 => Some(Self::Size8),
2 => Some(Self::Size16),
4 => Some(Self::Size32),
8 => Some(Self::Size64),
16 => Some(Self::Size128),
_ => None,
}
}
fn unwrap_new(bytes: u32) -> Self {
Self::new(bytes).unwrap_or_else(|| panic!("cannot create a `SlotSize` for {bytes} bytes"))
}
}
struct SlotSizeMap<T>([T; SLOT_SIZE_LEN]);
impl<T> Default for SlotSizeMap<T>
where
T: Default,
{
fn default() -> Self {
Self::new()
}
}
impl<T> Index<SlotSize> for SlotSizeMap<T> {
type Output = T;
fn index(&self, index: SlotSize) -> &Self::Output {
self.get(index)
}
}
impl<T> IndexMut<SlotSize> for SlotSizeMap<T> {
fn index_mut(&mut self, index: SlotSize) -> &mut Self::Output {
self.get_mut(index)
}
}
impl<T> SlotSizeMap<T> {
fn new() -> Self
where
T: Default,
{
Self([
T::default(),
T::default(),
T::default(),
T::default(),
T::default(),
])
}
fn clear(&mut self)
where
T: Default,
{
*self = Self::new();
}
fn get(&self, size: SlotSize) -> &T {
let index = size as u8 as usize;
&self.0[index]
}
fn get_mut(&mut self, size: SlotSize) -> &mut T {
let index = size as u8 as usize;
&mut self.0[index]
}
}
type LiveSet = HashSet<ir::Value>;
#[derive(Default)]
struct Worklist {
stack: Vec<u32>,
need_updates: HashSet<u32>,
}
impl Extend<u32> for Worklist {
fn extend<T>(&mut self, iter: T)
where
T: IntoIterator<Item = u32>,
{
for block_index in iter {
self.push(block_index);
}
}
}
impl Worklist {
fn clear(&mut self) {
let Worklist {
stack,
need_updates,
} = self;
stack.clear();
need_updates.clear();
}
fn reserve(&mut self, capacity: usize) {
let Worklist {
stack,
need_updates,
} = self;
stack.reserve(capacity);
need_updates.reserve(capacity);
}
fn push(&mut self, block_index: u32) {
self.need_updates.insert(block_index);
self.stack.push(block_index);
}
fn pop(&mut self) -> Option<u32> {
while let Some(block_index) = self.stack.pop() {
if self.need_updates.remove(&block_index) {
return Some(block_index);
}
}
None
}
}
pub(crate) struct LivenessAnalysis {
dfs: Dfs,
post_order: Vec<ir::Block>,
block_to_index: SecondaryMap<ir::Block, u32>,
predecessors: Vec<SmallVec<[u32; 4]>>,
worklist: Worklist,
live_ins: Vec<LiveSet>,
live_outs: Vec<LiveSet>,
currently_live: LiveSet,
safepoints: HashMap<ir::Inst, SmallVec<[ir::Value; 4]>>,
live_across_any_safepoint: EntitySet<ir::Value>,
}
impl Default for LivenessAnalysis {
fn default() -> Self {
Self {
dfs: Default::default(),
post_order: Default::default(),
block_to_index: SecondaryMap::with_default(u32::MAX),
predecessors: Default::default(),
worklist: Default::default(),
live_ins: Default::default(),
live_outs: Default::default(),
currently_live: Default::default(),
safepoints: Default::default(),
live_across_any_safepoint: Default::default(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RecordSafepoints {
Yes,
No,
}
impl LivenessAnalysis {
pub fn clear(&mut self) {
let LivenessAnalysis {
dfs,
post_order,
block_to_index,
predecessors,
worklist,
live_ins,
live_outs,
currently_live,
safepoints,
live_across_any_safepoint,
} = self;
dfs.clear();
post_order.clear();
block_to_index.clear();
predecessors.clear();
worklist.clear();
live_ins.clear();
live_outs.clear();
currently_live.clear();
safepoints.clear();
live_across_any_safepoint.clear();
}
fn reserve_capacity(&mut self, func: &Function) {
let LivenessAnalysis {
dfs: _,
post_order,
block_to_index,
predecessors,
worklist,
live_ins,
live_outs,
currently_live: _,
safepoints: _,
live_across_any_safepoint: _,
} = self;
block_to_index.resize(func.dfg.num_blocks());
let capacity = post_order.len();
worklist.reserve(capacity);
predecessors.resize(capacity, Default::default());
live_ins.resize(capacity, Default::default());
live_outs.resize(capacity, Default::default());
}
fn initialize_block_to_index_map(&mut self) {
for (block_index, block) in self.post_order.iter().enumerate() {
self.block_to_index[*block] = u32::try_from(block_index).unwrap();
}
}
fn initialize_predecessors_map(&mut self, func: &Function) {
for (block_index, block) in self.post_order.iter().enumerate() {
let block_index = u32::try_from(block_index).unwrap();
for succ in func.block_successors(*block) {
let succ_index = self.block_to_index[succ];
debug_assert_ne!(succ_index, u32::MAX);
let succ_index = usize::try_from(succ_index).unwrap();
self.predecessors[succ_index].push(block_index);
}
}
}
fn process_def(&mut self, val: ir::Value) {
if self.currently_live.remove(&val) {
log::trace!("liveness: defining {val:?}, removing it from the live set");
}
}
fn record_safepoint(&mut self, func: &Function, inst: Inst) {
log::trace!(
"liveness: found safepoint: {inst:?}: {}",
func.dfg.display_inst(inst)
);
log::trace!("liveness: live set = {:?}", self.currently_live);
let mut live = self.currently_live.iter().copied().collect::<SmallVec<_>>();
live.sort();
self.live_across_any_safepoint.extend(live.iter().copied());
self.safepoints.insert(inst, live);
}
fn process_use(&mut self, func: &Function, inst: Inst, val: Value) {
if self.currently_live.insert(val) {
log::trace!(
"liveness: found use of {val:?}, marking it live: {inst:?}: {}",
func.dfg.display_inst(inst)
);
}
}
fn process_block(
&mut self,
func: &mut Function,
stack_map_values: &EntitySet<ir::Value>,
block_index: usize,
record_safepoints: RecordSafepoints,
) {
let block = self.post_order[block_index];
log::trace!("liveness: traversing {block:?}");
self.currently_live.clear();
self.currently_live
.extend(self.live_outs[block_index].iter().copied());
let mut option_inst = func.layout.last_inst(block);
while let Some(inst) = option_inst {
for val in func.dfg.inst_results(inst) {
self.process_def(*val);
}
let opcode = func.dfg.insts[inst].opcode();
if record_safepoints == RecordSafepoints::Yes && opcode.is_safepoint() {
self.record_safepoint(func, inst);
}
for val in func.dfg.inst_values(inst) {
let val = func.dfg.resolve_aliases(val);
if stack_map_values.contains(val) {
self.process_use(func, inst, val);
}
}
option_inst = func.layout.prev_inst(inst);
}
for val in func.dfg.block_params(block) {
self.process_def(*val);
}
}
pub fn run(&mut self, func: &mut Function, stack_map_values: &EntitySet<ir::Value>) {
self.clear();
self.post_order.extend(self.dfs.post_order_iter(func));
self.reserve_capacity(func);
self.initialize_block_to_index_map();
self.initialize_predecessors_map(func);
self.worklist
.extend((0..u32::try_from(self.post_order.len()).unwrap()).rev());
while let Some(block_index) = self.worklist.pop() {
let block_index = usize::try_from(block_index).unwrap();
let initial_live_in_len = self.live_ins[block_index].len();
for successor in func.block_successors(self.post_order[block_index]) {
let successor_index = self.block_to_index[successor];
debug_assert_ne!(successor_index, u32::MAX);
let successor_index = usize::try_from(successor_index).unwrap();
self.live_outs[block_index].extend(self.live_ins[successor_index].iter().copied());
}
self.process_block(func, stack_map_values, block_index, RecordSafepoints::No);
self.live_ins[block_index].extend(self.currently_live.iter().copied());
if self.live_ins[block_index].len() != initial_live_in_len {
self.worklist
.extend(self.predecessors[block_index].iter().copied());
}
}
for block_index in 0..self.post_order.len() {
self.process_block(func, stack_map_values, block_index, RecordSafepoints::Yes);
debug_assert_eq!(
self.currently_live, self.live_ins[block_index],
"when we recompute the live-in set for a block as part of \
computing live sets at each safepoint, we should get the same \
result we computed in the fixed-point"
);
}
}
}
#[derive(Default)]
struct StackSlots {
stack_slots: HashMap<ir::Value, ir::StackSlot>,
free_stack_slots: SlotSizeMap<SmallVec<[ir::StackSlot; 4]>>,
}
impl StackSlots {
fn clear(&mut self) {
let StackSlots {
stack_slots,
free_stack_slots,
} = self;
stack_slots.clear();
free_stack_slots.clear();
}
fn get(&self, val: ir::Value) -> Option<ir::StackSlot> {
self.stack_slots.get(&val).copied()
}
fn get_or_create_stack_slot(&mut self, func: &mut Function, val: ir::Value) -> ir::StackSlot {
*self.stack_slots.entry(val).or_insert_with(|| {
log::trace!("rewriting: {val:?} needs a stack slot");
let ty = func.dfg.value_type(val);
let size = ty.bytes();
match self.free_stack_slots[SlotSize::unwrap_new(size)].pop() {
Some(slot) => {
log::trace!("rewriting: reusing free stack slot {slot:?} for {val:?}");
slot
}
None => {
debug_assert!(size.is_power_of_two());
let log2_size = size.ilog2();
let slot = func.create_sized_stack_slot(ir::StackSlotData::new(
ir::StackSlotKind::ExplicitSlot,
size,
log2_size.try_into().unwrap(),
));
log::trace!("rewriting: created new stack slot {slot:?} for {val:?}");
slot
}
}
})
}
fn free_stack_slot(&mut self, size: SlotSize, slot: ir::StackSlot) {
log::trace!("rewriting: returning {slot:?} to the free list");
self.free_stack_slots[size].push(slot);
}
}
#[derive(Default)]
pub(super) struct SafepointSpiller {
liveness: LivenessAnalysis,
stack_slots: StackSlots,
}
impl SafepointSpiller {
pub fn clear(&mut self) {
let SafepointSpiller {
liveness,
stack_slots,
} = self;
liveness.clear();
stack_slots.clear();
}
pub fn run(&mut self, func: &mut Function, stack_map_values: &EntitySet<ir::Value>) {
log::trace!(
"values needing inclusion in stack maps: {:?}",
stack_map_values
);
log::trace!(
"before inserting safepoint spills and reloads:\n{}",
func.display()
);
self.clear();
self.liveness.run(func, stack_map_values);
self.rewrite(func);
log::trace!(
"after inserting safepoint spills and reloads:\n{}",
func.display()
);
}
fn rewrite_def(&mut self, pos: &mut FuncCursor<'_>, val: ir::Value) {
if let Some(slot) = self.stack_slots.get(val) {
let i = pos.ins().stack_store(val, slot, 0);
log::trace!(
"rewriting: spilling {val:?} to {slot:?}: {}",
pos.func.dfg.display_inst(i)
);
let ty = pos.func.dfg.value_type(val);
let size = SlotSize::try_from(ty).unwrap();
self.stack_slots.free_stack_slot(size, slot);
}
}
fn rewrite_safepoint(&mut self, func: &mut Function, inst: ir::Inst) {
log::trace!(
"rewriting: found safepoint: {inst:?}: {}",
func.dfg.display_inst(inst)
);
let live = self
.liveness
.safepoints
.get(&inst)
.expect("should only call `rewrite_safepoint` on safepoint instructions");
for val in live {
let slot = self.stack_slots.get_or_create_stack_slot(func, *val);
log::trace!(
"rewriting: adding stack map entry for {val:?} at {slot:?}: {}",
func.dfg.display_inst(inst)
);
let ty = func.dfg.value_type(*val);
func.dfg.append_user_stack_map_entry(
inst,
ir::UserStackMapEntry {
ty,
slot,
offset: 0,
},
);
}
}
fn rewrite_use(&mut self, pos: &mut FuncCursor<'_>, val: &mut ir::Value) -> bool {
if !self.liveness.live_across_any_safepoint.contains(*val) {
return false;
}
let old_val = *val;
log::trace!("rewriting: found use of {old_val:?}");
let ty = pos.func.dfg.value_type(*val);
let slot = self.stack_slots.get_or_create_stack_slot(pos.func, *val);
*val = pos.ins().stack_load(ty, slot, 0);
log::trace!(
"rewriting: reloading {old_val:?}: {}",
pos.func
.dfg
.display_inst(pos.func.dfg.value_def(*val).unwrap_inst())
);
true
}
fn rewrite(&mut self, func: &mut Function) {
let mut vals: SmallVec<[_; 8]> = Default::default();
for block_index in 0..self.liveness.post_order.len() {
let block = self.liveness.post_order[block_index];
log::trace!("rewriting: processing {block:?}");
let mut option_inst = func.layout.last_inst(block);
while let Some(inst) = option_inst {
let mut pos = FuncCursor::new(func).after_inst(inst);
vals.extend_from_slice(pos.func.dfg.inst_results(inst));
for val in vals.drain(..) {
self.rewrite_def(&mut pos, val);
}
if self.liveness.safepoints.contains_key(&inst) {
self.rewrite_safepoint(func, inst);
}
let mut pos = FuncCursor::new(func).at_inst(inst);
vals.extend(pos.func.dfg.inst_values(inst));
let mut replaced_any = false;
for val in &mut vals {
replaced_any |= self.rewrite_use(&mut pos, val);
}
if replaced_any {
pos.func.dfg.overwrite_inst_values(inst, vals.drain(..));
log::trace!(
"rewriting: updated {inst:?} operands with reloaded values: {}",
pos.func.dfg.display_inst(inst)
);
} else {
vals.clear();
}
option_inst = func.layout.prev_inst(inst);
}
let mut pos = FuncCursor::new(func).at_position(CursorPosition::Before(block));
pos.next_inst(); vals.clear();
vals.extend_from_slice(pos.func.dfg.block_params(block));
for val in vals.drain(..) {
self.rewrite_def(&mut pos, val);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use cranelift_codegen::isa::CallConv;
#[test]
fn needs_stack_map_and_loop() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let signature = builder.func.import_signature(sig);
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
let a = builder.func.dfg.block_params(block0)[0];
let b = builder.func.dfg.block_params(block0)[1];
builder.declare_value_needs_stack_map(a);
builder.declare_value_needs_stack_map(b);
builder.switch_to_block(block0);
builder.ins().call(func_ref, &[a]);
builder.ins().jump(block0, &[a, b]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32, i32) system_v {
ss0 = explicit_slot 4, align = 4
ss1 = explicit_slot 4, align = 4
sig0 = (i32) system_v
fn0 = colocated u0:0 sig0
block0(v0: i32, v1: i32):
stack_store v0, ss0
stack_store v1, ss1
v4 = stack_load.i32 ss0
call fn0(v4), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
v2 = stack_load.i32 ss0
v3 = stack_load.i32 ss1
jump block0(v2, v3)
}
"#
);
}
#[test]
fn needs_stack_map_simple() {
let _ = env_logger::try_init();
let sig = Signature::new(CallConv::SystemV);
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let signature = builder.func.import_signature(sig);
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.ins().iconst(ir::types::I32, 0);
builder.declare_value_needs_stack_map(v0);
let v1 = builder.ins().iconst(ir::types::I32, 1);
builder.declare_value_needs_stack_map(v1);
let v2 = builder.ins().iconst(ir::types::I32, 2);
builder.declare_value_needs_stack_map(v2);
let v3 = builder.ins().iconst(ir::types::I32, 3);
builder.declare_value_needs_stack_map(v3);
builder.ins().call(func_ref, &[v3]);
builder.ins().call(func_ref, &[v0]);
builder.ins().call(func_ref, &[v1]);
builder.ins().call(func_ref, &[v2]);
builder.ins().return_(&[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample() system_v {
ss0 = explicit_slot 4, align = 4
ss1 = explicit_slot 4, align = 4
ss2 = explicit_slot 4, align = 4
sig0 = (i32) system_v
fn0 = colocated u0:0 sig0
block0:
v0 = iconst.i32 0
stack_store v0, ss2 ; v0 = 0
v1 = iconst.i32 1
stack_store v1, ss1 ; v1 = 1
v2 = iconst.i32 2
stack_store v2, ss0 ; v2 = 2
v3 = iconst.i32 3
call fn0(v3), stack_map=[i32 @ ss2+0, i32 @ ss1+0, i32 @ ss0+0] ; v3 = 3
v6 = stack_load.i32 ss2
call fn0(v6), stack_map=[i32 @ ss1+0, i32 @ ss0+0]
v5 = stack_load.i32 ss1
call fn0(v5), stack_map=[i32 @ ss0+0]
v4 = stack_load.i32 ss0
call fn0(v4)
return
}
"#
);
}
#[test]
fn needs_stack_map_and_post_order_early_return() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
let block3 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
builder.declare_value_needs_stack_map(v1);
builder.ins().jump(block3, &[]);
builder.switch_to_block(block2);
builder.ins().call(func_ref, &[]);
builder.ins().return_(&[]);
builder.switch_to_block(block3);
builder.ins().iadd_imm(v1, 0);
builder.ins().return_(&[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) system_v {
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
brif v0, block1, block2
block1:
v1 = iconst.i64 0x1234_5678
jump block3
block2:
call fn0()
return
block3:
v2 = iadd_imm.i64 v1, 0 ; v1 = 0x1234_5678
return
}
"#
);
}
#[test]
fn needs_stack_map_conditional_branches_and_liveness() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
builder.declare_value_needs_stack_map(v1);
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
builder.ins().call(func_ref, &[]);
builder.ins().return_(&[]);
builder.switch_to_block(block2);
builder.ins().iadd_imm(v1, 0);
builder.ins().return_(&[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) system_v {
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
v1 = iconst.i64 0x1234_5678
brif v0, block1, block2
block1:
call fn0()
return
block2:
v2 = iadd_imm.i64 v1, 0 ; v1 = 0x1234_5678
return
}
"#
);
func.clear();
fn_ctx.clear();
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
func.signature = sig;
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
builder.declare_value_needs_stack_map(v1);
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
builder.ins().iadd_imm(v1, 0);
builder.ins().return_(&[]);
builder.switch_to_block(block2);
builder.ins().call(func_ref, &[]);
builder.ins().return_(&[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function u0:0(i32) system_v {
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
v1 = iconst.i64 0x1234_5678
brif v0, block1, block2
block1:
v2 = iadd_imm.i64 v1, 0 ; v1 = 0x1234_5678
return
block2:
call fn0()
return
}
"#
);
}
#[test]
fn needs_stack_map_and_tail_calls() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
builder.declare_value_needs_stack_map(v1);
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
builder.ins().return_call(func_ref, &[]);
builder.switch_to_block(block2);
builder.ins().iadd_imm(v1, 0);
builder.ins().return_(&[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) system_v {
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
v1 = iconst.i64 0x1234_5678
brif v0, block1, block2
block1:
return_call fn0()
block2:
v2 = iadd_imm.i64 v1, 0 ; v1 = 0x1234_5678
return
}
"#
);
func.clear();
fn_ctx.clear();
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
func.signature = sig;
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
let v1 = builder.ins().iconst(ir::types::I64, 0x12345678);
builder.declare_value_needs_stack_map(v1);
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
builder.ins().iadd_imm(v1, 0);
builder.ins().return_(&[]);
builder.switch_to_block(block2);
builder.ins().return_call(func_ref, &[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function u0:0(i32) system_v {
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
v1 = iconst.i64 0x1234_5678
brif v0, block1, block2
block1:
v2 = iadd_imm.i64 v1, 0 ; v1 = 0x1234_5678
return
block2:
return_call fn0()
}
"#
);
}
#[test]
fn needs_stack_map_and_cfg_diamond() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
let block3 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
let v1 = builder.ins().iconst(ir::types::I64, 1);
builder.declare_value_needs_stack_map(v1);
let v2 = builder.ins().iconst(ir::types::I64, 2);
builder.declare_value_needs_stack_map(v2);
builder.ins().call(func_ref, &[]);
builder.ins().jump(block3, &[v1, v2]);
builder.switch_to_block(block2);
let v3 = builder.ins().iconst(ir::types::I64, 3);
builder.declare_value_needs_stack_map(v3);
let v4 = builder.ins().iconst(ir::types::I64, 4);
builder.declare_value_needs_stack_map(v4);
builder.ins().call(func_ref, &[]);
builder.ins().jump(block3, &[v3, v3]);
builder.switch_to_block(block3);
builder.append_block_param(block3, ir::types::I64);
builder.append_block_param(block3, ir::types::I64);
builder.ins().call(func_ref, &[]);
builder.ins().iadd_imm(v1, 0);
builder.ins().return_(&[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) system_v {
ss0 = explicit_slot 8, align = 8
ss1 = explicit_slot 8, align = 8
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
brif v0, block1, block2
block1:
v1 = iconst.i64 1
stack_store v1, ss0 ; v1 = 1
v2 = iconst.i64 2
stack_store v2, ss1 ; v2 = 2
call fn0(), stack_map=[i64 @ ss0+0, i64 @ ss1+0]
v9 = stack_load.i64 ss0
v10 = stack_load.i64 ss1
jump block3(v9, v10)
block2:
v3 = iconst.i64 3
stack_store v3, ss0 ; v3 = 3
v4 = iconst.i64 4
call fn0(), stack_map=[i64 @ ss0+0, i64 @ ss0+0]
v11 = stack_load.i64 ss0
v12 = stack_load.i64 ss0
jump block3(v11, v12)
block3(v5: i64, v6: i64):
call fn0(), stack_map=[i64 @ ss0+0]
v8 = stack_load.i64 ss0
v7 = iadd_imm v8, 0
return
}
"#
);
}
#[test]
fn needs_stack_map_and_heterogeneous_types() {
let _ = env_logger::try_init();
let mut sig = Signature::new(CallConv::SystemV);
for ty in [
ir::types::I8,
ir::types::I16,
ir::types::I32,
ir::types::I64,
ir::types::I128,
ir::types::F32,
ir::types::F64,
ir::types::I8X16,
ir::types::I16X8,
] {
sig.params.push(AbiParam::new(ty));
sig.returns.push(AbiParam::new(ty));
}
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let params = builder.func.dfg.block_params(block0).to_vec();
for val in ¶ms {
builder.declare_value_needs_stack_map(*val);
}
builder.ins().call(func_ref, &[]);
builder.ins().return_(¶ms);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i8, i16, i32, i64, i128, f32, f64, i8x16, i16x8) -> i8, i16, i32, i64, i128, f32, f64, i8x16, i16x8 system_v {
ss0 = explicit_slot 1
ss1 = explicit_slot 2, align = 2
ss2 = explicit_slot 4, align = 4
ss3 = explicit_slot 8, align = 8
ss4 = explicit_slot 16, align = 16
ss5 = explicit_slot 4, align = 4
ss6 = explicit_slot 8, align = 8
ss7 = explicit_slot 16, align = 16
ss8 = explicit_slot 16, align = 16
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i8, v1: i16, v2: i32, v3: i64, v4: i128, v5: f32, v6: f64, v7: i8x16, v8: i16x8):
stack_store v0, ss0
stack_store v1, ss1
stack_store v2, ss2
stack_store v3, ss3
stack_store v4, ss4
stack_store v5, ss5
stack_store v6, ss6
stack_store v7, ss7
stack_store v8, ss8
call fn0(), stack_map=[i8 @ ss0+0, i16 @ ss1+0, i32 @ ss2+0, i64 @ ss3+0, i128 @ ss4+0, f32 @ ss5+0, f64 @ ss6+0, i8x16 @ ss7+0, i16x8 @ ss8+0]
v9 = stack_load.i8 ss0
v10 = stack_load.i16 ss1
v11 = stack_load.i32 ss2
v12 = stack_load.i64 ss3
v13 = stack_load.i128 ss4
v14 = stack_load.f32 ss5
v15 = stack_load.f64 ss6
v16 = stack_load.i8x16 ss7
v17 = stack_load.i16x8 ss8
return v9, v10, v11, v12, v13, v14, v15, v16, v17
}
"#
);
}
#[test]
fn series_of_non_overlapping_live_ranges_needs_stack_map() {
let sig = Signature::new(CallConv::SystemV);
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let foo_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 1,
});
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let signature = builder.func.import_signature(sig);
let consume_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.ins().iconst(ir::types::I32, 0);
builder.declare_value_needs_stack_map(v0);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(consume_func_ref, &[v0]);
let v1 = builder.ins().iconst(ir::types::I32, 1);
builder.declare_value_needs_stack_map(v1);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(consume_func_ref, &[v1]);
let v2 = builder.ins().iconst(ir::types::I32, 2);
builder.declare_value_needs_stack_map(v2);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(consume_func_ref, &[v2]);
let v3 = builder.ins().iconst(ir::types::I32, 3);
builder.declare_value_needs_stack_map(v3);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(consume_func_ref, &[v3]);
builder.ins().return_(&[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample() system_v {
ss0 = explicit_slot 4, align = 4
sig0 = () system_v
sig1 = (i32) system_v
fn0 = colocated u0:0 sig0
fn1 = colocated u0:1 sig1
block0:
v0 = iconst.i32 0
stack_store v0, ss0 ; v0 = 0
call fn0(), stack_map=[i32 @ ss0+0]
v7 = stack_load.i32 ss0
call fn1(v7)
v1 = iconst.i32 1
stack_store v1, ss0 ; v1 = 1
call fn0(), stack_map=[i32 @ ss0+0]
v6 = stack_load.i32 ss0
call fn1(v6)
v2 = iconst.i32 2
stack_store v2, ss0 ; v2 = 2
call fn0(), stack_map=[i32 @ ss0+0]
v5 = stack_load.i32 ss0
call fn1(v5)
v3 = iconst.i32 3
stack_store v3, ss0 ; v3 = 3
call fn0(), stack_map=[i32 @ ss0+0]
v4 = stack_load.i32 ss0
call fn1(v4)
return
}
"#
);
}
#[test]
fn vars_block_params_and_needs_stack_map() {
let _ = env_logger::try_init();
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
sig.returns.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let signature = builder.func.import_signature(sig);
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let x = Variable::from_u32(0);
builder.declare_var(x, ir::types::I32);
builder.declare_var_needs_stack_map(x);
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
let block3 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
let val = builder.ins().iconst(ir::types::I32, 42);
builder.def_var(x, val);
{
let x = builder.use_var(x);
builder.ins().call(func_ref, &[x]);
}
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
{
let x = builder.use_var(x);
builder.ins().call(func_ref, &[x]);
builder.ins().call(func_ref, &[x]);
}
let val = builder.ins().iconst(ir::types::I32, 36);
builder.def_var(x, val);
{
let x = builder.use_var(x);
builder.ins().call(func_ref, &[x]);
}
builder.ins().jump(block3, &[]);
builder.switch_to_block(block2);
{
let x = builder.use_var(x);
builder.ins().call(func_ref, &[x]);
builder.ins().call(func_ref, &[x]);
}
let val = builder.ins().iconst(ir::types::I32, 36);
builder.def_var(x, val);
{
let x = builder.use_var(x);
builder.ins().call(func_ref, &[x]);
}
builder.ins().jump(block3, &[]);
builder.switch_to_block(block3);
let x = builder.use_var(x);
builder.ins().call(func_ref, &[x]);
builder.ins().return_(&[x]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) -> i32 system_v {
ss0 = explicit_slot 4, align = 4
ss1 = explicit_slot 4, align = 4
sig0 = (i32) system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
v1 = iconst.i32 42
v2 -> v1
v4 -> v1
stack_store v1, ss0 ; v1 = 42
v13 = stack_load.i32 ss0
call fn0(v13), stack_map=[i32 @ ss0+0]
brif v0, block1, block2
block1:
call fn0(v2), stack_map=[i32 @ ss0+0] ; v2 = 42
call fn0(v2) ; v2 = 42
v3 = iconst.i32 36
stack_store v3, ss0 ; v3 = 36
v10 = stack_load.i32 ss0
call fn0(v10), stack_map=[i32 @ ss0+0]
v9 = stack_load.i32 ss0
jump block3(v9)
block2:
call fn0(v4), stack_map=[i32 @ ss0+0] ; v4 = 42
call fn0(v4) ; v4 = 42
v5 = iconst.i32 36
stack_store v5, ss1 ; v5 = 36
v12 = stack_load.i32 ss1
call fn0(v12), stack_map=[i32 @ ss1+0]
v11 = stack_load.i32 ss1
jump block3(v11)
block3(v6: i32):
stack_store v6, ss0
v8 = stack_load.i32 ss0
call fn0(v8), stack_map=[i32 @ ss0+0]
v7 = stack_load.i32 ss0
return v7
}
"#
);
}
#[test]
fn var_needs_stack_map() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params
.push(AbiParam::new(cranelift_codegen::ir::types::I32));
sig.returns
.push(AbiParam::new(cranelift_codegen::ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let var = Variable::from_u32(0);
builder.declare_var(var, cranelift_codegen::ir::types::I32);
builder.declare_var_needs_stack_map(var);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let arg = builder.func.dfg.block_params(block0)[0];
builder.def_var(var, arg);
builder.ins().call(func_ref, &[]);
let val = builder.use_var(var);
builder.ins().return_(&[val]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) -> i32 system_v {
ss0 = explicit_slot 4, align = 4
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
stack_store v0, ss0
call fn0(), stack_map=[i32 @ ss0+0]
v1 = stack_load.i32 ss0
return v1
}
"#
);
}
#[test]
fn first_inst_defines_needs_stack_map() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params
.push(AbiParam::new(cranelift_codegen::ir::types::I32));
sig.returns
.push(AbiParam::new(cranelift_codegen::ir::types::I32));
sig.returns
.push(AbiParam::new(cranelift_codegen::ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let arg = builder.func.dfg.block_params(block0)[0];
builder.declare_value_needs_stack_map(arg);
let val = builder.ins().iconst(ir::types::I32, 42);
builder.declare_value_needs_stack_map(val);
builder.ins().call(func_ref, &[]);
builder.ins().return_(&[arg, val]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) -> i32, i32 system_v {
ss0 = explicit_slot 4, align = 4
ss1 = explicit_slot 4, align = 4
sig0 = () system_v
fn0 = colocated u0:0 sig0
block0(v0: i32):
stack_store v0, ss0
v1 = iconst.i32 42
stack_store v1, ss1 ; v1 = 42
call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0]
v2 = stack_load.i32 ss0
v3 = stack_load.i32 ss1
return v2, v3
}
"#
);
}
#[test]
fn needs_stack_map_and_loops_and_partially_live_values() {
let _ = env_logger::try_init();
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func =
Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig.clone());
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let foo_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 1,
index: 1,
});
let signature = builder.func.import_signature(sig);
let bar_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.ins().jump(block1, &[]);
builder.switch_to_block(block1);
let v0 = builder.func.dfg.block_params(block0)[0];
builder.declare_value_needs_stack_map(v0);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(bar_func_ref, &[v0]);
builder.ins().call(foo_func_ref, &[]);
builder.ins().jump(block1, &[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32) system_v {
ss0 = explicit_slot 4, align = 4
sig0 = () system_v
sig1 = (i32) system_v
fn0 = colocated u0:0 sig0
fn1 = colocated u1:1 sig1
block0(v0: i32):
stack_store v0, ss0
jump block1
block1:
call fn0(), stack_map=[i32 @ ss0+0]
v1 = stack_load.i32 ss0
call fn1(v1), stack_map=[i32 @ ss0+0]
call fn0(), stack_map=[i32 @ ss0+0]
jump block1
}
"#,
);
}
#[test]
fn needs_stack_map_and_irreducible_loops() {
let _ = env_logger::try_init();
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let foo_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 1,
index: 1,
});
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let signature = builder.func.import_signature(sig);
let bar_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
let block3 = builder.create_block();
let block4 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
let v1 = builder.func.dfg.block_params(block0)[1];
builder.declare_value_needs_stack_map(v1);
builder.ins().brif(v0, block1, &[], block2, &[]);
builder.switch_to_block(block1);
builder.ins().jump(block3, &[]);
builder.switch_to_block(block2);
builder.ins().jump(block4, &[]);
builder.switch_to_block(block3);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(bar_func_ref, &[v1]);
builder.ins().call(foo_func_ref, &[]);
builder.ins().jump(block2, &[]);
builder.switch_to_block(block4);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(bar_func_ref, &[v1]);
builder.ins().call(foo_func_ref, &[]);
builder.ins().jump(block1, &[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32, i32) system_v {
ss0 = explicit_slot 4, align = 4
sig0 = () system_v
sig1 = (i32) system_v
fn0 = colocated u0:0 sig0
fn1 = colocated u1:1 sig1
block0(v0: i32, v1: i32):
stack_store v1, ss0
brif v0, block1, block2
block1:
jump block3
block2:
jump block4
block3:
call fn0(), stack_map=[i32 @ ss0+0]
v3 = stack_load.i32 ss0
call fn1(v3), stack_map=[i32 @ ss0+0]
call fn0(), stack_map=[i32 @ ss0+0]
jump block2
block4:
call fn0(), stack_map=[i32 @ ss0+0]
v2 = stack_load.i32 ss0
call fn1(v2), stack_map=[i32 @ ss0+0]
call fn0(), stack_map=[i32 @ ss0+0]
jump block1
}
"#,
);
}
#[test]
fn needs_stack_map_and_back_edge_to_back_edge() {
let _ = env_logger::try_init();
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
sig.params.push(AbiParam::new(ir::types::I32));
sig.params.push(AbiParam::new(ir::types::I32));
sig.params.push(AbiParam::new(ir::types::I32));
let mut fn_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ir::UserFuncName::testcase("sample"), sig);
let mut builder = FunctionBuilder::new(&mut func, &mut fn_ctx);
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 0,
index: 0,
});
let signature = builder
.func
.import_signature(Signature::new(CallConv::SystemV));
let foo_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let name = builder
.func
.declare_imported_user_function(ir::UserExternalName {
namespace: 1,
index: 1,
});
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(ir::types::I32));
let signature = builder.func.import_signature(sig);
let bar_func_ref = builder.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(name),
signature,
colocated: true,
});
let block0 = builder.create_block();
let block1 = builder.create_block();
let block2 = builder.create_block();
let block3 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
let v0 = builder.func.dfg.block_params(block0)[0];
builder.declare_value_needs_stack_map(v0);
let v1 = builder.func.dfg.block_params(block0)[1];
builder.declare_value_needs_stack_map(v1);
let v2 = builder.func.dfg.block_params(block0)[2];
builder.declare_value_needs_stack_map(v2);
let v3 = builder.func.dfg.block_params(block0)[3];
builder.ins().jump(block1, &[v3]);
builder.switch_to_block(block1);
let v4 = builder.append_block_param(block1, ir::types::I32);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(bar_func_ref, &[v0]);
builder.ins().call(foo_func_ref, &[]);
builder.ins().jump(block2, &[]);
builder.switch_to_block(block2);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(bar_func_ref, &[v1]);
builder.ins().call(foo_func_ref, &[]);
let v5 = builder.ins().iadd_imm(v4, -1);
builder.ins().brif(v4, block1, &[v5], block3, &[]);
builder.switch_to_block(block3);
builder.ins().call(foo_func_ref, &[]);
builder.ins().call(bar_func_ref, &[v2]);
builder.ins().call(foo_func_ref, &[]);
builder.ins().jump(block2, &[]);
builder.seal_all_blocks();
builder.finalize();
assert_eq_output!(
func.display().to_string(),
r#"
function %sample(i32, i32, i32, i32) system_v {
ss0 = explicit_slot 4, align = 4
ss1 = explicit_slot 4, align = 4
ss2 = explicit_slot 4, align = 4
sig0 = () system_v
sig1 = (i32) system_v
fn0 = colocated u0:0 sig0
fn1 = colocated u1:1 sig1
block0(v0: i32, v1: i32, v2: i32, v3: i32):
stack_store v0, ss0
stack_store v1, ss1
stack_store v2, ss2
jump block1(v3)
block1(v4: i32):
call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
v8 = stack_load.i32 ss0
call fn1(v8), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
jump block2
block2:
call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
v7 = stack_load.i32 ss1
call fn1(v7), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
v5 = iadd_imm.i32 v4, -1
brif.i32 v4, block1(v5), block3
block3:
call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
v6 = stack_load.i32 ss2
call fn1(v6), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
call fn0(), stack_map=[i32 @ ss0+0, i32 @ ss1+0, i32 @ ss2+0]
jump block2
}
"#,
);
}
}