#![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_float_inst(
inst: Inst,
func: &Function,
value_map: &HashMap<Value, LoweredValueId>,
next_value_id: &mut LoweredValueId,
) -> Option<LoweredOp> {
let opcode = func.dfg.insts[inst].opcode();
match opcode {
Opcode::Fadd
| Opcode::Fsub
| Opcode::Fmul
| Opcode::Fdiv
| Opcode::Fma
| Opcode::Fneg
| Opcode::Fabs => {}
_ => return None,
}
let result_value = func.dfg.first_result(inst);
let ty = match func.dfg.value_type(result_value) {
t if t == ir::types::F32 => LoweredType::F32,
t if t == ir::types::F64 => LoweredType::F64,
_ => return None,
};
let args = func.dfg.inst_args(inst);
let result = *next_value_id;
*next_value_id = next_value_id.checked_add(1)?;
let lowered = match opcode {
Opcode::Fadd => LoweredOp::AddF {
ty,
lhs: lookup(value_map, args[0])?,
rhs: lookup(value_map, args[1])?,
result,
},
Opcode::Fsub => LoweredOp::SubF {
ty,
lhs: lookup(value_map, args[0])?,
rhs: lookup(value_map, args[1])?,
result,
},
Opcode::Fmul => LoweredOp::MulF {
ty,
lhs: lookup(value_map, args[0])?,
rhs: lookup(value_map, args[1])?,
result,
},
Opcode::Fdiv => LoweredOp::DivF {
ty,
lhs: lookup(value_map, args[0])?,
rhs: lookup(value_map, args[1])?,
result,
},
Opcode::Fma => LoweredOp::Fma {
ty,
a: lookup(value_map, args[0])?,
b: lookup(value_map, args[1])?,
c: lookup(value_map, args[2])?,
result,
},
Opcode::Fneg => LoweredOp::FNeg {
ty,
src: lookup(value_map, args[0])?,
result,
},
Opcode::Fabs => LoweredOp::FAbs {
ty,
src: lookup(value_map, args[0])?,
result,
},
_ => unreachable!("opcode already validated by the dispatch match"),
};
Some(lowered)
}
fn lookup(map: &HashMap<Value, LoweredValueId>, v: Value) -> Option<LoweredValueId> {
let id = map.get(&v).copied();
if id.is_none() {
tracing::debug!(
target: "tensor_wasm_jit::lower_float",
value = ?v,
"operand not pre-mapped; skipping float instruction"
);
}
id
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift_codegen::ir::{
types, AbiParam, Block, Function, InstructionData, Signature, UserFuncName, Value,
};
use cranelift_codegen::isa::CallConv;
struct InstFixture {
func: Function,
inst: Inst,
operands: Vec<Value>,
}
fn build_func_with_inst(
param_ty: cranelift_codegen::ir::Type,
arg_count: usize,
make_data: impl FnOnce(&[Value]) -> InstructionData,
) -> InstFixture {
let mut sig = Signature::new(CallConv::Fast);
for _ in 0..arg_count {
sig.params.push(AbiParam::new(param_ty));
}
sig.returns.push(AbiParam::new(param_ty));
let mut func = Function::with_name_signature(UserFuncName::testcase("t"), sig);
let block: Block = func.dfg.make_block();
let mut operands: Vec<Value> = Vec::with_capacity(arg_count);
for _ in 0..arg_count {
operands.push(func.dfg.append_block_param(block, param_ty));
}
let data = make_data(&operands);
let inst = func.dfg.make_inst(data);
func.dfg.make_inst_results(inst, param_ty);
InstFixture {
func,
inst,
operands,
}
}
fn map_operands(operands: &[Value]) -> (HashMap<Value, LoweredValueId>, LoweredValueId) {
let mut map = HashMap::new();
let mut next: LoweredValueId = 0;
for v in operands {
map.insert(*v, next);
next += 1;
}
(map, next)
}
fn binary(opcode: Opcode, args: [Value; 2]) -> InstructionData {
InstructionData::Binary { opcode, args }
}
fn ternary(opcode: Opcode, args: [Value; 3]) -> InstructionData {
InstructionData::Ternary { opcode, args }
}
fn unary(opcode: Opcode, arg: Value) -> InstructionData {
InstructionData::Unary { opcode, arg }
}
#[test]
fn lower_fadd_f32() {
let fx = build_func_with_inst(types::F32, 2, |params| {
binary(Opcode::Fadd, [params[0], params[1]])
});
let (map, mut next) = map_operands(&fx.operands);
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next).expect("fadd lowers");
match op {
LoweredOp::AddF {
ty,
lhs,
rhs,
result,
} => {
assert_eq!(ty, LoweredType::F32);
assert_eq!(lhs, 0);
assert_eq!(rhs, 1);
assert_eq!(result, 2);
}
other => panic!("expected AddF, got {other:?}"),
}
assert_eq!(next, 3);
}
#[test]
fn lower_fsub_f64() {
let fx = build_func_with_inst(types::F64, 2, |params| {
binary(Opcode::Fsub, [params[0], params[1]])
});
let (map, mut next) = map_operands(&fx.operands);
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next).expect("fsub lowers");
match op {
LoweredOp::SubF { ty, .. } => assert_eq!(ty, LoweredType::F64),
other => panic!("expected SubF, got {other:?}"),
}
}
#[test]
fn lower_fmul_f32() {
let fx = build_func_with_inst(types::F32, 2, |params| {
binary(Opcode::Fmul, [params[0], params[1]])
});
let (map, mut next) = map_operands(&fx.operands);
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next).expect("fmul lowers");
match op {
LoweredOp::MulF { ty, .. } => assert_eq!(ty, LoweredType::F32),
other => panic!("expected MulF, got {other:?}"),
}
}
#[test]
fn lower_fdiv_f64() {
let fx = build_func_with_inst(types::F64, 2, |params| {
binary(Opcode::Fdiv, [params[0], params[1]])
});
let (map, mut next) = map_operands(&fx.operands);
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next).expect("fdiv lowers");
match op {
LoweredOp::DivF { ty, .. } => assert_eq!(ty, LoweredType::F64),
other => panic!("expected DivF, got {other:?}"),
}
}
#[test]
fn lower_fma_f32() {
let fx = build_func_with_inst(types::F32, 3, |params| {
ternary(Opcode::Fma, [params[0], params[1], params[2]])
});
let (map, mut next) = map_operands(&fx.operands);
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next).expect("fma lowers");
match op {
LoweredOp::Fma {
ty,
a,
b,
c,
result,
} => {
assert_eq!(ty, LoweredType::F32);
assert_eq!(a, 0);
assert_eq!(b, 1);
assert_eq!(c, 2);
assert_eq!(result, 3);
}
other => panic!("expected Fma, got {other:?}"),
}
}
#[test]
fn lower_fneg_f64() {
let fx = build_func_with_inst(types::F64, 1, |params| unary(Opcode::Fneg, params[0]));
let (map, mut next) = map_operands(&fx.operands);
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next).expect("fneg lowers");
match op {
LoweredOp::FNeg { ty, src, result } => {
assert_eq!(ty, LoweredType::F64);
assert_eq!(src, 0);
assert_eq!(result, 1);
}
other => panic!("expected FNeg, got {other:?}"),
}
}
#[test]
fn lower_fabs_f32() {
let fx = build_func_with_inst(types::F32, 1, |params| unary(Opcode::Fabs, params[0]));
let (map, mut next) = map_operands(&fx.operands);
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next).expect("fabs lowers");
match op {
LoweredOp::FAbs { ty, src, result } => {
assert_eq!(ty, LoweredType::F32);
assert_eq!(src, 0);
assert_eq!(result, 1);
}
other => panic!("expected FAbs, got {other:?}"),
}
}
#[test]
fn non_float_opcode_returns_none() {
let fx = build_func_with_inst(types::I32, 2, |params| {
binary(Opcode::Iadd, [params[0], params[1]])
});
let (map, mut next) = map_operands(&fx.operands);
let before = next;
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next);
assert!(op.is_none(), "iadd must not be claimed by lower_float");
assert_eq!(next, before, "counter must not advance on rejection");
}
#[test]
fn vector_fadd_returns_none() {
let fx = build_func_with_inst(types::F32X4, 2, |params| {
binary(Opcode::Fadd, [params[0], params[1]])
});
let (map, mut next) = map_operands(&fx.operands);
let before = next;
let op = lower_float_inst(fx.inst, &fx.func, &map, &mut next);
assert!(
op.is_none(),
"vector fadd must not be claimed by lower_float"
);
assert_eq!(next, before);
}
}