#![cfg(feature = "cuda-oxide-backend")]
use std::collections::HashMap;
use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir as cl;
use crate::lower_arith::lower_arith_inst;
use crate::lower_cf::lower_cf_inst;
use crate::lower_conv::lower_conv_inst;
use crate::lower_float::lower_float_inst;
use crate::lower_memory::{lower_memory_inst, MemLowerContext, MemLowerError};
use crate::lower_signature::lower_signature;
use crate::lower_vector::lower_vector_inst;
use crate::lowered_ir::{LoweredBlock, LoweredFunction, LoweredOp, LoweredValueId};
use crate::lowering_builder::LoweringBuilder;
use crate::lowering_errors::{InstLocation, LoweringError};
pub fn lower_function(func: &cl::Function) -> Result<LoweredFunction, LoweringError> {
let signature =
lower_signature(&func.signature).map_err(|err| LoweringError::UnsupportedType {
ty: err.to_string(),
location: InstLocation {
block: 0,
inst_index: 0,
},
})?;
if let Some(rejection) = crate::reject_list::check_function(func) {
return Err(LoweringError::Rejected {
reason: format!("{:?}", rejection.reason),
location: InstLocation::new(rejection.block, 0),
});
}
let mut builder = LoweringBuilder::new();
for block in func.layout.blocks() {
let _ = builder.get_or_alloc_block(block);
}
let mut block_params: HashMap<cl::Block, Vec<(LoweredValueId, cl::Value)>> = HashMap::new();
for block in func.layout.blocks() {
let mut params = Vec::new();
for ¶m in func.dfg.block_params(block) {
let id = builder.get_or_alloc_value(param);
params.push((id, param));
}
block_params.insert(block, params);
}
let mut lowered_blocks: Vec<LoweredBlock> = Vec::new();
for cl_block in func.layout.blocks() {
let block_id = builder
.lookup_block(cl_block)
.expect("block pre-allocated above");
let mut lblock = LoweredBlock::new(block_id);
let params = block_params
.get(&cl_block)
.expect("block params seeded above");
for (id, cl_value) in params {
let cl_ty = func.dfg.value_type(*cl_value);
let lty =
crate::lower_signature::cranelift_type_to_lowered(cl_ty).ok_or_else(|| {
LoweringError::UnsupportedType {
ty: cl_ty.to_string(),
location: InstLocation::new(cl_block, 0),
}
})?;
lblock.params.push((*id, lty));
}
for (inst_index, inst) in func.layout.block_insts(cl_block).enumerate() {
let inst_index = inst_index as u32;
let location = InstLocation::new(cl_block, inst_index);
dispatch_inst(inst, func, &mut builder, &mut lblock, location)?;
}
lowered_blocks.push(lblock);
}
let entry_cl_block = func
.layout
.entry_block()
.ok_or_else(|| LoweringError::MalformedTerminator { block_id: 0 })?;
let entry = builder
.lookup_block(entry_cl_block)
.expect("entry block pre-allocated above");
Ok(LoweredFunction {
name: function_name(func),
signature,
blocks: lowered_blocks,
entry,
})
}
fn function_name(func: &cl::Function) -> String {
match &func.name {
cl::UserFuncName::Testcase(t) => {
let display = t.to_string();
display.strip_prefix('%').unwrap_or(&display).to_string()
}
cl::UserFuncName::User(u) => format!("user{}_{}", u.namespace, u.index),
}
}
fn dispatch_inst(
inst: cl::Inst,
func: &cl::Function,
builder: &mut LoweringBuilder,
lblock: &mut LoweredBlock,
location: InstLocation,
) -> Result<(), LoweringError> {
if let Some(op) = arith_attempt(inst, func, builder) {
lblock.ops.push(op);
return Ok(());
}
if let Some(op) = float_attempt(inst, func, builder) {
bind_result_if_any(func, inst, &op, builder);
lblock.ops.push(op);
return Ok(());
}
match memory_attempt(inst, func, builder, location.clone())? {
Some(ops) => {
let cl_results = func.dfg.inst_results(inst);
if let Some(cl_result_v) = cl_results.first() {
if let Some(lid) = ops.iter().rev().find_map(LoweredOp::result) {
builder.bind_value(*cl_result_v, lid);
}
}
for op in ops {
lblock.ops.push(op);
}
return Ok(());
}
None => {}
}
if let Some(op) = cf_attempt(inst, func, builder) {
lblock.ops.push(op);
return Ok(());
}
if let Some(op) = vector_attempt(inst, func, builder) {
bind_result_if_any(func, inst, &op, builder);
lblock.ops.push(op);
return Ok(());
}
if let Some(op) = conv_attempt(inst, func, builder) {
bind_result_if_any(func, inst, &op, builder);
lblock.ops.push(op);
return Ok(());
}
let opcode = func.dfg.insts[inst].opcode();
Err(LoweringError::UnsupportedOpcode {
op: format!("{opcode:?}"),
location,
})
}
fn bind_result_if_any(
func: &cl::Function,
inst: cl::Inst,
op: &LoweredOp,
builder: &mut LoweringBuilder,
) {
if let Some(lid) = op.result() {
if let Some(&cl_result_v) = func.dfg.inst_results(inst).first() {
builder.bind_value(cl_result_v, lid);
}
}
}
fn arith_attempt(
inst: cl::Inst,
func: &cl::Function,
builder: &mut LoweringBuilder,
) -> Option<LoweredOp> {
let mut next_id_local: LoweredValueId = *builder.next_value_id_mut();
let result = lower_arith_inst(inst, func, builder.value_map_mut(), &mut next_id_local);
*builder.next_value_id_mut() = next_id_local;
result
}
fn float_attempt(
inst: cl::Inst,
func: &cl::Function,
builder: &mut LoweringBuilder,
) -> Option<LoweredOp> {
let mut next_id_local: LoweredValueId = *builder.next_value_id_mut();
let result = lower_float_inst(inst, func, builder.value_map(), &mut next_id_local);
*builder.next_value_id_mut() = next_id_local;
result
}
fn memory_attempt(
inst: cl::Inst,
func: &cl::Function,
builder: &mut LoweringBuilder,
location: InstLocation,
) -> Result<Option<Vec<LoweredOp>>, LoweringError> {
let result = {
let (value_map, stack_slot_map, next_value_id) = builder.split_borrow_for_memory();
let mut ctx = MemLowerContext {
linear_memory_base: 0,
value_map,
stack_slot_map,
next_value_id,
};
lower_memory_inst(inst, func, &mut ctx)
};
match result {
Ok(some_ops) => Ok(some_ops),
Err(MemLowerError::UnmappedValue(v)) => {
Err(LoweringError::UndefinedValue { value: v, location })
}
Err(MemLowerError::UnsupportedType(ty)) => {
Err(LoweringError::UnsupportedType { ty, location })
}
}
}
fn cf_attempt(
inst: cl::Inst,
func: &cl::Function,
builder: &mut LoweringBuilder,
) -> Option<LoweredOp> {
lower_cf_inst(inst, func, builder.value_map(), builder.block_map())
}
fn vector_attempt(
inst: cl::Inst,
func: &cl::Function,
builder: &mut LoweringBuilder,
) -> Option<LoweredOp> {
let mut next_id_local: LoweredValueId = *builder.next_value_id_mut();
let result = lower_vector_inst(inst, func, builder.value_map(), &mut next_id_local);
*builder.next_value_id_mut() = next_id_local;
result
}
fn conv_attempt(
inst: cl::Inst,
func: &cl::Function,
builder: &mut LoweringBuilder,
) -> Option<LoweredOp> {
let mut next_id_local: LoweredValueId = *builder.next_value_id_mut();
let result = lower_conv_inst(inst, func, builder.value_map(), &mut next_id_local);
*builder.next_value_id_mut() = next_id_local;
result
}
#[allow(dead_code)]
fn _entity_ref_witness() -> &'static str {
let _: fn(cl::Block) -> usize = |b| b.index();
"ok"
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lowered_ir::{LoweredOp, LoweredType};
use crate::lowering_test_support::function_with_binary_op;
use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::ir::{
types, AbiParam, BlockCall, Function, InstBuilder, InstructionData, Opcode, Signature,
UserFuncName,
};
use cranelift_codegen::isa::CallConv;
#[test]
fn lowers_single_block_iadd_return() {
let (func, _inst) = function_with_binary_op(Opcode::Iadd, types::I32);
let lowered = lower_function(&func).expect("must lower");
assert_eq!(lowered.blocks.len(), 1, "one block expected");
assert_eq!(
lowered.signature.params,
vec![LoweredType::I32, LoweredType::I32]
);
assert_eq!(lowered.signature.returns, vec![LoweredType::I32]);
let block = &lowered.blocks[0];
assert_eq!(block.ops.len(), 2, "AddI + Return");
assert!(matches!(block.ops[0], LoweredOp::AddI { .. }));
assert!(matches!(block.ops[1], LoweredOp::Return { .. }));
assert!(block.is_well_formed());
assert!(lowered.is_well_formed());
}
#[test]
fn lowers_single_block_fadd_return() {
let (func, _inst) = function_with_binary_op(Opcode::Fadd, types::F32);
let lowered = lower_function(&func).expect("must lower");
assert_eq!(lowered.blocks.len(), 1);
assert_eq!(
lowered.signature.params,
vec![LoweredType::F32, LoweredType::F32]
);
assert_eq!(lowered.signature.returns, vec![LoweredType::F32]);
let block = &lowered.blocks[0];
assert_eq!(block.ops.len(), 2);
match &block.ops[0] {
LoweredOp::AddF { ty, .. } => assert_eq!(*ty, LoweredType::F32),
other => panic!("expected AddF, got {other:?}"),
}
assert!(matches!(block.ops[1], LoweredOp::Return { .. }));
}
#[test]
fn errors_on_unsupported_opcode_iconst() {
let mut sig = Signature::new(CallConv::SystemV);
sig.returns.push(AbiParam::new(types::I32));
let mut func =
Function::with_name_signature(UserFuncName::testcase("iconst_fn".as_bytes()), sig);
let block = func.dfg.make_block();
func.layout.append_block(block);
let mut cursor = FuncCursor::new(&mut func).at_bottom(block);
let v = cursor.ins().iconst(types::I32, 7);
cursor.ins().return_(&[v]);
let err = lower_function(&func).expect_err("iconst is not in any wave-1 family");
match err {
LoweringError::UnsupportedOpcode { op, .. } => {
assert!(
op.contains("Iconst"),
"expected Iconst in the error op, got {op:?}",
);
}
other => panic!("expected UnsupportedOpcode, got {other:?}"),
}
}
#[test]
fn lowers_multi_block_jump_then_return() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I32));
sig.returns.push(AbiParam::new(types::I32));
let mut func =
Function::with_name_signature(UserFuncName::testcase("two_block".as_bytes()), sig);
let entry = func.dfg.make_block();
let entry_param = func.dfg.append_block_param(entry, types::I32);
let next = func.dfg.make_block();
let next_param = func.dfg.append_block_param(next, types::I32);
func.layout.append_block(entry);
func.layout.append_block(next);
let block_call = BlockCall::new(next, &[entry_param], &mut func.dfg.value_lists);
let jump_inst = func.dfg.make_inst(InstructionData::Jump {
opcode: Opcode::Jump,
destination: block_call,
});
func.dfg.make_inst_results(jump_inst, types::INVALID);
func.layout.append_inst(jump_inst, entry);
{
let mut cursor = FuncCursor::new(&mut func).at_bottom(next);
cursor.ins().return_(&[next_param]);
}
let lowered = lower_function(&func).expect("multi-block must lower");
assert_eq!(lowered.blocks.len(), 2, "two blocks expected");
let entry_lblock = &lowered.blocks[0];
assert_eq!(entry_lblock.ops.len(), 1, "entry has one op (Br)");
match &entry_lblock.ops[0] {
LoweredOp::Br { target, args } => {
assert_eq!(*target, lowered.blocks[1].id);
assert_eq!(args.len(), 1);
}
other => panic!("expected Br, got {other:?}"),
}
let next_lblock = &lowered.blocks[1];
assert_eq!(next_lblock.ops.len(), 1, "next has one op (Return)");
assert!(matches!(next_lblock.ops[0], LoweredOp::Return { .. }));
assert!(lowered.is_well_formed());
assert_eq!(lowered.entry, entry_lblock.id);
}
#[test]
fn errors_on_function_without_entry_block() {
let func = Function::new();
let err = lower_function(&func).expect_err("no entry block");
match err {
LoweringError::MalformedTerminator { block_id } => {
assert_eq!(block_id, 0);
}
other => panic!("expected MalformedTerminator, got {other:?}"),
}
}
#[test]
fn errors_on_unsupported_signature_type() {
let mut sig = Signature::new(CallConv::SystemV);
sig.params.push(AbiParam::new(types::I128));
let func = Function::with_name_signature(UserFuncName::default(), sig);
let err = lower_function(&func).expect_err("I128 param must reject");
match err {
LoweringError::UnsupportedType { ty, .. } => {
assert!(
ty.contains("i128"),
"expected i128 in the error display, got {ty:?}",
);
}
other => panic!("expected UnsupportedType, got {other:?}"),
}
}
#[test]
fn function_name_renders_testcase_and_user() {
let f1 = Function::with_name_signature(
UserFuncName::testcase("k1".as_bytes()),
Signature::new(CallConv::SystemV),
);
assert_eq!(function_name(&f1), "k1");
let f2 = Function::with_name_signature(
UserFuncName::user(2, 5),
Signature::new(CallConv::SystemV),
);
assert_eq!(function_name(&f2), "user2_5");
}
}