Skip to main content

rucc_codegen/
quad.rs

1//! The float that is wider than any instruction, as the calls that do the work.
2//!
3//! `_Float128` is the one arithmetic type a C program writes that no machine computes with.
4//! Sixteen bytes fit in a vector register, so moving one, passing one and returning one are
5//! instructions this back end has, and `spec/10-backend.md` section 10.8's rule set is written at
6//! this width for exactly those three. Everything else is a call: no processor anyone compiles for
7//! has an instruction that adds two binary128 values, which is why `spec/12-abi-and-runtime.md`
8//! section 12.8 puts the whole format in the runtime rather than in the part of the runtime that
9//! exists for targets with no floating point unit. This pass is what turns the operation the
10//! program wrote into the call the routine is.
11//!
12//! After it there is no arithmetic, no comparison and no conversion at this format left in the
13//! function, which is what lets the selector go on being a table over widths the machine has. What
14//! is left at the width is the three things a rule is written for, and a call is one of them in the
15//! sense that matters: the value travels in the register the convention names.
16//!
17//! # Why a pass and not a rule
18//!
19//! The same reason [`crate::wide`] is a pass. A rule rewrites a term into instructions of the
20//! machine, and there is no instruction here to rewrite into, so there is nothing for a rule to
21//! produce. A call is not a rewrite of a term either, because what it needs first is the
22//! convention's answer about where each operand goes, which is `crate::abi`'s and not a rule's.
23//!
24//! # The names are libgcc's, and the spelling is a target fact
25//!
26//! `__addtf3` and the rest, which is what `runtime/builtins/quad.c` defines and what libgcc defines
27//! beside it, so a call this pass writes links against either. The `tf` in the name means binary128
28//! on every target this compiler has a back end for, and on ppc64 it does not: there `long double`
29//! is a pair of doubles, `__addtf3` is the arithmetic on that pair, and binary128 is spelled `kf`.
30//! So the table below is right for the back ends that exist and is the first thing to look at when
31//! a ppc64 one does, which is `tamnd/rucc#618`'s row rather than this pass's business today.
32//!
33//! # What is left alone
34//!
35//! An operation at this format whose routine is not in the archive is left exactly as it was and
36//! refused below by name, the same way [`crate::wide`] leaves a conversion at eighty bits alone.
37//! That is a conversion against a `_Float16` or an eighty bit float, and a `select` of two quads,
38//! which is a move the rule set has no conditional form of. A refusal naming the instruction is the
39//! outcome every one of those had before this pass existed and it is still the right one: the
40//! alternative is a call to a routine no archive defines, which is a link that fails further from
41//! the cause.
42//!
43//! A conversion against a `__int128` is not in that list and is not this pass's work either.
44//! [`crate::wide`] runs above here and turns one into a call to `__floattitf`, `__floatuntitf`,
45//! `__fixtfti` or `__fixunstfti`, with the integer as the pair of words the convention passes it in,
46//! so by the time this pass looks there is nothing at that width left to refuse.
47
48use rucc_base::Interner;
49use rucc_ir::{
50    CallInfo, Extra, Flags, Float, FloatPred, Func, Imm, Inst, InstData, IntPred, MemInfo,
51    MemOrder, Opcode, Restrict, Signature, Type, Value,
52};
53
54/// The format this pass is about.
55const QUAD: Float = Float::F128;
56
57/// How wide it is, in bits and then in bytes.
58const BITS: u32 = 128;
59
60/// The two widths the runtime has an integer conversion at, which are the two a C program on a
61/// machine with sixty four bit registers has integers of.
62const NARROW: u32 = 32;
63const WORD: u32 = 64;
64
65/// Rewrites every operation at this format into the call that performs it.
66///
67/// The instructions are collected before any of them is touched, because a rewrite puts
68/// instructions in front of the one it replaces and the walk would otherwise see its own work.
69pub fn calls(func: &mut Func, names: &mut Interner) {
70    let found: Vec<Inst> =
71        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
72    for inst in found {
73        match func[inst].opcode {
74            Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv => {
75                arithmetic(func, names, inst);
76            }
77            Opcode::FNeg => negate(func, names, inst),
78            Opcode::FCmp => compare(func, names, inst),
79            Opcode::FConst => constant(func, inst),
80            Opcode::FPExt => widen(func, names, inst),
81            Opcode::FPTrunc => narrow(func, names, inst),
82            Opcode::SIToFP | Opcode::UIToFP => from_integer(func, names, inst),
83            Opcode::FPToSI | Opcode::FPToUI => to_integer(func, names, inst),
84            _ => {}
85        }
86    }
87}
88
89/// Whether this type is the format.
90fn quad(ty: Type) -> bool {
91    ty.is_scalar() && ty.format() == Some(QUAD)
92}
93
94/// The type of an instruction's first result, or nothing where it has none.
95fn produced(func: &Func, inst: Inst) -> Option<Type> {
96    func[inst].first_result.map(|value| func[value].ty)
97}
98
99/// The four operations, each of them the routine of its name over the two operands.
100///
101/// The call goes in place of the instruction rather than in front of it, so the value the rest of
102/// the function reads is the value it already read and nothing has to be substituted anywhere.
103/// That works here and not in [`crate::wide`] because the answer is one value of the same type:
104/// nothing about this format is split, it simply is not computed.
105fn arithmetic(func: &mut Func, names: &mut Interner, inst: Inst) {
106    let Some(ty) = produced(func, inst) else { return };
107    if !quad(ty) {
108        return;
109    }
110    let args = func[func[inst].args].to_vec();
111    let [a, b] = args[..] else { return };
112    let routine = match func[inst].opcode {
113        Opcode::FAdd => "__addtf3",
114        Opcode::FSub => "__subtf3",
115        Opcode::FMul => "__multf3",
116        Opcode::FDiv => "__divtf3",
117        _ => return,
118    };
119    into_call(func, names, inst, routine, &[a, b]);
120}
121
122/// The negation, which is a call here and an exclusive or at the two narrower formats.
123///
124/// [`crate::expand::floats`] flips the sign bit of a narrower float in a general purpose register,
125/// and the exchange that makes that worth doing runs out at this width twice over: the integer that
126/// would hold the bits has no register either, and the mask would want a constant pool that nothing
127/// else in this back end needs. libgcc has the routine, so the routine is what this is.
128fn negate(func: &mut Func, names: &mut Interner, inst: Inst) {
129    let Some(ty) = produced(func, inst) else { return };
130    let Some(&arg) = func[func[inst].args].first() else { return };
131    if !quad(ty) {
132        return;
133    }
134    into_call(func, names, inst, "__negtf2", &[arg]);
135}
136
137/// A comparison, as the call that answers it and the test of that answer against zero.
138///
139/// Only the sign of what these routines hand back is specified, never its magnitude, so the caller
140/// compares against zero and the predicate it compares with is the one the name promised. Six of
141/// the sixteen predicates are a routine each, six more are one of those six read the other way
142/// round, and two need both calls.
143///
144/// The six that are one call are the ordered comparisons, because the number a routine answers for
145/// a not a number is the one that makes its own test come out false. So `__lttf2` is below zero for
146/// `a < b` and above zero for a not a number, and a test for below zero is then ordered and less
147/// than and nothing else. Reading that same answer as at or above zero is unordered or greater than
148/// or equal, which is the negation, and that is where the other six come from: `ult` is not `oge`,
149/// `ule` is not `ogt`, and so on down.
150///
151/// `one` and `ueq` are the two that are not a reading of one answer. Ordered and not equal is
152/// neither operand a not a number and the two of them different, and there is no single routine for
153/// it, so it is `__unordtf2` saying ordered and `__netf2` saying different. Unordered or equal is
154/// the negation of that and is the same two calls with the other connective. gcc emits the pair for
155/// them too. Neither is a shape C's operators produce, since `!(a == b)` is unordered or not equal
156/// and not this, but the optimizer may fold its way to one and the back end has to have an answer.
157fn compare(func: &mut Func, names: &mut Interner, inst: Inst) {
158    let args = func[func[inst].args].to_vec();
159    let [a, b] = args[..] else { return };
160    if !quad(func[a].ty) || !quad(func[b].ty) {
161        return;
162    }
163    let Extra::FloatPred(pred) = func[inst].extra else { return };
164    if let Some((routine, test)) = single(pred) {
165        let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
166        let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
167        let extra = Extra::IntPred(test);
168        becomes(func, inst, Opcode::ICmp, extra, &[answer, zero]);
169        return;
170    }
171    // Always false and always true are constants rather than comparisons, and they are here rather
172    // than left alone because a rule at this format would be a rule about an operand it never
173    // reads.
174    if let FloatPred::False | FloatPred::True = pred {
175        let bits = u128::from(pred == FloatPred::True);
176        let extra = Extra::Imm(func.add_imm(Imm::int(bits as i128, Type::I1)));
177        becomes(func, inst, Opcode::IConst, extra, &[]);
178        return;
179    }
180    let (FloatPred::One | FloatPred::Ueq) = pred else { return };
181    let ordered = pair(func, names, inst, "__unordtf2", a, b, IntPred::Eq);
182    let different = pair(func, names, inst, "__netf2", a, b, IntPred::Ne);
183    // Ordered and different, or the negation of it, which by De Morgan is unordered or the same.
184    let (opcode, args) = if pred == FloatPred::One {
185        (Opcode::And, [ordered, different])
186    } else {
187        let unordered = flipped(func, inst, ordered);
188        let same = flipped(func, inst, different);
189        (Opcode::Or, [unordered, same])
190    };
191    becomes(func, inst, opcode, Extra::None, &args);
192}
193
194/// The routine for a predicate that is one call, and the test its answer is read with.
195fn single(pred: FloatPred) -> Option<(&'static str, IntPred)> {
196    Some(match pred {
197        FloatPred::Oeq => ("__eqtf2", IntPred::Eq),
198        FloatPred::Une => ("__netf2", IntPred::Ne),
199        FloatPred::Olt => ("__lttf2", IntPred::Slt),
200        FloatPred::Ole => ("__letf2", IntPred::Sle),
201        FloatPred::Ogt => ("__gttf2", IntPred::Sgt),
202        FloatPred::Oge => ("__getf2", IntPred::Sge),
203        FloatPred::Uno => ("__unordtf2", IntPred::Ne),
204        FloatPred::Ord => ("__unordtf2", IntPred::Eq),
205        // The four that are one of the ordered answers read as its negation.
206        FloatPred::Ult => ("__getf2", IntPred::Slt),
207        FloatPred::Ule => ("__gttf2", IntPred::Sle),
208        FloatPred::Ugt => ("__letf2", IntPred::Sgt),
209        FloatPred::Uge => ("__lttf2", IntPred::Sge),
210        _ => return None,
211    })
212}
213
214/// One of the two calls a `one` or a `ueq` is made of, and its answer tested against zero.
215fn pair(
216    func: &mut Func,
217    names: &mut Interner,
218    inst: Inst,
219    routine: &str,
220    a: Value,
221    b: Value,
222    test: IntPred,
223) -> Value {
224    let answer = call(func, names, inst, routine, &[a, b], Type::int(NARROW));
225    let zero = ahead_const(func, inst, Imm::int(0, Type::int(NARROW)), Type::int(NARROW));
226    let args = func.push_values(&[answer, zero]);
227    let extra = Extra::IntPred(test);
228    written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, Type::I1)
229}
230
231/// A truth value with the other answer, which is an exclusive or with one.
232fn flipped(func: &mut Func, inst: Inst, value: Value) -> Value {
233    let one = ahead_const(func, inst, Imm::int(1, Type::I1), Type::I1);
234    let args = func.push_values(&[value, one]);
235    written(func, inst, InstData { args, ..InstData::new(Opcode::Xor) }, Type::I1)
236}
237
238/// A constant, as the bits written into a frame slot and read back at this format.
239///
240/// Every other constant in this back end is an immediate in an instruction, and this one cannot be:
241/// no instruction carries sixteen bytes of immediate, the integer that would spell the bits has no
242/// register either, and the constant pool a literal would otherwise go in is a section this back
243/// end does not have yet. So the bits go where the value lives, which for a value this back end
244/// has no other home for is the frame, and the read back is the whole register move the rule set
245/// already has at this width.
246///
247/// The low word goes at the lower address, which is this machine's order and is the same
248/// assumption [`crate::wide`] makes about the halves of an integer this wide. A back end for a big
249/// endian target is where that becomes a question, and it is the same question in both passes.
250///
251/// The slot is a fixed size `alloca`, so it is one slot in the frame however many times control
252/// reaches it, and a constant inside a loop costs two stores a time round rather than anything that
253/// grows.
254fn constant(func: &mut Func, inst: Inst) {
255    let Some(ty) = produced(func, inst) else { return };
256    let Extra::Imm(imm) = func[inst].extra else { return };
257    if !quad(ty) {
258        return;
259    }
260    let bits = func[imm].bits();
261    let bytes = u64::from(BITS / 8);
262    let whole = MemInfo {
263        size: bytes,
264        align: BITS / 8,
265        order: MemOrder::NotAtomic,
266        tbaa: None,
267        owns: 0,
268        restrict: Restrict::NONE,
269    };
270    let slot = {
271        let extra = Extra::Mem(func.add_mem(whole));
272        written(func, inst, InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
273    };
274    let half = u64::from(WORD / 8);
275    let word = Type::int(WORD);
276    let low = ahead_const(func, inst, Imm::int(bits as i128, word), word);
277    write(func, inst, low, slot, MemInfo { size: half, ..whole });
278    let step = ahead_const(func, inst, Imm::int(half as i128, word), word);
279    let args = func.push_values(&[slot, step]);
280    let above = written(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
281    let high = ahead_const(func, inst, Imm::int((bits >> WORD) as i128, word), word);
282    write(func, inst, high, above, MemInfo { size: half, align: WORD / 8, ..whole });
283    let extra = Extra::Mem(func.add_mem(whole));
284    becomes(func, inst, Opcode::Load, extra, &[slot]);
285}
286
287/// A narrower float becoming a quad, which is one of two routines and never rounds.
288///
289/// Only from the two formats the runtime has a routine from. A `_Float16` widening straight to this
290/// format is not one of them and neither is the eighty bit format, which this machine has no
291/// register for anyway, and both are left alone and refused below.
292fn widen(func: &mut Func, names: &mut Interner, inst: Inst) {
293    let Some(ty) = produced(func, inst) else { return };
294    let Some(&arg) = func[func[inst].args].first() else { return };
295    if !quad(ty) {
296        return;
297    }
298    let routine = match func[arg].ty.format() {
299        Some(Float::F32) => "__extendsftf2",
300        Some(Float::F64) => "__extenddftf2",
301        _ => return,
302    };
303    into_call(func, names, inst, routine, &[arg]);
304}
305
306/// A quad becoming a narrower float, which is the other direction of the same pair and rounds.
307fn narrow(func: &mut Func, names: &mut Interner, inst: Inst) {
308    let Some(ty) = produced(func, inst) else { return };
309    let Some(&arg) = func[func[inst].args].first() else { return };
310    if !quad(func[arg].ty) {
311        return;
312    }
313    let routine = match ty.format() {
314        Some(Float::F32) => "__trunctfsf2",
315        Some(Float::F64) => "__trunctfdf2",
316        _ => return,
317    };
318    into_call(func, names, inst, routine, &[arg]);
319}
320
321/// An integer becoming a quad, which is a widening to a width the runtime has a routine at and then
322/// that routine.
323///
324/// The runtime has four, a signed and an unsigned integer at thirty two bits and at sixty four, so
325/// an integer narrower than that is widened first, with the sign for a signed one and with zeroes
326/// for an unsigned one. That is the same move [`crate::expand::floats`] makes in front of the
327/// machine's own conversion and for the same reason: after the widening the value is the same
328/// number at a width there is a conversion from.
329///
330/// Nothing rounds in any of the four, which is the property `spec/12-abi-and-runtime.md` section
331/// 12.8 measures rather than assumes, so a program that widens an integer through this format and
332/// back has the integer it started with.
333///
334/// A `__int128` never gets this far. [`crate::wide`] has already turned a conversion at that width
335/// into a call of its own, to the one routine in this family whose answer is not exact: a hundred
336/// and thirteen significant bits hold every integer the four below deal in and do not hold every
337/// value of a `__int128`, so that one rounds and these four do not.
338fn from_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
339    let Some(ty) = produced(func, inst) else { return };
340    let Some(&arg) = func[func[inst].args].first() else { return };
341    let from = func[arg].ty;
342    if !quad(ty) || !from.is_int() || !from.is_scalar() {
343        return;
344    }
345    let signed = func[inst].opcode == Opcode::SIToFP;
346    let Some(width) = holder(from.bits()) else { return };
347    let routine = match (signed, width) {
348        (true, NARROW) => "__floatsitf",
349        (false, NARROW) => "__floatunsitf",
350        (true, _) => "__floatditf",
351        (false, _) => "__floatunditf",
352    };
353    let value = if from.bits() == width {
354        arg
355    } else {
356        let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
357        let args = func.push_values(&[arg]);
358        written(func, inst, InstData { args, ..InstData::new(opcode) }, Type::int(width))
359    };
360    into_call(func, names, inst, routine, &[value]);
361}
362
363/// A quad becoming an integer, which is the routine at a width the runtime has one at and then a
364/// truncation to the width the program asked for.
365///
366/// The four routines answer an `int`, an `unsigned int`, a `long long` and an `unsigned long long`,
367/// so a narrower answer is the thirty two bit routine and a truncation. Nothing is lost by that:
368/// the value has to fit the type the program named or the conversion is undefined, and a value that
369/// fits is a value the truncation leaves alone.
370///
371/// The four cases C leaves undefined, which are a value too large, a value too small, an infinity
372/// and a not a number, answer zero in the routine. Section 12.8 records that as a convention the
373/// differential can hold both implementations to rather than as a promise a program may read, and
374/// nothing here makes it one: no test goes in front of the call, the same way nothing tests a
375/// divisor for zero in front of `__divti3`.
376fn to_integer(func: &mut Func, names: &mut Interner, inst: Inst) {
377    let Some(ty) = produced(func, inst) else { return };
378    let Some(&arg) = func[func[inst].args].first() else { return };
379    if !quad(func[arg].ty) || !ty.is_int() || !ty.is_scalar() {
380        return;
381    }
382    let signed = func[inst].opcode == Opcode::FPToSI;
383    let Some(width) = holder(ty.bits()) else { return };
384    let routine = match (signed, width) {
385        (true, NARROW) => "__fixtfsi",
386        (false, NARROW) => "__fixunstfsi",
387        (true, _) => "__fixtfdi",
388        (false, _) => "__fixunstfdi",
389    };
390    if ty.bits() == width {
391        into_call(func, names, inst, routine, &[arg]);
392        return;
393    }
394    let answer = call(func, names, inst, routine, &[arg], Type::int(width));
395    becomes(func, inst, Opcode::Trunc, Extra::None, &[answer]);
396}
397
398/// The width of the routine that serves an integer of this width, where one does.
399///
400/// A width at or below thirty two is served by the thirty two bit routine and one above it by the
401/// sixty four bit routine, and a hundred and twenty eight is served by nothing here because nothing
402/// at that width arrives: [`crate::wide`] has written its call already by then. Every width
403/// reaching this pass is one of the machine's own, because [`crate::widths`] has already rounded an
404/// integer of forty bits up into one of sixty four, so the only widths this sees are one, eight,
405/// sixteen, thirty two, sixty four and a hundred and twenty eight.
406fn holder(bits: u32) -> Option<u32> {
407    match bits {
408        0..=NARROW => Some(NARROW),
409        33..=WORD => Some(WORD),
410        _ => None,
411    }
412}
413
414/// Turns an instruction into the call that performs it, in place.
415///
416/// In place rather than in front of, because the call produces one value of the type the
417/// instruction already produced, so every reader of it goes on reading the same value. What the
418/// program said about rounding and about not a numbers is dropped, since a call carries none of it
419/// and the routine has its own answers, which are libgcc's.
420fn into_call(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, args: &[Value]) {
421    let Some(ty) = produced(func, inst) else { return };
422    let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
423    let signature = func.add_signature(Signature::new().with_params(&params).with_returns(&[ty]));
424    let callee = Some(names.intern(routine));
425    let varargs = func.push_abis(&[]);
426    let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
427    becomes(func, inst, Opcode::Call, extra, args);
428}
429
430/// A call to a runtime routine written in front of an instruction, and the value it answers.
431fn call(
432    func: &mut Func,
433    names: &mut Interner,
434    inst: Inst,
435    routine: &str,
436    args: &[Value],
437    ty: Type,
438) -> Value {
439    let params: Vec<Type> = args.iter().map(|&value| func[value].ty).collect();
440    let signature = func.add_signature(Signature::new().with_params(&params).with_returns(&[ty]));
441    let callee = Some(names.intern(routine));
442    let varargs = func.push_abis(&[]);
443    let extra = Extra::Call(func.add_call(CallInfo { callee, signature, varargs }));
444    let args = func.push_values(args);
445    written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Call) }, ty)
446}
447
448/// A constant put in front of an instruction.
449fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
450    let extra = Extra::Imm(func.add_imm(imm));
451    written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
452}
453
454/// A store put in front of an instruction, which produces nothing and is only its effect.
455fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
456    let span = func.span(inst);
457    let extra = Extra::Mem(func.add_mem(info));
458    let args = func.push_values(&[value, into]);
459    let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
460    let made = func.create_inst(data, &[], span);
461    func.insert_before(made, inst);
462}
463
464/// Creates an instruction, puts it in front of another one, and reads its value back out.
465fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
466    let span = func.span(inst);
467    let made = func.create_inst(data, &[ty], span);
468    func.insert_before(made, inst);
469    func[made].first_result.expect("an instruction created with one result has one")
470}
471
472/// Turns an instruction into a different one over different operands, in place.
473fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) {
474    let args = func.push_values(args);
475    let data = &mut func[inst];
476    data.opcode = opcode;
477    data.args = args;
478    data.extra = extra;
479    data.flags = data.flags.intersection(Flags::legal_on(opcode));
480}
481
482#[cfg(test)]
483mod tests {
484    use rucc_base::Interner;
485    use rucc_ir::{Block, Builder, Module, Signature};
486    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
487
488    use super::{BITS, Flags, Float, FloatPred, Func, Opcode, Type, Value, calls};
489
490    /// The format the pass is about, as a type, which is what every test builds with.
491    fn quad() -> Type {
492        Type::float(Float::F128)
493    }
494
495    fn target() -> TargetInfo {
496        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
497    }
498
499    fn printed(func: &Func, names: &mut Interner) -> String {
500        let module = Module::new(names.intern("q.c"), &target());
501        rucc_ir::print_func(&module, func, names)
502    }
503
504    /// A function of those parameters returning that, with its entry block and its parameters.
505    fn shell(names: &mut Interner, params: &[Type], returns: &[Type]) -> (Func, Block, Vec<Value>) {
506        let signature = Signature::new().with_params(params).with_returns(returns);
507        let mut func = Func::new(names.intern("f"), signature);
508        let entry = func.create_block();
509        let values = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
510        (func, entry, values)
511    }
512
513    /// The pass run over a function of two quads whose one instruction is that binary operation.
514    fn binary(opcode: Opcode) -> String {
515        let mut names = Interner::new();
516        let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[quad()]);
517        let mut build = Builder::new(&mut func, entry);
518        let answer = build.binary(opcode, params[0], params[1], Flags::NONE);
519        build.ret(&[answer]);
520        calls(&mut func, &mut names);
521        printed(&func, &mut names)
522    }
523
524    /// The pass run over a function of two quads whose one instruction is that comparison.
525    fn compared(pred: FloatPred) -> String {
526        let mut names = Interner::new();
527        let (mut func, entry, params) = shell(&mut names, &[quad(), quad()], &[Type::I1]);
528        let mut build = Builder::new(&mut func, entry);
529        let answer = build.fcmp(pred, params[0], params[1], Flags::NONE);
530        build.ret(&[answer]);
531        calls(&mut func, &mut names);
532        printed(&func, &mut names)
533    }
534
535    #[test]
536    fn the_four_operations_are_the_four_routines() {
537        for (opcode, routine) in [
538            (Opcode::FAdd, "__addtf3"),
539            (Opcode::FSub, "__subtf3"),
540            (Opcode::FMul, "__multf3"),
541            (Opcode::FDiv, "__divtf3"),
542        ] {
543            let text = binary(opcode);
544            assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
545            // The arithmetic is gone rather than sitting beside the call, which is the whole point:
546            // the selector has no rule to match it with.
547            assert_eq!(text.matches(" = f").count(), 0, "no float arithmetic left: {text}");
548            assert_eq!(text.matches(" = call").count(), 1, "one call: {text}");
549        }
550    }
551
552    #[test]
553    fn a_negation_is_the_routine_rather_than_a_sign_flip() {
554        let mut names = Interner::new();
555        let (mut func, entry, params) = shell(&mut names, &[quad()], &[quad()]);
556        let mut build = Builder::new(&mut func, entry);
557        let answer = build.unary(Opcode::FNeg, params[0], quad());
558        build.ret(&[answer]);
559        calls(&mut func, &mut names);
560        let text = printed(&func, &mut names);
561        assert!(text.contains("@__negtf2"), "{text}");
562        assert!(!text.contains("xor"), "no sign flip in a register: {text}");
563    }
564
565    /// The six ordered predicates, each the routine of its name and the test its answer is read
566    /// with.
567    #[test]
568    fn an_ordered_comparison_is_its_own_routine_tested_against_zero() {
569        for (pred, routine, test) in [
570            (FloatPred::Oeq, "__eqtf2", "icmp eq"),
571            (FloatPred::Une, "__netf2", "icmp ne"),
572            (FloatPred::Olt, "__lttf2", "icmp slt"),
573            (FloatPred::Ole, "__letf2", "icmp sle"),
574            (FloatPred::Ogt, "__gttf2", "icmp sgt"),
575            (FloatPred::Oge, "__getf2", "icmp sge"),
576        ] {
577            let text = compared(pred);
578            assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
579            assert!(text.contains(test), "{test}: {text}");
580            assert!(!text.contains("fcmp"), "the comparison is gone: {text}");
581        }
582    }
583
584    /// The four that are one of those six answers read as its negation.
585    ///
586    /// The routine is the opposite one and the test is the same one, which is the part worth a test
587    /// of its own: a pass that took the obvious route and kept the routine while flipping the test
588    /// would be wrong only for a not a number, which is the operand nothing in a corpus has.
589    #[test]
590    fn an_unordered_comparison_is_the_opposite_routine_read_the_same_way() {
591        for (pred, routine, test) in [
592            (FloatPred::Ult, "__getf2", "icmp slt"),
593            (FloatPred::Ule, "__gttf2", "icmp sle"),
594            (FloatPred::Ugt, "__letf2", "icmp sgt"),
595            (FloatPred::Uge, "__lttf2", "icmp sge"),
596        ] {
597            let text = compared(pred);
598            assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
599            assert!(text.contains(test), "{test}: {text}");
600        }
601    }
602
603    #[test]
604    fn whether_two_values_can_be_ordered_at_all_is_one_routine_either_way_round() {
605        let unordered = compared(FloatPred::Uno);
606        assert!(unordered.contains("@__unordtf2"), "{unordered}");
607        assert!(unordered.contains("icmp ne"), "{unordered}");
608        let ordered = compared(FloatPred::Ord);
609        assert!(ordered.contains("@__unordtf2"), "{ordered}");
610        assert!(ordered.contains("icmp eq"), "{ordered}");
611    }
612
613    /// Ordered and not equal is the one predicate that needs both calls.
614    #[test]
615    fn ordered_and_different_is_two_calls_joined() {
616        let text = compared(FloatPred::One);
617        assert!(text.contains("@__unordtf2"), "{text}");
618        assert!(text.contains("@__netf2"), "{text}");
619        assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
620        assert_eq!(text.matches(" = and").count(), 1, "joined: {text}");
621        assert!(!text.contains("xor"), "nothing is negated: {text}");
622    }
623
624    /// Unordered or equal is the negation of that, which is the same two calls the other way up.
625    #[test]
626    fn unordered_or_equal_is_the_negation_of_it() {
627        let text = compared(FloatPred::Ueq);
628        assert_eq!(text.matches(" = call").count(), 2, "both calls: {text}");
629        assert_eq!(text.matches(" = or").count(), 1, "joined the other way: {text}");
630        assert_eq!(text.matches(" = xor").count(), 2, "both answers negated: {text}");
631    }
632
633    #[test]
634    fn the_two_comparisons_with_no_operands_to_read_are_constants() {
635        let never = compared(FloatPred::False);
636        assert!(never.contains("iconst.i1 0"), "{never}");
637        assert!(!never.contains("call"), "nothing is called: {never}");
638        // Printed as minus one, because the printer writes an integer signed and the one bit of an
639        // `i1` that is set is its sign bit.
640        let always = compared(FloatPred::True);
641        assert!(always.contains("iconst.i1 -1"), "{always}");
642    }
643
644    /// A constant is the bits written into a slot and read back at the format.
645    #[test]
646    fn a_constant_goes_through_the_frame_a_word_at_a_time() {
647        let mut names = Interner::new();
648        let (mut func, entry, _) = shell(&mut names, &[], &[quad()]);
649        let mut build = Builder::new(&mut func, entry);
650        // One in the low word and one in the high word, so a pass that wrote either word twice or
651        // wrote one of them into the wrong half is a different answer rather than the same zero.
652        let value = build.fconst(quad(), (3u128 << 64) | 5);
653        build.ret(&[value]);
654        calls(&mut func, &mut names);
655        let text = printed(&func, &mut names);
656        assert!(!text.contains("fconst"), "the constant is gone: {text}");
657        assert_eq!(text.matches("alloca").count(), 1, "one slot: {text}");
658        assert_eq!(text.matches("store").count(), 2, "a word at a time: {text}");
659        assert!(text.contains("iconst.i64 5"), "the low word first: {text}");
660        assert!(text.contains("iconst.i64 3"), "the high word above it: {text}");
661        assert_eq!(text.matches("ptr_add").count(), 1, "the high word is eight bytes up: {text}");
662        assert_eq!(text.matches(" = load").count(), 1, "read back as one value: {text}");
663    }
664
665    #[test]
666    fn the_two_narrower_formats_are_a_routine_each_way() {
667        for (from, to, routine) in [
668            (Float::F32, Float::F128, "__extendsftf2"),
669            (Float::F64, Float::F128, "__extenddftf2"),
670            (Float::F128, Float::F32, "__trunctfsf2"),
671            (Float::F128, Float::F64, "__trunctfdf2"),
672        ] {
673            let mut names = Interner::new();
674            let (mut func, entry, params) =
675                shell(&mut names, &[Type::float(from)], &[Type::float(to)]);
676            let mut build = Builder::new(&mut func, entry);
677            let opcode = if to == Float::F128 { Opcode::FPExt } else { Opcode::FPTrunc };
678            let answer = build.unary(opcode, params[0], Type::float(to));
679            build.ret(&[answer]);
680            calls(&mut func, &mut names);
681            let text = printed(&func, &mut names);
682            assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
683        }
684    }
685
686    /// An integer the runtime has no routine at is widened to one it does, with the sign the
687    /// conversion has.
688    #[test]
689    fn a_narrow_integer_is_widened_before_the_conversion() {
690        for (opcode, bits, extend, routine) in [
691            (Opcode::SIToFP, 16, " = sext", "__floatsitf"),
692            (Opcode::UIToFP, 16, " = zext", "__floatunsitf"),
693            (Opcode::SIToFP, 32, "", "__floatsitf"),
694            (Opcode::UIToFP, 64, "", "__floatunditf"),
695        ] {
696            let mut names = Interner::new();
697            let (mut func, entry, params) = shell(&mut names, &[Type::int(bits)], &[quad()]);
698            let mut build = Builder::new(&mut func, entry);
699            let answer = build.unary(opcode, params[0], quad());
700            build.ret(&[answer]);
701            calls(&mut func, &mut names);
702            let text = printed(&func, &mut names);
703            assert!(text.contains(&format!("@{routine}")), "{routine}: {text}");
704            if extend.is_empty() {
705                assert!(!text.contains(" = sext"), "nothing to widen: {text}");
706                assert!(!text.contains(" = zext"), "nothing to widen: {text}");
707            } else {
708                assert!(text.contains(extend), "{extend}: {text}");
709            }
710        }
711    }
712
713    /// Coming down, the answer is truncated to the width the program asked for.
714    #[test]
715    fn a_narrow_answer_is_the_wider_routine_and_a_truncation() {
716        let mut names = Interner::new();
717        let (mut func, entry, params) = shell(&mut names, &[quad()], &[Type::int(16)]);
718        let mut build = Builder::new(&mut func, entry);
719        let answer = build.unary(Opcode::FPToSI, params[0], Type::int(16));
720        build.ret(&[answer]);
721        calls(&mut func, &mut names);
722        let text = printed(&func, &mut names);
723        assert!(text.contains("@__fixtfsi"), "{text}");
724        assert_eq!(text.matches(" = trunc").count(), 1, "cut down afterwards: {text}");
725    }
726
727    #[test]
728    fn a_conversion_against_a_wide_integer_is_left_exactly_as_it_was() {
729        let mut names = Interner::new();
730        let (mut func, entry, params) = shell(&mut names, &[Type::int(BITS)], &[quad()]);
731        let mut build = Builder::new(&mut func, entry);
732        let answer = build.unary(Opcode::SIToFP, params[0], quad());
733        build.ret(&[answer]);
734        calls(&mut func, &mut names);
735        let text = printed(&func, &mut names);
736        assert!(!text.contains("call"), "no routine is called: {text}");
737        assert!(text.contains("sitofp"), "the conversion is still there to be refused: {text}");
738    }
739
740    /// An operation at a format the machine has is not this pass's business.
741    #[test]
742    fn the_narrower_formats_go_past_untouched() {
743        let mut names = Interner::new();
744        let double = Type::float(Float::F64);
745        let (mut func, entry, params) = shell(&mut names, &[double, double], &[double]);
746        let mut build = Builder::new(&mut func, entry);
747        let sum = build.binary(Opcode::FAdd, params[0], params[1], Flags::NONE);
748        let answer = build.fcmp(FloatPred::Olt, sum, params[1], Flags::NONE);
749        build.ret(&[sum]);
750        let _ = answer;
751        calls(&mut func, &mut names);
752        let text = printed(&func, &mut names);
753        assert!(!text.contains("call"), "nothing became a call: {text}");
754        assert!(text.contains("fadd"), "the add is still an add: {text}");
755        assert!(text.contains("fcmp"), "the comparison is still a comparison: {text}");
756    }
757}