#![cfg(feature = "cuda-oxide-backend")]
use std::collections::HashMap;
use cranelift_codegen::ir::{self, Function, Inst, Opcode, Value};
use crate::lowered_ir::{LoweredOp, LoweredType, LoweredValueId};
pub fn lower_arith_inst(
inst: Inst,
func: &Function,
value_map: &mut HashMap<Value, LoweredValueId>,
next_id: &mut LoweredValueId,
) -> Option<LoweredOp> {
let opcode = func.dfg.insts[inst].opcode();
let kind = match opcode {
Opcode::Iadd => ArithKind::AddI,
Opcode::Isub => ArithKind::SubI,
Opcode::Imul => ArithKind::MulI,
Opcode::Sdiv => ArithKind::DivS,
Opcode::Udiv => ArithKind::DivU,
Opcode::Srem => ArithKind::RemS,
Opcode::Urem => ArithKind::RemU,
_ => return None,
};
let args = func.dfg.inst_args(inst);
if args.len() != 2 {
return None;
}
let results = func.dfg.inst_results(inst);
if results.len() != 1 {
return None;
}
let lhs_value = args[0];
let rhs_value = args[1];
let result_value = results[0];
let cl_ty = func.dfg.value_type(result_value);
let ty = cranelift_int_type_to_lowered(cl_ty)?;
let lhs = *value_map.get(&lhs_value)?;
let rhs = *value_map.get(&rhs_value)?;
let result = *next_id;
*next_id = next_id.checked_add(1)?;
value_map.insert(result_value, result);
Some(match kind {
ArithKind::AddI => LoweredOp::AddI {
ty,
lhs,
rhs,
result,
},
ArithKind::SubI => LoweredOp::SubI {
ty,
lhs,
rhs,
result,
},
ArithKind::MulI => LoweredOp::MulI {
ty,
lhs,
rhs,
result,
},
ArithKind::DivS => LoweredOp::DivS {
ty,
lhs,
rhs,
result,
},
ArithKind::DivU => LoweredOp::DivU {
ty,
lhs,
rhs,
result,
},
ArithKind::RemS => LoweredOp::RemS {
ty,
lhs,
rhs,
result,
},
ArithKind::RemU => LoweredOp::RemU {
ty,
lhs,
rhs,
result,
},
})
}
enum ArithKind {
AddI,
SubI,
MulI,
DivS,
DivU,
RemS,
RemU,
}
fn cranelift_int_type_to_lowered(ty: ir::Type) -> Option<LoweredType> {
if ty == ir::types::I8 {
Some(LoweredType::I8)
} else if ty == ir::types::I16 {
Some(LoweredType::I16)
} else if ty == ir::types::I32 {
Some(LoweredType::I32)
} else if ty == ir::types::I64 {
Some(LoweredType::I64)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::ir::types::{I128, I16, I32, I64, I8};
use cranelift_codegen::ir::{Function, InstBuilder};
fn fixture_with_binary_op(opcode: Opcode, ty: ir::Type) -> (Function, Inst, Value, Value) {
let mut func = Function::new();
let block = func.dfg.make_block();
let lhs = func.dfg.append_block_param(block, ty);
let rhs = func.dfg.append_block_param(block, ty);
let mut pos = FuncCursor::new(&mut func);
pos.insert_block(block);
let inst = match opcode {
Opcode::Iadd => pos.ins().iadd(lhs, rhs),
Opcode::Isub => pos.ins().isub(lhs, rhs),
Opcode::Imul => pos.ins().imul(lhs, rhs),
Opcode::Sdiv => pos.ins().sdiv(lhs, rhs),
Opcode::Udiv => pos.ins().udiv(lhs, rhs),
Opcode::Srem => pos.ins().srem(lhs, rhs),
Opcode::Urem => pos.ins().urem(lhs, rhs),
other => panic!("fixture_with_binary_op: opcode {other:?} not in arith family"),
};
let inst = pos.func.dfg.value_def(inst).unwrap_inst();
(func, inst, lhs, rhs)
}
fn seed_map(lhs: Value, rhs: Value) -> (HashMap<Value, LoweredValueId>, LoweredValueId) {
let mut map = HashMap::new();
map.insert(lhs, 0);
map.insert(rhs, 1);
(map, 2)
}
#[test]
fn lowers_iadd_i32() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Iadd, I32);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let op = lower_arith_inst(inst, &func, &mut map, &mut next_id).expect("iadd lowered");
match op {
LoweredOp::AddI {
ty,
lhs,
rhs,
result,
} => {
assert_eq!(ty, LoweredType::I32);
assert_eq!(lhs, 0);
assert_eq!(rhs, 1);
assert_eq!(result, 2);
}
other => panic!("expected AddI, got {other:?}"),
}
assert_eq!(next_id, 3, "result-id allocator advanced once");
let result_value = func.dfg.inst_results(inst)[0];
assert_eq!(map.get(&result_value), Some(&2));
}
#[test]
fn lowers_isub_i64() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Isub, I64);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let op = lower_arith_inst(inst, &func, &mut map, &mut next_id).expect("isub lowered");
assert!(matches!(
op,
LoweredOp::SubI {
ty: LoweredType::I64,
lhs: 0,
rhs: 1,
result: 2
}
));
}
#[test]
fn lowers_imul_i16() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Imul, I16);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let op = lower_arith_inst(inst, &func, &mut map, &mut next_id).expect("imul lowered");
assert!(matches!(
op,
LoweredOp::MulI {
ty: LoweredType::I16,
lhs: 0,
rhs: 1,
result: 2
}
));
}
#[test]
fn lowers_sdiv_i32() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Sdiv, I32);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let op = lower_arith_inst(inst, &func, &mut map, &mut next_id).expect("sdiv lowered");
assert!(matches!(
op,
LoweredOp::DivS {
ty: LoweredType::I32,
lhs: 0,
rhs: 1,
result: 2
}
));
}
#[test]
fn lowers_udiv_i64() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Udiv, I64);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let op = lower_arith_inst(inst, &func, &mut map, &mut next_id).expect("udiv lowered");
assert!(matches!(
op,
LoweredOp::DivU {
ty: LoweredType::I64,
lhs: 0,
rhs: 1,
result: 2
}
));
}
#[test]
fn lowers_srem_i8() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Srem, I8);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let op = lower_arith_inst(inst, &func, &mut map, &mut next_id).expect("srem lowered");
assert!(matches!(
op,
LoweredOp::RemS {
ty: LoweredType::I8,
lhs: 0,
rhs: 1,
result: 2
}
));
}
#[test]
fn lowers_urem_i32() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Urem, I32);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let op = lower_arith_inst(inst, &func, &mut map, &mut next_id).expect("urem lowered");
assert!(matches!(
op,
LoweredOp::RemU {
ty: LoweredType::I32,
lhs: 0,
rhs: 1,
result: 2
}
));
}
#[test]
fn returns_none_for_non_arith_opcode() {
let mut func = Function::new();
let block = func.dfg.make_block();
let mut pos = FuncCursor::new(&mut func);
pos.insert_block(block);
let v = pos.ins().iconst(I32, 7);
let iconst_inst = pos.func.dfg.value_def(v).unwrap_inst();
let mut map = HashMap::new();
let mut next_id: LoweredValueId = 0;
assert!(lower_arith_inst(iconst_inst, &func, &mut map, &mut next_id).is_none());
assert_eq!(next_id, 0, "next_id untouched on miss");
assert!(map.is_empty(), "value_map untouched on miss");
}
#[test]
fn returns_none_for_i128() {
let (func, inst, lhs, rhs) = fixture_with_binary_op(Opcode::Iadd, I128);
let (mut map, mut next_id) = seed_map(lhs, rhs);
let before_next = next_id;
let before_len = map.len();
assert!(lower_arith_inst(inst, &func, &mut map, &mut next_id).is_none());
assert_eq!(
next_id, before_next,
"next_id untouched on unsupported type"
);
assert_eq!(
map.len(),
before_len,
"value_map untouched on unsupported type"
);
}
#[test]
fn returns_none_when_operand_missing_from_map() {
let (func, inst, _lhs, rhs) = fixture_with_binary_op(Opcode::Iadd, I32);
let mut map = HashMap::new();
map.insert(rhs, 9);
let mut next_id: LoweredValueId = 10;
assert!(lower_arith_inst(inst, &func, &mut map, &mut next_id).is_none());
assert_eq!(next_id, 10);
assert_eq!(map.len(), 1);
}
#[test]
fn allocates_sequential_ids_across_calls() {
let (func_a, inst_a, lhs_a, rhs_a) = fixture_with_binary_op(Opcode::Iadd, I32);
let (func_b, inst_b, lhs_b, rhs_b) = fixture_with_binary_op(Opcode::Imul, I32);
let mut map_a = HashMap::new();
map_a.insert(lhs_a, 0);
map_a.insert(rhs_a, 1);
let mut next_id: LoweredValueId = 2;
let op_a = lower_arith_inst(inst_a, &func_a, &mut map_a, &mut next_id).unwrap();
assert_eq!(op_a.result(), Some(2));
assert_eq!(next_id, 3);
let mut map_b = HashMap::new();
map_b.insert(lhs_b, 100);
map_b.insert(rhs_b, 101);
let op_b = lower_arith_inst(inst_b, &func_b, &mut map_b, &mut next_id).unwrap();
assert_eq!(op_b.result(), Some(3));
assert_eq!(next_id, 4);
}
}