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