#![cfg(feature = "cuda-oxide-backend")]
use std::collections::HashMap;
use cranelift_codegen::ir::{Block, Function, Inst, InstructionData, Opcode, Value};
use crate::lowered_ir::{LoweredBlockId, LoweredOp, LoweredValueId};
pub fn lower_cf_inst(
inst: Inst,
func: &Function,
value_map: &HashMap<Value, LoweredValueId>,
block_map: &HashMap<Block, LoweredBlockId>,
) -> Option<LoweredOp> {
let data = &func.dfg.insts[inst];
match data {
InstructionData::Jump { destination, .. } => {
let target = *block_map.get(&destination.block(&func.dfg.value_lists))?;
let args =
map_block_call_args(destination.args_slice(&func.dfg.value_lists), value_map)?;
Some(LoweredOp::Br { target, args })
}
InstructionData::Brif { arg, blocks, .. } => {
let cond = *value_map.get(arg)?;
let then_call = &blocks[0];
let else_call = &blocks[1];
let then_target = *block_map.get(&then_call.block(&func.dfg.value_lists))?;
let else_target = *block_map.get(&else_call.block(&func.dfg.value_lists))?;
let then_args =
map_block_call_args(then_call.args_slice(&func.dfg.value_lists), value_map)?;
let else_args =
map_block_call_args(else_call.args_slice(&func.dfg.value_lists), value_map)?;
Some(LoweredOp::CondBr {
cond,
then_target,
then_args,
else_target,
else_args,
})
}
InstructionData::BranchTable { arg, table, .. } => {
let value = *value_map.get(arg)?;
let jt = &func.dfg.jump_tables[*table];
let default_call = jt.default_block();
let default_target = *block_map.get(&default_call.block(&func.dfg.value_lists))?;
let mut cases: Vec<(u32, LoweredBlockId)> = Vec::with_capacity(jt.as_slice().len());
for (idx, case_call) in jt.as_slice().iter().enumerate() {
if !case_call.args_slice(&func.dfg.value_lists).is_empty() {
return None;
}
let target = *block_map.get(&case_call.block(&func.dfg.value_lists))?;
cases.push((idx as u32, target));
}
if !default_call.args_slice(&func.dfg.value_lists).is_empty() {
return None;
}
Some(LoweredOp::Switch {
value,
default_target,
cases,
})
}
InstructionData::MultiAry { opcode, args } if *opcode == Opcode::Return => {
let cranelift_args = args.as_slice(&func.dfg.value_lists);
let mut values = Vec::with_capacity(cranelift_args.len());
for v in cranelift_args {
values.push(*value_map.get(v)?);
}
Some(LoweredOp::Return { values })
}
_ => None,
}
}
fn map_block_call_args(
cranelift_args: &[Value],
value_map: &HashMap<Value, LoweredValueId>,
) -> Option<Vec<LoweredValueId>> {
let mut out = Vec::with_capacity(cranelift_args.len());
for v in cranelift_args {
out.push(*value_map.get(v)?);
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift_codegen::ir::{
types, AbiParam, BlockCall, Function, InstructionData, JumpTableData, Opcode, Signature,
UserFuncName, ValueList,
};
use cranelift_codegen::isa::CallConv;
fn fresh_function() -> Function {
let sig = Signature::new(CallConv::SystemV);
Function::with_name_signature(UserFuncName::default(), sig)
}
fn new_block(func: &mut Function) -> Block {
func.dfg.make_block()
}
fn fresh_i32_value(func: &mut Function, block: Block) -> Value {
func.dfg.append_block_param(block, types::I32)
}
fn new_block_call(func: &mut Function, block: Block, args: &[Value]) -> BlockCall {
BlockCall::new(block, args, &mut func.dfg.value_lists)
}
#[test]
fn lowers_jump_no_args() {
let mut func = fresh_function();
let entry = new_block(&mut func);
let target = new_block(&mut func);
let bc = new_block_call(&mut func, target, &[]);
let inst = func.dfg.make_inst(InstructionData::Jump {
opcode: Opcode::Jump,
destination: bc,
});
let value_map = HashMap::new();
let mut block_map = HashMap::new();
block_map.insert(entry, 0u32);
block_map.insert(target, 1u32);
let op = lower_cf_inst(inst, &func, &value_map, &block_map).expect("jump must lower");
match op {
LoweredOp::Br { target, args } => {
assert_eq!(target, 1);
assert!(args.is_empty());
}
other => panic!("expected LoweredOp::Br, got {other:?}"),
}
}
#[test]
fn lowers_jump_with_block_args() {
let mut func = fresh_function();
let entry = new_block(&mut func);
let target = new_block(&mut func);
let v0 = fresh_i32_value(&mut func, entry);
let v1 = fresh_i32_value(&mut func, entry);
let bc = new_block_call(&mut func, target, &[v0, v1]);
let inst = func.dfg.make_inst(InstructionData::Jump {
opcode: Opcode::Jump,
destination: bc,
});
let mut value_map = HashMap::new();
value_map.insert(v0, 10u32);
value_map.insert(v1, 20u32);
let mut block_map = HashMap::new();
block_map.insert(entry, 0u32);
block_map.insert(target, 7u32);
let op =
lower_cf_inst(inst, &func, &value_map, &block_map).expect("jump-with-args must lower");
match op {
LoweredOp::Br { target, args } => {
assert_eq!(target, 7);
assert_eq!(args, vec![10, 20]);
}
other => panic!("expected LoweredOp::Br, got {other:?}"),
}
}
#[test]
fn lowers_brif() {
let mut func = fresh_function();
let entry = new_block(&mut func);
let then_blk = new_block(&mut func);
let else_blk = new_block(&mut func);
let cond = fresh_i32_value(&mut func, entry);
let then_arg = fresh_i32_value(&mut func, entry);
let else_arg = fresh_i32_value(&mut func, entry);
let then_call = new_block_call(&mut func, then_blk, &[then_arg]);
let else_call = new_block_call(&mut func, else_blk, &[else_arg]);
let inst = func.dfg.make_inst(InstructionData::Brif {
opcode: Opcode::Brif,
arg: cond,
blocks: [then_call, else_call],
});
let mut value_map = HashMap::new();
value_map.insert(cond, 100u32);
value_map.insert(then_arg, 101u32);
value_map.insert(else_arg, 102u32);
let mut block_map = HashMap::new();
block_map.insert(entry, 0u32);
block_map.insert(then_blk, 1u32);
block_map.insert(else_blk, 2u32);
let op = lower_cf_inst(inst, &func, &value_map, &block_map).expect("brif must lower");
match op {
LoweredOp::CondBr {
cond,
then_target,
then_args,
else_target,
else_args,
} => {
assert_eq!(cond, 100);
assert_eq!(then_target, 1);
assert_eq!(then_args, vec![101]);
assert_eq!(else_target, 2);
assert_eq!(else_args, vec![102]);
}
other => panic!("expected LoweredOp::CondBr, got {other:?}"),
}
}
#[test]
fn lowers_br_table_with_cases_and_default() {
let mut func = fresh_function();
let entry = new_block(&mut func);
let default_blk = new_block(&mut func);
let case0 = new_block(&mut func);
let case1 = new_block(&mut func);
let case2 = new_block(&mut func);
let switched = fresh_i32_value(&mut func, entry);
let default_call = new_block_call(&mut func, default_blk, &[]);
let c0 = new_block_call(&mut func, case0, &[]);
let c1 = new_block_call(&mut func, case1, &[]);
let c2 = new_block_call(&mut func, case2, &[]);
let jt = func.create_jump_table(JumpTableData::new(default_call, &[c0, c1, c2]));
let inst = func.dfg.make_inst(InstructionData::BranchTable {
opcode: Opcode::BrTable,
arg: switched,
table: jt,
});
let mut value_map = HashMap::new();
value_map.insert(switched, 42u32);
let mut block_map = HashMap::new();
block_map.insert(entry, 0u32);
block_map.insert(default_blk, 100u32);
block_map.insert(case0, 200u32);
block_map.insert(case1, 201u32);
block_map.insert(case2, 202u32);
let op = lower_cf_inst(inst, &func, &value_map, &block_map).expect("br_table must lower");
match op {
LoweredOp::Switch {
value,
default_target,
cases,
} => {
assert_eq!(value, 42);
assert_eq!(default_target, 100);
assert_eq!(cases, vec![(0u32, 200u32), (1u32, 201u32), (2u32, 202u32)]);
}
other => panic!("expected LoweredOp::Switch, got {other:?}"),
}
}
#[test]
fn lowers_return_no_values() {
let mut func = fresh_function();
let _entry = new_block(&mut func);
let args = ValueList::default();
let inst = func.dfg.make_inst(InstructionData::MultiAry {
opcode: Opcode::Return,
args,
});
let value_map = HashMap::new();
let block_map = HashMap::new();
let op = lower_cf_inst(inst, &func, &value_map, &block_map).expect("return must lower");
match op {
LoweredOp::Return { values } => assert!(values.is_empty()),
other => panic!("expected LoweredOp::Return, got {other:?}"),
}
}
#[test]
fn lowers_return_with_values() {
let mut func = fresh_function();
let entry = new_block(&mut func);
func.signature.returns.push(AbiParam::new(types::I32));
func.signature.returns.push(AbiParam::new(types::I32));
let v0 = fresh_i32_value(&mut func, entry);
let v1 = fresh_i32_value(&mut func, entry);
let mut args = ValueList::default();
args.push(v0, &mut func.dfg.value_lists);
args.push(v1, &mut func.dfg.value_lists);
let inst = func.dfg.make_inst(InstructionData::MultiAry {
opcode: Opcode::Return,
args,
});
let mut value_map = HashMap::new();
value_map.insert(v0, 1000u32);
value_map.insert(v1, 2000u32);
let block_map = HashMap::new();
let op = lower_cf_inst(inst, &func, &value_map, &block_map).expect("return must lower");
match op {
LoweredOp::Return { values } => assert_eq!(values, vec![1000, 2000]),
other => panic!("expected LoweredOp::Return, got {other:?}"),
}
}
#[test]
fn non_cf_op_returns_none() {
let mut func = fresh_function();
let entry = new_block(&mut func);
let a = fresh_i32_value(&mut func, entry);
let b = fresh_i32_value(&mut func, entry);
let inst = func.dfg.make_inst(InstructionData::Binary {
opcode: Opcode::Iadd,
args: [a, b],
});
assert!(lower_cf_inst(inst, &func, &HashMap::new(), &HashMap::new()).is_none());
}
#[test]
fn return_call_is_not_lowered_here() {
let mut func = fresh_function();
let _entry = new_block(&mut func);
let args = ValueList::default();
let inst = func.dfg.make_inst(InstructionData::MultiAry {
opcode: Opcode::ReturnCall,
args,
});
assert!(lower_cf_inst(inst, &func, &HashMap::new(), &HashMap::new()).is_none());
}
#[test]
fn missing_block_returns_none() {
let mut func = fresh_function();
let entry = new_block(&mut func);
let target = new_block(&mut func);
let bc = new_block_call(&mut func, target, &[]);
let inst = func.dfg.make_inst(InstructionData::Jump {
opcode: Opcode::Jump,
destination: bc,
});
let value_map = HashMap::new();
let mut block_map = HashMap::new();
block_map.insert(entry, 0u32);
assert!(lower_cf_inst(inst, &func, &value_map, &block_map).is_none());
}
#[test]
fn missing_value_returns_none() {
let mut func = fresh_function();
let entry = new_block(&mut func);
let then_blk = new_block(&mut func);
let else_blk = new_block(&mut func);
let cond = fresh_i32_value(&mut func, entry);
let then_call = new_block_call(&mut func, then_blk, &[]);
let else_call = new_block_call(&mut func, else_blk, &[]);
let inst = func.dfg.make_inst(InstructionData::Brif {
opcode: Opcode::Brif,
arg: cond,
blocks: [then_call, else_call],
});
let value_map = HashMap::new();
let mut block_map = HashMap::new();
block_map.insert(entry, 0u32);
block_map.insert(then_blk, 1u32);
block_map.insert(else_blk, 2u32);
assert!(lower_cf_inst(inst, &func, &value_map, &block_map).is_none());
}
}