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