#![cfg(feature = "cuda-oxide-backend")]
#![allow(dead_code)]
use std::collections::HashMap;
use cranelift_codegen::ir::{self, Function, Inst, Opcode, Value};
use crate::lowered_ir::{LoweredOp, LoweredType, LoweredValueId};
fn cranelift_type_to_lowered(ty: ir::Type) -> Option<LoweredType> {
use cranelift_codegen::ir::types;
match ty {
types::I8 => Some(LoweredType::I8),
types::I16 => Some(LoweredType::I16),
types::I32 => Some(LoweredType::I32),
types::I64 => Some(LoweredType::I64),
types::F32 => Some(LoweredType::F32),
types::F64 => Some(LoweredType::F64),
_ => None,
}
}
fn lookup_value(value_map: &HashMap<Value, LoweredValueId>, v: Value) -> Option<LoweredValueId> {
value_map.get(&v).copied()
}
fn alloc_result(next_value_id: &mut LoweredValueId) -> Option<LoweredValueId> {
let id = *next_value_id;
*next_value_id = next_value_id.checked_add(1)?;
Some(id)
}
pub fn lower_conv_inst(
inst: Inst,
func: &Function,
value_map: &HashMap<Value, LoweredValueId>,
next_value_id: &mut LoweredValueId,
) -> Option<LoweredOp> {
let opcode = func.dfg.insts[inst].opcode();
let args = func.dfg.inst_args(inst);
let results = func.dfg.inst_results(inst);
match opcode {
Opcode::Select => {
if args.len() != 3 || results.len() != 1 {
return None;
}
let cond_v = args[0];
let then_v = args[1];
let else_v = args[2];
let result_v = results[0];
let ty = cranelift_type_to_lowered(func.dfg.value_type(result_v))?;
let cond = lookup_value(value_map, cond_v)?;
let then_id = lookup_value(value_map, then_v)?;
let else_id = lookup_value(value_map, else_v)?;
let result = alloc_result(next_value_id)?;
Some(LoweredOp::Select {
ty,
cond,
then_v: then_id,
else_v: else_id,
result,
})
}
Opcode::Bitcast => {
if args.len() != 1 || results.len() != 1 {
return None;
}
let src_v = args[0];
let result_v = results[0];
let from_ty = cranelift_type_to_lowered(func.dfg.value_type(src_v))?;
let to_ty = cranelift_type_to_lowered(func.dfg.value_type(result_v))?;
let src = lookup_value(value_map, src_v)?;
let result = alloc_result(next_value_id)?;
Some(LoweredOp::Bitcast {
from_ty,
to_ty,
src,
result,
})
}
Opcode::Ireduce => {
if args.len() != 1 || results.len() != 1 {
return None;
}
let src_v = args[0];
let result_v = results[0];
let from_ty = cranelift_type_to_lowered(func.dfg.value_type(src_v))?;
let to_ty = cranelift_type_to_lowered(func.dfg.value_type(result_v))?;
if !from_ty.is_int() || !to_ty.is_int() {
return None;
}
let src = lookup_value(value_map, src_v)?;
let result = alloc_result(next_value_id)?;
Some(LoweredOp::TruncI {
from_ty,
to_ty,
src,
result,
})
}
Opcode::Uextend => {
if args.len() != 1 || results.len() != 1 {
return None;
}
let src_v = args[0];
let result_v = results[0];
let from_ty = cranelift_type_to_lowered(func.dfg.value_type(src_v))?;
let to_ty = cranelift_type_to_lowered(func.dfg.value_type(result_v))?;
if !from_ty.is_int() || !to_ty.is_int() {
return None;
}
let src = lookup_value(value_map, src_v)?;
let result = alloc_result(next_value_id)?;
Some(LoweredOp::ExtendU {
from_ty,
to_ty,
src,
result,
})
}
Opcode::Sextend => {
if args.len() != 1 || results.len() != 1 {
return None;
}
let src_v = args[0];
let result_v = results[0];
let from_ty = cranelift_type_to_lowered(func.dfg.value_type(src_v))?;
let to_ty = cranelift_type_to_lowered(func.dfg.value_type(result_v))?;
if !from_ty.is_int() || !to_ty.is_int() {
return None;
}
let src = lookup_value(value_map, src_v)?;
let result = alloc_result(next_value_id)?;
Some(LoweredOp::ExtendS {
from_ty,
to_ty,
src,
result,
})
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::ir::types::{F32, I16, I32, I64, I8};
use cranelift_codegen::ir::{Function, InstBuilder, MemFlags};
fn run_lower<F>(param_types: &[ir::Type], build: F) -> Option<LoweredOp>
where
F: FnOnce(&mut FuncCursor<'_>, &[ir::Value]) -> Inst,
{
let mut func = Function::new();
let block = func.dfg.make_block();
let mut params = Vec::with_capacity(param_types.len());
for ty in param_types {
params.push(func.dfg.append_block_param(block, *ty));
}
let mut value_map: HashMap<Value, LoweredValueId> = HashMap::new();
for (i, v) in params.iter().enumerate() {
value_map.insert(*v, i as LoweredValueId);
}
let mut next_value_id: LoweredValueId = params.len() as LoweredValueId;
let inst = {
let mut cur = FuncCursor::new(&mut func);
cur.insert_block(block);
build(&mut cur, ¶ms)
};
lower_conv_inst(inst, &func, &value_map, &mut next_value_id)
}
#[test]
fn select_lowers_to_loweredop_select() {
let op = run_lower(&[I8, I32, I32], |cur, params| {
let cond = params[0];
let then_v = params[1];
let else_v = params[2];
cur.ins().select(cond, then_v, else_v);
cur.current_block()
.and_then(|b| cur.func.layout.last_inst(b))
.expect("instruction was appended")
})
.expect("select must lower");
match op {
LoweredOp::Select {
ty,
cond,
then_v,
else_v,
result,
} => {
assert_eq!(ty, LoweredType::I32);
assert_eq!(cond, 0);
assert_eq!(then_v, 1);
assert_eq!(else_v, 2);
assert_eq!(result, 3);
}
other => panic!("expected LoweredOp::Select, got {other:?}"),
}
}
#[test]
fn bitcast_lowers_to_loweredop_bitcast() {
let op = run_lower(&[I32], |cur, params| {
cur.ins().bitcast(F32, MemFlags::new(), params[0]);
cur.current_block()
.and_then(|b| cur.func.layout.last_inst(b))
.expect("instruction was appended")
})
.expect("bitcast must lower");
match op {
LoweredOp::Bitcast {
from_ty,
to_ty,
src,
result,
} => {
assert_eq!(from_ty, LoweredType::I32);
assert_eq!(to_ty, LoweredType::F32);
assert_eq!(src, 0);
assert_eq!(result, 1);
}
other => panic!("expected LoweredOp::Bitcast, got {other:?}"),
}
}
#[test]
fn ireduce_lowers_to_loweredop_trunci() {
let op = run_lower(&[I64], |cur, params| {
cur.ins().ireduce(I16, params[0]);
cur.current_block()
.and_then(|b| cur.func.layout.last_inst(b))
.expect("instruction was appended")
})
.expect("ireduce must lower");
match op {
LoweredOp::TruncI {
from_ty,
to_ty,
src,
result,
} => {
assert_eq!(from_ty, LoweredType::I64);
assert_eq!(to_ty, LoweredType::I16);
assert_eq!(src, 0);
assert_eq!(result, 1);
}
other => panic!("expected LoweredOp::TruncI, got {other:?}"),
}
}
#[test]
fn uextend_lowers_to_loweredop_extendu() {
let op = run_lower(&[I8], |cur, params| {
cur.ins().uextend(I32, params[0]);
cur.current_block()
.and_then(|b| cur.func.layout.last_inst(b))
.expect("instruction was appended")
})
.expect("uextend must lower");
match op {
LoweredOp::ExtendU {
from_ty,
to_ty,
src,
result,
} => {
assert_eq!(from_ty, LoweredType::I8);
assert_eq!(to_ty, LoweredType::I32);
assert_eq!(src, 0);
assert_eq!(result, 1);
}
other => panic!("expected LoweredOp::ExtendU, got {other:?}"),
}
}
#[test]
fn sextend_lowers_to_loweredop_extends() {
let op = run_lower(&[I16], |cur, params| {
cur.ins().sextend(I64, params[0]);
cur.current_block()
.and_then(|b| cur.func.layout.last_inst(b))
.expect("instruction was appended")
})
.expect("sextend must lower");
match op {
LoweredOp::ExtendS {
from_ty,
to_ty,
src,
result,
} => {
assert_eq!(from_ty, LoweredType::I16);
assert_eq!(to_ty, LoweredType::I64);
assert_eq!(src, 0);
assert_eq!(result, 1);
}
other => panic!("expected LoweredOp::ExtendS, got {other:?}"),
}
}
#[test]
fn non_conv_opcode_returns_none() {
let op = run_lower(&[I32, I32], |cur, params| {
cur.ins().iadd(params[0], params[1]);
cur.current_block()
.and_then(|b| cur.func.layout.last_inst(b))
.expect("instruction was appended")
});
assert!(op.is_none(), "lower_conv_inst should ignore non-conv ops");
}
#[test]
fn unmapped_operand_returns_none() {
let mut func = Function::new();
let block = func.dfg.make_block();
let v_in = func.dfg.append_block_param(block, I8);
let inst = {
let mut cur = FuncCursor::new(&mut func);
cur.insert_block(block);
cur.ins().uextend(I32, v_in);
cur.current_block()
.and_then(|b| cur.func.layout.last_inst(b))
.expect("instruction was appended")
};
let value_map: HashMap<Value, LoweredValueId> = HashMap::new(); let mut next_value_id: LoweredValueId = 0;
let op = lower_conv_inst(inst, &func, &value_map, &mut next_value_id);
assert!(op.is_none(), "unmapped operand must surface as None");
assert_eq!(next_value_id, 0);
}
}