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