#![cfg(feature = "cuda-oxide-backend")]
use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::ir::immediates::Offset32;
use cranelift_codegen::ir::{
AbiParam, Function, Inst, InstBuilder, MemFlags, Opcode, Signature, Type, UserFuncName,
};
use cranelift_codegen::isa::CallConv;
const PTR_TY: Type = cranelift_codegen::ir::types::I64;
pub fn empty_function(name: &str, params: &[Type], returns: &[Type]) -> Function {
let mut sig = Signature::new(CallConv::SystemV);
for p in params {
sig.params.push(AbiParam::new(*p));
}
for r in returns {
sig.returns.push(AbiParam::new(*r));
}
let mut func = Function::with_name_signature(UserFuncName::testcase(name.as_bytes()), sig);
let block = func.dfg.make_block();
let param_types: Vec<Type> = func.signature.params.iter().map(|p| p.value_type).collect();
for pty in param_types {
func.dfg.append_block_param(block, pty);
}
func.layout.append_block(block);
let mut cursor = FuncCursor::new(&mut func).at_bottom(block);
cursor.ins().return_(&[]);
func
}
pub fn function_with_binary_op(opcode: Opcode, lane_ty: Type) -> (Function, Inst) {
let mut func = empty_function_no_return("binop", &[lane_ty, lane_ty], &[lane_ty]);
let block = func.layout.entry_block().expect("entry block");
let params: Vec<_> = func.dfg.block_params(block).to_vec();
let p0 = params[0];
let p1 = params[1];
let mut cursor = FuncCursor::new(&mut func).at_bottom(block);
let (inst, dfg) = cursor.ins().Binary(opcode, lane_ty, p0, p1);
let result = dfg.first_result(inst);
cursor.ins().return_(&[result]);
(func, inst)
}
pub fn function_with_unary_op(opcode: Opcode, lane_ty: Type) -> (Function, Inst) {
let mut func = empty_function_no_return("unop", &[lane_ty], &[lane_ty]);
let block = func.layout.entry_block().expect("entry block");
let params: Vec<_> = func.dfg.block_params(block).to_vec();
let p0 = params[0];
let mut cursor = FuncCursor::new(&mut func).at_bottom(block);
let (inst, dfg) = cursor.ins().Unary(opcode, lane_ty, p0);
let result = dfg.first_result(inst);
cursor.ins().return_(&[result]);
(func, inst)
}
pub fn function_with_load(ty: Type, offset: i32) -> (Function, Inst) {
let mut func = empty_function_no_return("load_fn", &[PTR_TY], &[ty]);
let block = func.layout.entry_block().expect("entry block");
let params: Vec<_> = func.dfg.block_params(block).to_vec();
let p0 = params[0];
let mut cursor = FuncCursor::new(&mut func).at_bottom(block);
let (inst, dfg) = cursor.ins().Load(
Opcode::Load,
ty,
MemFlags::trusted(),
Offset32::new(offset),
p0,
);
let result = dfg.first_result(inst);
cursor.ins().return_(&[result]);
(func, inst)
}
pub fn function_with_store(ty: Type, offset: i32) -> (Function, Inst) {
let mut func = empty_function_no_return("store_fn", &[ty, PTR_TY], &[]);
let block = func.layout.entry_block().expect("entry block");
let params: Vec<_> = func.dfg.block_params(block).to_vec();
let p0 = params[0]; let p1 = params[1];
let mut cursor = FuncCursor::new(&mut func).at_bottom(block);
let (inst, _dfg) = cursor.ins().Store(
Opcode::Store,
ty,
MemFlags::trusted(),
Offset32::new(offset),
p0,
p1,
);
cursor.ins().return_(&[]);
(func, inst)
}
fn empty_function_no_return(name: &str, params: &[Type], returns: &[Type]) -> Function {
let mut sig = Signature::new(CallConv::SystemV);
for p in params {
sig.params.push(AbiParam::new(*p));
}
for r in returns {
sig.returns.push(AbiParam::new(*r));
}
let mut func = Function::with_name_signature(UserFuncName::testcase(name.as_bytes()), sig);
let block = func.dfg.make_block();
let param_types: Vec<Type> = func.signature.params.iter().map(|p| p.value_type).collect();
for pty in param_types {
func.dfg.append_block_param(block, pty);
}
func.layout.append_block(block);
func
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift_codegen::ir::types::{F32, I32, I64};
#[test]
fn empty_function_shape() {
let func = empty_function("noop", &[I32, F32], &[]);
assert_eq!(func.layout.blocks().count(), 1);
let block = func.layout.entry_block().expect("entry block");
assert_eq!(func.dfg.num_block_params(block), 2);
let ptys: Vec<Type> = func
.dfg
.block_params(block)
.iter()
.map(|v| func.dfg.value_type(*v))
.collect();
assert_eq!(ptys, vec![I32, F32]);
let last = func.layout.last_inst(block).expect("terminator");
assert_eq!(func.dfg.insts[last].opcode(), Opcode::Return);
}
#[test]
fn empty_function_signature_records_returns() {
let func = empty_function("with_ret", &[I32], &[I64]);
assert_eq!(func.signature.params.len(), 1);
assert_eq!(func.signature.returns.len(), 1);
assert_eq!(func.signature.params[0].value_type, I32);
assert_eq!(func.signature.returns[0].value_type, I64);
}
#[test]
fn function_with_binary_op_iadd_shape() {
let (func, inst) = function_with_binary_op(Opcode::Iadd, I32);
assert_eq!(func.layout.blocks().count(), 1);
let block = func.layout.entry_block().unwrap();
assert_eq!(func.dfg.num_block_params(block), 2);
assert_eq!(func.dfg.insts[inst].opcode(), Opcode::Iadd);
let args = func.dfg.inst_args(inst);
let params = func.dfg.block_params(block);
assert_eq!(args[0], params[0]);
assert_eq!(args[1], params[1]);
let last = func.layout.last_inst(block).unwrap();
assert_eq!(func.dfg.insts[last].opcode(), Opcode::Return);
}
#[test]
fn function_with_binary_op_fadd_shape() {
let (func, inst) = function_with_binary_op(Opcode::Fadd, F32);
assert_eq!(func.dfg.insts[inst].opcode(), Opcode::Fadd);
let block = func.layout.entry_block().unwrap();
let ptys: Vec<Type> = func
.dfg
.block_params(block)
.iter()
.map(|v| func.dfg.value_type(*v))
.collect();
assert_eq!(ptys, vec![F32, F32]);
}
#[test]
fn function_with_unary_op_fneg_shape() {
let (func, inst) = function_with_unary_op(Opcode::Fneg, F32);
assert_eq!(func.layout.blocks().count(), 1);
let block = func.layout.entry_block().unwrap();
assert_eq!(func.dfg.num_block_params(block), 1);
assert_eq!(func.dfg.insts[inst].opcode(), Opcode::Fneg);
let args = func.dfg.inst_args(inst);
assert_eq!(args[0], func.dfg.block_params(block)[0]);
let last = func.layout.last_inst(block).unwrap();
assert_eq!(func.dfg.insts[last].opcode(), Opcode::Return);
}
#[test]
fn function_with_load_shape() {
let (func, inst) = function_with_load(I32, 16);
assert_eq!(func.layout.blocks().count(), 1);
let block = func.layout.entry_block().unwrap();
assert_eq!(func.dfg.num_block_params(block), 1);
let ptys: Vec<Type> = func
.dfg
.block_params(block)
.iter()
.map(|v| func.dfg.value_type(*v))
.collect();
assert_eq!(ptys, vec![PTR_TY]);
assert_eq!(func.dfg.insts[inst].opcode(), Opcode::Load);
let offset = func.dfg.insts[inst]
.load_store_offset()
.expect("load instruction missing offset");
assert_eq!(offset, 16);
let last = func.layout.last_inst(block).unwrap();
assert_eq!(func.dfg.insts[last].opcode(), Opcode::Return);
}
#[test]
fn function_with_store_shape() {
let (func, inst) = function_with_store(I32, 32);
assert_eq!(func.layout.blocks().count(), 1);
let block = func.layout.entry_block().unwrap();
assert_eq!(func.dfg.num_block_params(block), 2);
let ptys: Vec<Type> = func
.dfg
.block_params(block)
.iter()
.map(|v| func.dfg.value_type(*v))
.collect();
assert_eq!(ptys, vec![I32, PTR_TY]);
assert_eq!(func.dfg.insts[inst].opcode(), Opcode::Store);
let offset = func.dfg.insts[inst]
.load_store_offset()
.expect("store instruction missing offset");
assert_eq!(offset, 32);
let last = func.layout.last_inst(block).unwrap();
assert_eq!(func.dfg.insts[last].opcode(), Opcode::Return);
}
}