use rucc_base::Interner;
use rucc_ir::{Extra, Float, FloatPred, Func, Imm, Inst, InstData, IntPred, Opcode, Type};
use rucc_target::AbiDescription;
use crate::quad::{ahead_const, becomes, call, flipped, into_call, written};
const NARROW: u32 = 32;
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::FNeg => negate(func, names, abi, inst),
Opcode::FCmp => compare(func, names, abi, inst),
Opcode::FPExt | Opcode::FPTrunc => convert(func, names, abi, inst),
Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, abi, inst),
Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, abi, inst),
_ => {}
}
}
}
fn letters(ty: Type) -> Option<&'static str> {
if !ty.is_scalar() {
return None;
}
Some(match ty.format()? {
Float::D32 => "sd",
Float::D64 => "dd",
Float::D128 => "td",
Float::F16 => "hf",
Float::F32 => "sf",
Float::F64 => "df",
Float::F80 => "xf",
Float::F128 => "tf",
})
}
fn decimal(ty: Type) -> Option<&'static str> {
if ty.format().is_some_and(Float::is_decimal) { letters(ty) } else { None }
}
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 };
let Some(mode) = decimal(ty) else { return };
let args = func[func[inst].args].to_vec();
let [a, b] = args[..] else { return };
let operation = match func[inst].opcode {
Opcode::FAdd => "add",
Opcode::FSub => "sub",
Opcode::FMul => "mul",
_ => "div",
};
into_call(func, names, abi, inst, &format!("__bid_{operation}{mode}3"), &[a, b]);
}
fn negate(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 decimal(ty) != Some("td") {
return;
}
into_call(func, names, abi, inst, "__negtf2", &[arg]);
}
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 };
let Some(mode) = decimal(func[a].ty) else { return };
let Extra::FloatPred(pred) = func[inst].extra else { return };
let named = |operation: &str| format!("__bid_{operation}{mode}2");
let answer = Type::int(NARROW);
let single = match pred {
FloatPred::Oeq => Some(("eq", IntPred::Eq)),
FloatPred::Une => Some(("ne", IntPred::Ne)),
FloatPred::Olt => Some(("lt", IntPred::Slt)),
FloatPred::Ole => Some(("le", IntPred::Sle)),
FloatPred::Ogt => Some(("gt", IntPred::Sgt)),
FloatPred::Oge => Some(("ge", IntPred::Sge)),
FloatPred::Uno => Some(("unord", IntPred::Ne)),
FloatPred::Ord => Some(("unord", IntPred::Eq)),
FloatPred::Ult => Some(("ge", IntPred::Slt)),
FloatPred::Ule => Some(("gt", IntPred::Sle)),
FloatPred::Ugt => Some(("le", IntPred::Sgt)),
FloatPred::Uge => Some(("lt", IntPred::Sge)),
_ => None,
};
if let Some((operation, test)) = single {
let got = call(func, names, abi, inst, &named(operation), &[a, b], answer);
let zero = ahead_const(func, inst, Imm::int(0, answer), answer);
becomes(func, inst, Opcode::ICmp, Extra::IntPred(test), &[got, zero]);
return;
}
if let FloatPred::False | FloatPred::True = pred {
let bits = i128::from(pred == FloatPred::True);
let extra = Extra::Imm(func.add_imm(Imm::int(bits, Type::I1)));
becomes(func, inst, Opcode::IConst, extra, &[]);
return;
}
let (FloatPred::One | FloatPred::Ueq) = pred else { return };
let mut tested = |operation: &str, test: IntPred| {
let got = call(func, names, abi, inst, &named(operation), &[a, b], answer);
let zero = ahead_const(func, inst, Imm::int(0, answer), answer);
let args = func.push_values(&[got, zero]);
let extra = Extra::IntPred(test);
written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
};
let ordered = tested("unord", IntPred::Eq);
let different = tested("ne", IntPred::Ne);
let (opcode, args) = if pred == FloatPred::One {
(Opcode::And, [ordered, different])
} else {
let unordered = flipped(func, inst, ordered);
let same = flipped(func, inst, different);
(Opcode::Or, [unordered, same])
};
becomes(func, inst, opcode, Extra::None, &args);
}
fn convert(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 decimal(ty).is_none() && decimal(from).is_none() {
return;
}
let (Some(into), Some(out)) = (letters(ty), letters(from)) else { return };
let way = if across(from, ty) { "extend" } else { "trunc" };
let tail = if decimal(ty).is_some() && decimal(from).is_some() { "2" } else { "" };
into_call(func, names, abi, inst, &format!("__bid_{way}{out}{into}{tail}"), &[arg]);
}
fn across(from: Type, into: Type) -> bool {
let (a, b) = (into.bits(), from.bits());
a > b || (a == b && decimal(into).is_some())
}
fn holder(bits: u32) -> Option<(u32, &'static str)> {
match bits {
0..=32 => Some((32, "si")),
33..=64 => Some((64, "di")),
65..=128 => Some((128, "ti")),
_ => None,
}
}
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;
let Some(mode) = decimal(ty) else { return };
if !from.is_int() || !from.is_scalar() {
return;
}
let Some((width, letters)) = holder(from.bits()) else { return };
let signed = func[inst].opcode == Opcode::SIToFP;
let value = if from.bits() == width {
arg
} else {
let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
let args = func.push_values(&[arg]);
written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
};
let kind = if signed { "float" } else { "floatuns" };
into_call(func, names, abi, inst, &format!("__bid_{kind}{letters}{mode}"), &[value]);
}
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 };
let Some(mode) = decimal(func[arg].ty) else { return };
if !ty.is_int() || !ty.is_scalar() {
return;
}
let Some((width, letters)) = holder(ty.bits()) else { return };
let kind = if func[inst].opcode == Opcode::FPToSI { "fix" } else { "fixuns" };
let routine = format!("__bid_{kind}{mode}{letters}");
if ty.bits() == width {
into_call(func, names, abi, inst, &routine, &[arg]);
return;
}
let answer = call(func, names, abi, inst, &routine, &[arg], Type::int(width));
becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Block, Builder, Flags, Module, Signature, Value};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple, x86_64};
use super::{Float, FloatPred, Func, Opcode, Type, calls};
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn printed(func: &Func, names: &mut Interner) -> String {
let module = Module::new(names.intern("d.c"), &target());
rucc_ir::print_func(&module, func, names)
}
fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
let signature = Signature::new().with_params(params).with_returns(returns);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
(func, entry, values)
}
fn after(
params: &[Type],
returns: Type,
make: impl FnOnce(&mut Builder<'_>, &[Value]) -> Value,
) -> String {
let mut names = Interner::new();
let (mut func, entry, args) = shell(&mut names, params, &[returns]);
let mut build = Builder::new(&mut func, entry);
let made = make(&mut build, &args);
build.ret(&[made]);
calls(&mut func, &mut names, x86_64::SYSV.abi);
printed(&func, &mut names)
}
#[test]
fn arithmetic_at_each_width_is_the_routine_with_that_widths_letters() {
for (format, name) in [
(Float::D32, "__bid_addsd3"),
(Float::D64, "__bid_adddd3"),
(Float::D128, "__bid_addtd3"),
] {
let ty = Type::float(format);
let text = after(&[ty, ty], ty, |build, args| {
build.binary(Opcode::FAdd, args[0], args[1], Flags::NONE)
});
assert!(text.contains(name), "{text}");
assert!(!text.contains("fadd"), "{text}");
}
}
#[test]
fn a_binary_float_is_left_for_the_steps_after_this_one() {
let ty = Type::float(Float::F64);
let text = after(&[ty, ty], ty, |build, args| {
build.binary(Opcode::FMul, args[0], args[1], Flags::NONE)
});
assert!(text.contains("fmul"), "{text}");
assert!(!text.contains("__bid"), "{text}");
}
#[test]
fn a_comparison_is_the_routine_and_a_test_of_its_answer() {
let ty = Type::float(Float::D64);
let text = after(&[ty, ty], Type::I1, |build, args| {
build.fcmp(FloatPred::Olt, args[0], args[1], Flags::NONE)
});
assert!(text.contains("__bid_ltdd2"), "{text}");
assert!(text.contains("icmp slt"), "{text}");
let text = after(&[ty, ty], Type::I1, |build, args| {
build.fcmp(FloatPred::One, args[0], args[1], Flags::NONE)
});
assert!(text.contains("__bid_unorddd2") && text.contains("__bid_nedd2"), "{text}");
}
#[test]
fn a_conversion_is_named_the_way_libgcc_names_it() {
let rows = [
(Float::D32, Float::D64, "__bid_extendsddd2"),
(Float::D128, Float::D64, "__bid_trunctddd2"),
(Float::F64, Float::D64, "__bid_extenddfdd"),
(Float::D64, Float::F64, "__bid_truncdddf"),
(Float::F80, Float::D64, "__bid_truncxfdd"),
(Float::F80, Float::D128, "__bid_extendxftd"),
(Float::D32, Float::F128, "__bid_extendsdtf"),
];
for (from, into, name) in rows {
let (from, into) = (Type::float(from), Type::float(into));
let opcode = if super::across(from, into) { Opcode::FPExt } else { Opcode::FPTrunc };
let text = after(&[from], into, |build, args| build.unary(opcode, args[0], into));
assert!(text.contains(name), "{name}: {text}");
}
}
#[test]
fn an_integer_conversion_goes_through_a_width_there_is_a_routine_at() {
let ty = Type::float(Float::D64);
let text =
after(&[Type::int(16)], ty, |build, args| build.unary(Opcode::SIToFP, args[0], ty));
assert!(text.contains("sext") && text.contains("__bid_floatsidd"), "{text}");
let text = after(&[ty], Type::int(8), |build, args| {
build.unary(Opcode::FPToUI, args[0], Type::int(8))
});
assert!(text.contains("__bid_fixunsddsi") && text.contains("trunc"), "{text}");
let text =
after(&[Type::int(128)], ty, |build, args| build.unary(Opcode::UIToFP, args[0], ty));
assert!(text.contains("__bid_floatunstidd"), "{text}");
}
}