Skip to main content

rucc_codegen/
decimal.rs

1//! The decimal floating types, as the calls to libgcc's routines for them.
2//!
3//! `_Decimal32`, `_Decimal64` and `_Decimal128` are carried in the binary integer decimal encoding,
4//! which is what gcc uses on x86-64 and what the `__bid_` routines in libgcc compute with. No
5//! instruction on this machine does arithmetic on that encoding, so every operation on one is a call.
6//! Moving, loading, storing and passing one is not, because the convention puts a decimal in the same
7//! vector register a binary float of its width goes in, and the rule set is written by width for
8//! exactly those. So this pass is the whole of the difference, the same way [`crate::quad`] is for
9//! `_Float128`.
10//!
11//! Every name here is built rather than looked up in [`crate::capability`], because the family is
12//! regular: the operation, then `sd`, `dd` or `td` for the decimal width, then the other format's
13//! letters where there is one, then the operand count for arithmetic and comparisons. The one part
14//! that is not a pattern is which conversions libgcc calls `extend` and which `trunc`, and
15//! `across` spells that out.
16//!
17//! What is not done here is left to the steps after it. A negation or a constant at thirty two or
18//! sixty four bits is the sign bit and the bits, which [`crate::expand::floats`] already does by
19//! width without caring what the bits mean. A constant at a hundred and twenty eight bits goes through
20//! the frame in [`crate::quad`] for the same reason a `_Float128` one does. A conversion between a
21//! decimal and a `__bf16` has no routine and is left for the selector to refuse by name.
22
23use rucc_base::Interner;
24use rucc_ir::{Extra, Float, FloatPred, Func, Imm, Inst, InstData, IntPred, Opcode, Type};
25use rucc_target::AbiDescription;
26
27use crate::quad::{ahead_const, becomes, call, flipped, into_call, written};
28
29/// The width the comparison routines answer in, and the narrower of the two integer widths the
30/// conversion routines take.
31const NARROW: u32 = 32;
32
33/// Rewrites every operation on a decimal float into the call that performs it.
34///
35/// The instructions are collected before any of them is touched, for the reason [`crate::quad`]
36/// gives: a rewrite puts instructions in front of the one it replaces.
37pub fn calls(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription) {
38    let found: Vec<Inst> =
39        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
40    for inst in found {
41        match func[inst].opcode {
42            Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv => {
43                arithmetic(func, names, abi, inst);
44            }
45            Opcode::FNeg => negate(func, names, abi, inst),
46            Opcode::FCmp => compare(func, names, abi, inst),
47            Opcode::FPExt | Opcode::FPTrunc => convert(func, names, abi, inst),
48            Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, abi, inst),
49            Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, abi, inst),
50            _ => {}
51        }
52    }
53}
54
55/// The letters libgcc names a float format by, where it has a routine for it.
56fn letters(ty: Type) -> Option<&'static str> {
57    if !ty.is_scalar() {
58        return None;
59    }
60    Some(match ty.format()? {
61        Float::D32 => "sd",
62        Float::D64 => "dd",
63        Float::D128 => "td",
64        Float::F16 => "hf",
65        Float::F32 => "sf",
66        Float::F64 => "df",
67        Float::F80 => "xf",
68        Float::F128 => "tf",
69    })
70}
71
72/// The letters of a decimal type, and nothing for any other type.
73fn decimal(ty: Type) -> Option<&'static str> {
74    if ty.format().is_some_and(Float::is_decimal) { letters(ty) } else { None }
75}
76
77/// The type of an instruction's first result, or nothing where it has none.
78fn produced(func: &Func, inst: Inst) -> Option<Type> {
79    func[inst].first_result.map(|value| func[value].ty)
80}
81
82/// The four operations, each the routine of its name over the two operands, in place.
83fn arithmetic(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
84    let Some(ty) = produced(func, inst) else { return };
85    let Some(mode) = decimal(ty) else { return };
86    let args = func[func[inst].args].to_vec();
87    let [a, b] = args[..] else { return };
88    let operation = match func[inst].opcode {
89        Opcode::FAdd => "add",
90        Opcode::FSub => "sub",
91        Opcode::FMul => "mul",
92        _ => "div",
93    };
94    into_call(func, names, abi, inst, &format!("__bid_{operation}{mode}3"), &[a, b]);
95}
96
97/// The negation, which is only the sign bit at every width.
98///
99/// At thirty two and sixty four bits that is [`crate::expand::floats`]'s exclusive or, which reads
100/// the bits by width and so is right for a decimal as it stands. At a hundred and twenty eight there
101/// is no register to do it in, and `__negtf2` is a routine that flips bit one hundred and twenty
102/// seven and reads nothing else, in libgcc's soft float and in compiler-rt alike, which is the whole
103/// of a decimal negation too.
104fn negate(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
105    let Some(ty) = produced(func, inst) else { return };
106    let Some(&arg) = func[func[inst].args].first() else { return };
107    if decimal(ty) != Some("td") {
108        return;
109    }
110    into_call(func, names, abi, inst, "__negtf2", &[arg]);
111}
112
113/// A comparison, as the call that answers it and the test of that answer against zero.
114///
115/// The routines answer the way libgcc's binary ones do, so this is the table [`crate::quad`] has
116/// with other names. An ordered predicate is one routine read the way its name says, an unordered
117/// one is the routine for its negation read the other way round, and `one` and `ueq` are two calls.
118fn compare(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
119    let args = func[func[inst].args].to_vec();
120    let [a, b] = args[..] else { return };
121    let Some(mode) = decimal(func[a].ty) else { return };
122    let Extra::FloatPred(pred) = func[inst].extra else { return };
123    let named = |operation: &str| format!("__bid_{operation}{mode}2");
124    let answer = Type::int(NARROW);
125    let single = match pred {
126        FloatPred::Oeq => Some(("eq", IntPred::Eq)),
127        FloatPred::Une => Some(("ne", IntPred::Ne)),
128        FloatPred::Olt => Some(("lt", IntPred::Slt)),
129        FloatPred::Ole => Some(("le", IntPred::Sle)),
130        FloatPred::Ogt => Some(("gt", IntPred::Sgt)),
131        FloatPred::Oge => Some(("ge", IntPred::Sge)),
132        FloatPred::Uno => Some(("unord", IntPred::Ne)),
133        FloatPred::Ord => Some(("unord", IntPred::Eq)),
134        FloatPred::Ult => Some(("ge", IntPred::Slt)),
135        FloatPred::Ule => Some(("gt", IntPred::Sle)),
136        FloatPred::Ugt => Some(("le", IntPred::Sgt)),
137        FloatPred::Uge => Some(("lt", IntPred::Sge)),
138        _ => None,
139    };
140    if let Some((operation, test)) = single {
141        let got = call(func, names, abi, inst, &named(operation), &[a, b], answer);
142        let zero = ahead_const(func, inst, Imm::int(0, answer), answer);
143        becomes(func, inst, Opcode::ICmp, Extra::IntPred(test), &[got, zero]);
144        return;
145    }
146    if let FloatPred::False | FloatPred::True = pred {
147        let bits = i128::from(pred == FloatPred::True);
148        let extra = Extra::Imm(func.add_imm(Imm::int(bits, Type::I1)));
149        becomes(func, inst, Opcode::IConst, extra, &[]);
150        return;
151    }
152    let (FloatPred::One | FloatPred::Ueq) = pred else { return };
153    let mut tested = |operation: &str, test: IntPred| {
154        let got = call(func, names, abi, inst, &named(operation), &[a, b], answer);
155        let zero = ahead_const(func, inst, Imm::int(0, answer), answer);
156        let args = func.push_values(&[got, zero]);
157        let extra = Extra::IntPred(test);
158        written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
159    };
160    let ordered = tested("unord", IntPred::Eq);
161    let different = tested("ne", IntPred::Ne);
162    let (opcode, args) = if pred == FloatPred::One {
163        (Opcode::And, [ordered, different])
164    } else {
165        let unordered = flipped(func, inst, ordered);
166        let same = flipped(func, inst, different);
167        (Opcode::Or, [unordered, same])
168    };
169    becomes(func, inst, opcode, Extra::None, &args);
170}
171
172/// A conversion between two float formats where at least one of them is a decimal.
173///
174/// The IR's opcode is not what picks the routine's name, [`across`] is, because libgcc does not
175/// name these by which way the width goes. Between two decimals it does, with a `2` on the end.
176fn convert(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
177    let Some(ty) = produced(func, inst) else { return };
178    let Some(&arg) = func[func[inst].args].first() else { return };
179    let from = func[arg].ty;
180    if decimal(ty).is_none() && decimal(from).is_none() {
181        return;
182    }
183    let (Some(into), Some(out)) = (letters(ty), letters(from)) else { return };
184    let way = if across(from, ty) { "extend" } else { "trunc" };
185    let tail = if decimal(ty).is_some() && decimal(from).is_some() { "2" } else { "" };
186    into_call(func, names, abi, inst, &format!("__bid_{way}{out}{into}{tail}"), &[arg]);
187}
188
189/// Whether libgcc calls the conversion from one format to the other an `extend`.
190///
191/// It is the wider of the two answers when the widths differ, and at one width it is the conversion
192/// into the decimal, so `__bid_extenddfdd` and `__bid_truncdddf` and `__bid_extendtftd` and
193/// `__bid_trunctdtf`. The eighty bit format counts its eighty bits here, which is why `_Decimal64`
194/// from it is `__bid_truncxfdd` and `_Decimal128` from it is `__bid_extendxftd`.
195fn across(from: Type, into: Type) -> bool {
196    let (a, b) = (into.bits(), from.bits());
197    a > b || (a == b && decimal(into).is_some())
198}
199
200/// The width of the routine that serves an integer of this width.
201///
202/// libgcc has one at thirty two, sixty four and a hundred and twenty eight bits, signed and unsigned,
203/// so anything narrower than thirty two goes through that one. A `__int128` is passed whole here,
204/// because this pass runs above [`crate::wide`] and that step splits a call's operand the way it
205/// splits any other.
206fn holder(bits: u32) -> Option<(u32, &'static str)> {
207    match bits {
208        0..=32 => Some((32, "si")),
209        33..=64 => Some((64, "di")),
210        65..=128 => Some((128, "ti")),
211        _ => None,
212    }
213}
214
215/// An integer becoming a decimal, which is a widening to a width there is a routine at and then it.
216fn from_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
217    let Some(ty) = produced(func, inst) else { return };
218    let Some(&arg) = func[func[inst].args].first() else { return };
219    let from = func[arg].ty;
220    let Some(mode) = decimal(ty) else { return };
221    if !from.is_int() || !from.is_scalar() {
222        return;
223    }
224    let Some((width, letters)) = holder(from.bits()) else { return };
225    let signed = func[inst].opcode == Opcode::SIToFP;
226    let value = if from.bits() == width {
227        arg
228    } else {
229        let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
230        let args = func.push_values(&[arg]);
231        written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
232    };
233    let kind = if signed { "float" } else { "floatuns" };
234    into_call(func, names, abi, inst, &format!("__bid_{kind}{letters}{mode}"), &[value]);
235}
236
237/// A decimal becoming an integer, which is the routine at a width there is one at and then a
238/// truncation to the width the program asked for, the same way [`crate::quad`] does it.
239fn to_integer(func: &mut Func, names: &mut Interner, abi: &'static AbiDescription, inst: Inst) {
240    let Some(ty) = produced(func, inst) else { return };
241    let Some(&arg) = func[func[inst].args].first() else { return };
242    let Some(mode) = decimal(func[arg].ty) else { return };
243    if !ty.is_int() || !ty.is_scalar() {
244        return;
245    }
246    let Some((width, letters)) = holder(ty.bits()) else { return };
247    let kind = if func[inst].opcode == Opcode::FPToSI { "fix" } else { "fixuns" };
248    let routine = format!("__bid_{kind}{mode}{letters}");
249    if ty.bits() == width {
250        into_call(func, names, abi, inst, &routine, &[arg]);
251        return;
252    }
253    let answer = call(func, names, abi, inst, &routine, &[arg], Type::int(width));
254    becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
255}
256
257#[cfg(test)]
258mod tests {
259    use rucc_base::Interner;
260    use rucc_ir::{Block, Builder, Flags, Module, Signature, Value};
261    use rucc_target::{Arch, Env, Os, TargetInfo, Triple, x86_64};
262
263    use super::{Float, FloatPred, Func, Opcode, Type, calls};
264
265    fn target() -> TargetInfo {
266        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
267    }
268
269    fn printed(func: &Func, names: &mut Interner) -> String {
270        let module = Module::new(names.intern("d.c"), &target());
271        rucc_ir::print_func(&module, func, names)
272    }
273
274    fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
275        let signature = Signature::new().with_params(params).with_returns(returns);
276        let mut func = Func::new(names.intern("f"), signature);
277        let entry = func.create_block();
278        let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
279        (func, entry, values)
280    }
281
282    /// One instruction over the parameters, run through the pass and printed.
283    fn after(
284        params: &[Type],
285        returns: Type,
286        make: impl FnOnce(&mut Builder<'_>, &[Value]) -> Value,
287    ) -> String {
288        let mut names = Interner::new();
289        let (mut func, entry, args) = shell(&mut names, params, &[returns]);
290        let mut build = Builder::new(&mut func, entry);
291        let made = make(&mut build, &args);
292        build.ret(&[made]);
293        calls(&mut func, &mut names, x86_64::SYSV.abi);
294        printed(&func, &mut names)
295    }
296
297    #[test]
298    fn arithmetic_at_each_width_is_the_routine_with_that_widths_letters() {
299        for (format, name) in [
300            (Float::D32, "__bid_addsd3"),
301            (Float::D64, "__bid_adddd3"),
302            (Float::D128, "__bid_addtd3"),
303        ] {
304            let ty = Type::float(format);
305            let text = after(&[ty, ty], ty, |build, args| {
306                build.binary(Opcode::FAdd, args[0], args[1], Flags::NONE)
307            });
308            assert!(text.contains(name), "{text}");
309            assert!(!text.contains("fadd"), "{text}");
310        }
311    }
312
313    #[test]
314    fn a_binary_float_is_left_for_the_steps_after_this_one() {
315        let ty = Type::float(Float::F64);
316        let text = after(&[ty, ty], ty, |build, args| {
317            build.binary(Opcode::FMul, args[0], args[1], Flags::NONE)
318        });
319        assert!(text.contains("fmul"), "{text}");
320        assert!(!text.contains("__bid"), "{text}");
321    }
322
323    #[test]
324    fn a_comparison_is_the_routine_and_a_test_of_its_answer() {
325        let ty = Type::float(Float::D64);
326        let text = after(&[ty, ty], Type::I1, |build, args| {
327            build.fcmp(FloatPred::Olt, args[0], args[1], Flags::NONE)
328        });
329        assert!(text.contains("__bid_ltdd2"), "{text}");
330        assert!(text.contains("icmp slt"), "{text}");
331        let text = after(&[ty, ty], Type::I1, |build, args| {
332            build.fcmp(FloatPred::One, args[0], args[1], Flags::NONE)
333        });
334        assert!(text.contains("__bid_unorddd2") && text.contains("__bid_nedd2"), "{text}");
335    }
336
337    #[test]
338    fn a_conversion_is_named_the_way_libgcc_names_it() {
339        let rows = [
340            (Float::D32, Float::D64, "__bid_extendsddd2"),
341            (Float::D128, Float::D64, "__bid_trunctddd2"),
342            (Float::F64, Float::D64, "__bid_extenddfdd"),
343            (Float::D64, Float::F64, "__bid_truncdddf"),
344            (Float::F80, Float::D64, "__bid_truncxfdd"),
345            (Float::F80, Float::D128, "__bid_extendxftd"),
346            (Float::D32, Float::F128, "__bid_extendsdtf"),
347        ];
348        for (from, into, name) in rows {
349            let (from, into) = (Type::float(from), Type::float(into));
350            let opcode = if super::across(from, into) { Opcode::FPExt } else { Opcode::FPTrunc };
351            let text = after(&[from], into, |build, args| build.unary(opcode, args[0], into));
352            assert!(text.contains(name), "{name}: {text}");
353        }
354    }
355
356    #[test]
357    fn an_integer_conversion_goes_through_a_width_there_is_a_routine_at() {
358        let ty = Type::float(Float::D64);
359        let text =
360            after(&[Type::int(16)], ty, |build, args| build.unary(Opcode::SIToFP, args[0], ty));
361        assert!(text.contains("sext") && text.contains("__bid_floatsidd"), "{text}");
362        let text = after(&[ty], Type::int(8), |build, args| {
363            build.unary(Opcode::FPToUI, args[0], Type::int(8))
364        });
365        assert!(text.contains("__bid_fixunsddsi") && text.contains("trunc"), "{text}");
366        let text =
367            after(&[Type::int(128)], ty, |build, args| build.unary(Opcode::UIToFP, args[0], ty));
368        assert!(text.contains("__bid_floatunstidd"), "{text}");
369    }
370}