use rucc_base::Interner;
use rucc_ir::{
CallInfo, Extra, Float, Func, Inst, InstData, MemInfo, MemOrder, Opcode, Param, Restrict,
Signature, Type, Value,
};
use rucc_target::AbiDescription;
use crate::capability;
const HALF: Float = Float::F16;
const WIDE: Float = Float::F32;
const THROUGH: Float = Float::F64;
fn routine(opcode: Opcode, mode: &str) -> &'static str {
capability::libcall(opcode, mode)
.unwrap_or_else(|| panic!("no routine for `{}` at `{mode}`", opcode.name()))
}
pub fn calls(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription) {
let found: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in found {
match func[inst].opcode {
Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv => {
arithmetic(func, names, abi, inst);
}
Opcode::FCmp => compare(func, names, abi, inst),
Opcode::FPExt => widen(func, names, abi, inst),
Opcode::FPTrunc => narrow(func, names, abi, inst),
Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, abi, inst),
Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, abi, inst),
Opcode::Store => stored(func, inst),
_ => {}
}
}
}
fn half(ty: Type) -> bool {
ty.is_scalar() && ty.format() == Some(HALF)
}
fn produced(func: &Func, inst: Inst) -> Option<Type> {
func[inst].first_result.map(|value| func[value].ty)
}
fn arithmetic(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
let Some(ty) = produced(func, inst) else { return };
if !half(ty) {
return;
}
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
let opcode = func[inst].opcode;
let flags = func[inst].flags;
let a = extended(func, names, abi, inst, a);
let b = extended(func, names, abi, inst, b);
let args = func.push_values(&[a, b]);
let data = InstData { args, flags, ..InstData::new(opcode) };
let answer = written(func, inst, data, Type::float(WIDE));
into_call(func, names, abi, inst, routine(Opcode::FPTrunc, "f32.f16"), &[answer]);
}
fn compare(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
if !half(func[a].ty) || !half(func[b].ty) {
return;
}
let a = extended(func, names, abi, inst, a);
let b = extended(func, names, abi, inst, b);
func[inst].args = func.push_values(&[a, b]);
}
fn widen(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
let Some(ty) = produced(func, inst) else { return };
let Some(&arg) = func[func[inst].args].first() else { return };
if !half(func[arg].ty) {
return;
}
let routine = routine(Opcode::FPExt, "f16.f32");
if ty.format() == Some(WIDE) {
into_call(func, names, abi, inst, routine, &[arg]);
return;
}
let wide = call(func, names, abi, inst, routine, &[arg], Type::float(WIDE));
becomes(func, inst, Opcode::FPExt, Extra::None, &[wide]);
}
fn narrow(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
let Some(ty) = produced(func, inst) else { return };
let Some(&arg) = func[func[inst].args].first() else { return };
if !half(ty) {
return;
}
let mode = match func[arg].ty.format() {
Some(Float::F32) => "f32.f16",
Some(Float::F64) => "f64.f16",
Some(Float::F128) => "f128.f16",
_ => return,
};
into_call(func, names, abi, inst, routine(Opcode::FPTrunc, mode), &[arg]);
}
fn from_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
let Some(ty) = produced(func, inst) else { return };
let Some(&arg) = func[func[inst].args].first() else { return };
let from = func[arg].ty;
if !half(ty) || !from.is_int() || !from.is_scalar() {
return;
}
let opcode = func[inst].opcode;
let args = func.push_values(&[arg]);
let data = InstData { args, ..InstData::new(opcode) };
let wide = written(func, inst, data, Type::float(THROUGH));
into_call(func, names, abi, inst, routine(Opcode::FPTrunc, "f64.f16"), &[wide]);
}
fn to_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
let Some(ty) = produced(func, inst) else { return };
let Some(&arg) = func[func[inst].args].first() else { return };
if !half(func[arg].ty) || !ty.is_int() || !ty.is_scalar() {
return;
}
let opcode = func[inst].opcode;
let wide = extended(func, names, abi, inst, arg);
becomes(func, inst, opcode, Extra::None, &[wide]);
}
fn stored(func: &mut Func, inst: Inst) {
let args = func[func[inst].args].to_vec();
let [value, address] = args[..] else { return };
if !half(func[value].ty) {
return;
}
let bits = ahead(func, inst, Opcode::Bitcast, &[value], Type::int(16));
func[inst].args = func.push_values(&[bits, address]);
}
fn extended(
func: &mut Func,
names: &mut Interner,
abi: &'static AbiDescription,
inst: Inst,
value: Value,
) -> Value {
let routine = routine(Opcode::FPExt, "f16.f32");
call(func, names, abi, inst, routine, &[value], Type::float(WIDE))
}
fn into_call(
func: &mut Func,
names: &mut Interner,
abi: &'static AbiDescription,
inst: Inst,
routine: &str,
args: &[Value],
) {
let Some(ty) = produced(func, inst) else { return };
let shape = shaped(func, abi, inst, args);
let extra = signature(func, names, routine, &shape, ty);
becomes(func, inst, Opcode::Call, extra, &shape.values);
}
fn call(
func: &mut Func,
names: &mut Interner,
abi: &'static AbiDescription,
inst: Inst,
routine: &str,
args: &[Value],
ty: Type,
) -> Value {
let shape = shaped(func, abi, inst, args);
let extra = signature(func, names, routine, &shape, ty);
let args = func.push_values(&shape.values);
written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Call) }, ty)
}
struct Shape {
params: Vec<Param>,
values: Vec<Value>,
}
fn shaped(func: &mut Func, abi: &'static AbiDescription, inst: Inst, args: &[Value]) -> Shape {
let mut shape = Shape { params: Vec::new(), values: Vec::new() };
for &value in args {
let ty = func[value].ty;
let size = u64::from(ty.bits().div_ceil(8));
if quad(ty) && abi.scalar_is_by_reference(size) {
let copy = slot(func, inst);
write(func, inst, value, copy);
shape.params.push(Param::new(Type::PTR));
shape.values.push(copy);
} else {
shape.params.push(Param::new(ty));
shape.values.push(value);
}
}
shape
}
fn quad(ty: Type) -> bool {
ty.is_scalar() && ty.format() == Some(Float::F128)
}
fn signature(
func: &mut Func,
names: &mut Interner,
routine: &str,
shape: &Shape,
ty: Type,
) -> Extra {
let mut built = Signature::new();
built.params = shape.params.clone();
built.returns = vec![Param::new(ty)];
let signature = func.add_signature(built);
let callee = Some(names.intern(routine));
let varargs = func.push_abis(&[]);
Extra::Call(func.add_call(CallInfo { callee, signature, varargs }))
}
fn slot(func: &mut Func, inst: Inst) -> Value {
let extra = Extra::Mem(func.add_mem(whole()));
written(func, inst, InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
}
fn whole() -> MemInfo {
MemInfo {
size: 16,
align: 16,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
}
}
fn write(func: &mut Func, inst: Inst, value: Value, into: Value) {
let span = func.span(inst);
let extra = Extra::Mem(func.add_mem(whole()));
let args = func.push_values(&[value, into]);
let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
let made = func.create_inst(data, &[], span);
func.insert_before(made, inst);
}
fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
let args = func.push_values(args);
written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
}
fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
let span = func.span(inst);
let made = func.create_inst(data, &[ty], span);
func.insert_before(made, inst);
func[made].first_result.expect("an instruction created with one result has one")
}
fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) {
let args = func.push_values(args);
let data = &mut func[inst];
data.opcode = opcode;
data.args = args;
data.extra = extra;
data.flags = data.flags.intersection(rucc_ir::Flags::legal_on(opcode));
}