Skip to main content

rucc_codegen/
lowering.rs

1//! The passes that run before selection, as a group with a name and a stated membership.
2//!
3//! Design: `spec/optimizer/36-lowering-and-isel.md` section 36.1.
4//!
5//! Section 36.1 reads the list of passes gcc runs immediately before `pass_expand` and draws one
6//! conclusion from it. Nine of them are lowerings, and each one turns a construct into a shape of
7//! control flow or a shape of arithmetic that the expander would otherwise have to invent. The
8//! expander is the wrong place to invent control flow, because by the time it runs the graph is
9//! being consumed rather than edited. That is spec 10.2's rule arrived at from the other side: a
10//! lowering rule replaces a term with a term and has nowhere to put a block, so any construct whose
11//! lowering is a new shape of control flow is rewritten before selection runs.
12//!
13//! Every one of these passes already existed and every one of them was already called from
14//! `crate::pipeline`, one line at a time, in this order. What did not exist was the thing the
15//! section asks for, which is that they are a group rather than a set of unrelated passes that
16//! happen to run next to each other. The reason gcc's list is nine passes long is that it grew one
17//! pass at a time over three decades, and a group with a written down membership is the thing that
18//! stops the same happening here.
19//!
20//! # The name
21//!
22//! The lowering group, which is what gcc calls its own and is what this module is named after. The
23//! longer and more honest description section 36.1 gives is everything the selector cannot express,
24//! and that is the test for whether something belongs here: not that it is a rewrite of the IR, but
25//! that the thing it rewrites is one no rule in the table can be written for.
26//!
27//! # What is in it
28//!
29//! [`Step::GROUP`], in the order it runs, and that list is the membership. A new lowering is a new
30//! variant of [`Step`] and a new line in that list, which is one place rather than whichever line
31//! of the pipeline looked convenient.
32//!
33//! # What the order is for
34//!
35//! Most of it does not matter and the parts that do are on the variants. The rule behind them is
36//! the same one every time: a pass is written about the constructs the machine has, so anything
37//! that produces a construct somebody below is written about has to run above them. An integer of
38//! forty bits is not a width this machine has, an ordered load is not a load any pass below is
39//! written about, and a quad float is not a float the pass that rewrites floats knows anything of.
40//!
41//! # What it is not
42//!
43//! Not the selector, and not a fixed point. Each step runs once, and a step that produces work for
44//! a step above it would be a bug in this order rather than a reason to run the group twice.
45//!
46//! Not a promise that the construct is gone either, and this is the part worth reading twice. Every
47//! step here has cases it walks away from: a copy too large to be a run of moves, an ordered access
48//! wider than the machine does in one go, a conversion the machine already has an instruction for
49//! and so has no reason to touch. Some of those are the machine having the construct after all and
50//! some of them are a refusal, and a refusal is left standing on purpose, because the selector is
51//! what names the construct it had no rule for and that is a better error than a rewrite that
52//! guessed.
53//!
54//! So what [`Ran`] records is what each step found and what it left, and reading one of those is
55//! how you tell the two apart. What the group promises is only that every construct in the list was
56//! put in front of the step that answers for it, which is the thing that stops being true when
57//! somebody adds a lowering to whichever line of the pipeline looked convenient.
58
59use rucc_base::Interner;
60use rucc_cost::Goal;
61use rucc_ir::{Func, Opcode};
62use rucc_target::CallRegs;
63
64use crate::{expand, half, quad, retry, switch, varargs, wide, widths};
65
66/// One member of the group.
67///
68/// The name of the variant is the name of the construct rather than the name of the function that
69/// takes it out, because the membership is a list of constructs. Which function answers for one is
70/// something this file knows and nothing outside it needs to.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72pub enum Step {
73    /// A `switch`, as the decision tree document 24 describes.
74    Switches,
75    /// A read modify write this machine has no single instruction for, as a loop around the compare
76    /// and exchange.
77    ///
78    /// Beside the switches rather than down with the rest of the rewriting, because both of them
79    /// make blocks and nothing in [`crate::expand`] may.
80    Retries,
81    /// An ordered load or store, as the plain access and a barrier.
82    ///
83    /// Above everything below it, since what an ordered access becomes here is a plain one and
84    /// every pass below is written about a plain one by name. It is also why this is above the
85    /// retries rather than below: the head of the loop they build reads with an ordered load.
86    Orderings,
87    /// An arithmetic operation that also says whether it overflowed, as the arithmetic and the test.
88    ///
89    /// Above the splitting rather than below it, because an overflow check is the one instruction
90    /// whose result is two things and the splitting has no answer for that, while the arithmetic it
91    /// becomes here is adds, multiplies and comparisons the splitting knows already. Nothing is
92    /// lost by running it this early: the widths it is written for are the widths the machine has,
93    /// so a check at any other width is refused by name either way round.
94    Overflows,
95    /// Anything at all at the half float format, as the work at a wider one.
96    ///
97    /// Above the two that rewrite an integer and above the quad, because what it leaves behind is
98    /// a conversion at a wider format and a call, and each of those three is written about one of
99    /// those. A `__int128` becoming a `_Float16` is a conversion to a `double` and a narrowing
100    /// after it once this has run, and the conversion is then the splitting's work in the ordinary
101    /// way rather than a shape it has never seen. A `_Float128` becoming one is a call this writes
102    /// and the quad step never sees, which is what keeps the narrowing a single rounding.
103    HalfFloats,
104    /// An integer wider than a register, as the two halves of one.
105    ///
106    /// Ahead of the width legalisation and not part of it, because the two go in opposite
107    /// directions: an integer of forty bits becomes one of sixty four down there and one of a
108    /// hundred and twenty eight becomes two of sixty four here. Doing this first means a function
109    /// holding both is one the step below still works on.
110    Halves,
111    /// An integer at a width the machine does not have, as the width it is held in.
112    ///
113    /// Before everything after it, because every pass after it is written about widths the machine
114    /// has and an integer of forty bits is not one of them.
115    Widths,
116    /// A byte reversal, as the halving run of swaps it is.
117    Bytes,
118    /// A leading zero, trailing zero or set bit count, as the arithmetic that answers it.
119    Counts,
120    /// Anything at all at the quad float format, as a call to the routine for it.
121    ///
122    /// Above the float rewriting rather than part of it, because the two are written about
123    /// different machines: every rewrite down there ends at an instruction this machine has, and
124    /// every operation up here ends at a call because this machine has no instruction at the format
125    /// at all. Running first means the step below never sees a quad.
126    Quads,
127    /// A float constant, a negation and the conversions, as the integer work spec 10.2 asks for.
128    Floats,
129    /// A `memcpy`, a `memset` or a `memmove`, as the moves it is or as the call it is too big for.
130    Bulk,
131    /// The size of a stack allocation, rounded up to what the stack pointer has to stay on.
132    ///
133    /// The one step here that takes nothing out. It rewrites an operand of the instruction and
134    /// leaves the instruction where it is, which is why [`Step::opcodes`] answers with nothing for
135    /// it.
136    Rounds,
137    /// A variable argument list, as spec 10.7's split describes.
138    Varargs,
139}
140
141impl Step {
142    /// The group, in the order it runs, which is the membership section 36.1 asks to see.
143    pub const GROUP: &'static [Self] = &[
144        Self::Switches,
145        Self::Retries,
146        Self::Orderings,
147        Self::Overflows,
148        Self::HalfFloats,
149        Self::Halves,
150        Self::Widths,
151        Self::Bytes,
152        Self::Counts,
153        Self::Quads,
154        Self::Floats,
155        Self::Bulk,
156        Self::Rounds,
157        Self::Varargs,
158    ];
159
160    /// What it is called in a dump.
161    #[must_use]
162    pub const fn name(self) -> &'static str {
163        match self {
164            Self::Switches => "switches",
165            Self::Retries => "retries",
166            Self::Orderings => "orderings",
167            Self::Overflows => "overflows",
168            Self::HalfFloats => "half-floats",
169            Self::Halves => "halves",
170            Self::Widths => "widths",
171            Self::Bytes => "bytes",
172            Self::Counts => "counts",
173            Self::Quads => "quads",
174            Self::Floats => "floats",
175            Self::Bulk => "bulk",
176            Self::Rounds => "rounds",
177            Self::Varargs => "varargs",
178        }
179    }
180
181    /// The construct it is the answer to, in the words section 36.1 uses for it.
182    #[must_use]
183    pub const fn construct(self) -> &'static str {
184        match self {
185            Self::Switches => "a switch",
186            Self::Retries => "a read modify write with no instruction behind it",
187            Self::Orderings => "an ordered load or store",
188            Self::Overflows => "arithmetic that reports whether it overflowed",
189            Self::HalfFloats => "the half float format",
190            Self::Halves => "an integer wider than a register",
191            Self::Widths => "an integer at a width the machine does not have",
192            Self::Bytes => "a byte reversal",
193            Self::Counts => "a bit count",
194            Self::Quads => "the quad float format",
195            Self::Floats => "a float constant, a negation or a conversion",
196            Self::Bulk => "a bulk copy or fill",
197            Self::Rounds => "a stack allocation whose size is not a multiple of the alignment",
198            Self::Varargs => "a variable argument list",
199        }
200    }
201
202    /// The opcodes it is the answer to, which is what [`Did::found`] and [`Did::left`] count.
203    ///
204    /// Not a promise that none of them survive. Several of these steps have a case they leave where
205    /// it stands, either because the machine turns out to have the construct after all or because
206    /// this is a refusal being handed to the selector to name, and both of those show up here as a
207    /// count that did not reach zero. What the pair of numbers is for is telling somebody reading a
208    /// dump which of those happened.
209    ///
210    /// Empty for [`Step::Rounds`], which rewrites an operand rather than taking an instruction out,
211    /// and empty for the four that work by type rather than by opcode: an integer of forty bits,
212    /// one of a hundred and twenty eight, a quad float and a half float are all spelled with the
213    /// same opcodes as anything else, and what makes them the construct is the type on the values.
214    #[must_use]
215    pub const fn opcodes(self) -> &'static [Opcode] {
216        match self {
217            Self::Switches => &[Opcode::Switch],
218            Self::Retries => &[],
219            Self::Orderings => &[Opcode::AtomicLoad, Opcode::AtomicStore],
220            Self::Overflows => &[
221                Opcode::UAddOverflow,
222                Opcode::SAddOverflow,
223                Opcode::USubOverflow,
224                Opcode::SSubOverflow,
225                Opcode::UMulOverflow,
226                Opcode::SMulOverflow,
227            ],
228            Self::HalfFloats | Self::Halves | Self::Widths | Self::Rounds => &[],
229            Self::Bytes => &[Opcode::Bswap],
230            Self::Counts => &[Opcode::Ctlz, Opcode::Cttz, Opcode::Ctpop],
231            Self::Quads => &[],
232            Self::Floats => &[
233                Opcode::FConst,
234                Opcode::FNeg,
235                Opcode::SIToFP,
236                Opcode::UIToFP,
237                Opcode::FPToSI,
238                Opcode::FPToUI,
239            ],
240            Self::Bulk => &[Opcode::Memcpy, Opcode::Memset, Opcode::Memmove],
241            Self::Varargs => &[Opcode::VaArg, Opcode::VaObject, Opcode::VaCopy, Opcode::VaEnd],
242        }
243    }
244
245    /// Whether this step works on the whole function at once and says whether it rewrote it.
246    ///
247    /// Two of them do. Both retype every value of a width, so either the whole function can be
248    /// rewritten or none of it can, and they answer with a boolean for that reason. A `false` from
249    /// one covers two different things, a function with nothing at that width in it and a function
250    /// holding something the step did not understand, and neither is an error: the second leaves
251    /// the selector to refuse by naming the construct it had no rule for.
252    ///
253    /// Everything else here works instruction by instruction and has nothing to say at that scale,
254    /// which is why [`Did::untouched`] is only ever true for these two.
255    #[must_use]
256    pub const fn whole_function(self) -> bool {
257        matches!(self, Self::Halves | Self::Widths)
258    }
259
260    /// Runs this one step, answering whether it rewrote the function.
261    ///
262    /// Only the two that [`Step::whole_function`] names ever answer `false`, because they are the
263    /// only two that know. The rest work instruction by instruction and are not asked.
264    fn run(self, func: &mut Func, names: &mut Interner, conv: &CallRegs, goal: Goal) -> bool {
265        match self {
266            Self::Switches => switch::switches(func, goal),
267            Self::Retries => retry::loops(func),
268            Self::Orderings => expand::orderings(func, conv.word),
269            Self::Overflows => expand::overflows(func),
270            Self::HalfFloats => half::calls(func, names, conv.abi),
271            Self::Halves => return wide::halves(func, names, conv),
272            Self::Widths => return widths::integers(func),
273            Self::Bytes => expand::bytes(func),
274            Self::Counts => expand::counts(func),
275            Self::Quads => quad::calls(func, names, conv.abi),
276            Self::Floats => expand::floats(func),
277            Self::Bulk => expand::bulk(func, names, conv.word),
278            Self::Rounds => expand::rounds(func, conv.stack_align),
279            Self::Varargs => varargs::lists(func, conv),
280        }
281        true
282    }
283}
284
285/// What one step did to one function.
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub struct Did {
288    /// Which step it was.
289    pub step: Step,
290    /// How many instructions of the kind it answers for were there when it started.
291    pub found: usize,
292    /// How many were still there when it finished, which is not always zero. See [`Step::opcodes`].
293    pub left: usize,
294    /// How many instructions the function had before it ran.
295    pub before: usize,
296    /// How many it had after.
297    pub after: usize,
298    /// Whether it said it left the function exactly as it was, which only the two that
299    /// [`Step::whole_function`] names ever say.
300    pub untouched: bool,
301}
302
303/// What the whole group did to one function.
304#[derive(Debug, Default, Clone, PartialEq, Eq)]
305pub struct Ran {
306    /// One entry per step, in the order they ran, including the ones that found nothing.
307    ///
308    /// Including them on purpose. A dump that lists only the steps that fired is a dump that cannot
309    /// tell a step that found nothing from a step somebody forgot to add to the group.
310    pub did: Vec<Did>,
311}
312
313impl Ran {
314    /// What one step of the group did, which every step has an entry for.
315    ///
316    /// # Panics
317    ///
318    /// Panics if this record did not come from [`group`], since that is the only way a step of
319    /// [`Step::GROUP`] can be missing from it.
320    #[must_use]
321    pub fn of(&self, step: Step) -> Did {
322        *self.did.iter().find(|did| did.step == step).expect("every step has an entry")
323    }
324
325    /// The dump, one line per step.
326    ///
327    /// Plain text with the name first, because the thing anybody reads this for is which step
328    /// changed the function, and a format that has to be parsed to answer that is the wrong format
329    /// for a debugging aid. `-Zlowering=` writes it.
330    #[must_use]
331    pub fn render(&self, func: &str) -> String {
332        use std::fmt::Write;
333
334        let mut out = format!("lowering {func}\n");
335        for did in &self.did {
336            let _ = write!(
337                out,
338                "  {:<10} {:>4} -> {:>4} insts",
339                did.step.name(),
340                did.before,
341                did.after
342            );
343            // Said the rare way round on purpose. The two whole function steps answer `false` for
344            // every function with nothing at their width in it, which is nearly all of them, so a
345            // line per function saying so would bury the one that matters.
346            if did.step.whole_function() && !did.untouched {
347                let _ = write!(out, ", retyped every value at that width");
348            }
349            if did.found > 0 {
350                let _ = write!(out, ", found {}, left {}", did.found, did.left);
351            }
352            let _ = writeln!(out, " ({})", did.step.construct());
353        }
354        out
355    }
356}
357
358/// What the group did to every function a run lowered, in the order they came through.
359///
360/// The same shape [`crate::pressure::Pressure`] has and for the same reason: a caller collects one
361/// of these over a whole command line and asks for the listing once at the end.
362#[derive(Debug, Default, Clone, PartialEq, Eq)]
363pub struct Lowerings {
364    /// One per function, in the order they were lowered.
365    rows: Vec<(String, Ran)>,
366    /// Whether anything is going to read this, which is whether `-Zlowering` was given.
367    wanted: bool,
368}
369
370impl Lowerings {
371    /// Nothing recorded, and nothing counted either.
372    #[must_use]
373    pub fn new() -> Self {
374        Self::default()
375    }
376
377    /// The same, told whether to count, which is what `-Zlowering=FILE` decides.
378    #[must_use]
379    pub fn asked(wanted: bool) -> Self {
380        Self { rows: Vec::new(), wanted }
381    }
382
383    /// Whether the counting is worth doing, which is what [`group`] is passed.
384    ///
385    /// This is a question and not an assumption for a reason that showed up as soon as the numbers
386    /// were measured on something large. Counting is a walk of the function per step, and a
387    /// function's instructions are a linked list, so on the SQLite amalgamation the walks cost
388    /// about two seconds on top of nine, which is more than several of the passes they are
389    /// measuring. A debugging aid nobody asked for should cost nothing, so a run without the flag
390    /// runs the group and records no numbers at all.
391    #[must_use]
392    pub fn wanted(&self) -> bool {
393        self.wanted
394    }
395
396    /// Writes down what the group did to one function.
397    pub fn record(&mut self, name: &str, ran: Ran) {
398        self.rows.push((name.to_owned(), ran));
399    }
400
401    /// Takes in everything another one recorded, which is how one file's answer joins a run's.
402    pub fn merge(&mut self, other: &Self) {
403        self.rows.extend(other.rows.iter().cloned());
404    }
405
406    /// How many functions went through the group.
407    #[must_use]
408    pub fn functions(&self) -> usize {
409        self.rows.len()
410    }
411
412    /// What `-Zlowering=FILE` writes.
413    ///
414    /// A comment holding the count and then one block per function. Whoever reads one of these is
415    /// looking for which step changed a function they are surprised by, so the file is the same
416    /// text in the same order as the group ran, and every step is there whether it did anything or
417    /// not. A dump listing only the steps that fired could not tell a step that found nothing from
418    /// a step somebody forgot to put in the group, which is half of what this is read for.
419    #[must_use]
420    pub fn listing(&self) -> String {
421        let mut out = format!("# rucc lowering: {} functions\n", self.rows.len());
422        for (name, ran) in &self.rows {
423            out.push_str(&ran.render(name));
424        }
425        out
426    }
427}
428
429/// Runs the whole group over one function, in the order [`Step::GROUP`] gives.
430///
431/// This is the entry point section 36.1 asks for. Every caller wanting a function lowered calls
432/// this and nothing else, so adding a lowering is adding it to [`Step::GROUP`] rather than to
433/// whichever line of `crate::pipeline` looked convenient.
434///
435/// `counting` is whether to work out what each step found and left, which is what
436/// [`Lowerings::wanted`] answers and which costs what it says there. The steps run either way and
437/// the function comes out the same; what a `false` gives back is an empty [`Ran`].
438///
439/// `goal` is whether the level asked for small code, which the `switch` lowering reads to decide
440/// when a table is worth writing.
441pub fn group(
442    func: &mut Func,
443    names: &mut Interner,
444    conv: &CallRegs,
445    goal: Goal,
446    counting: bool,
447) -> Ran {
448    let mut ran = Ran::default();
449    for &step in Step::GROUP {
450        if !counting {
451            step.run(func, names, conv, goal);
452            continue;
453        }
454        let (before, found) = tally(func, step);
455        let did = step.run(func, names, conv, goal);
456        let (after, left) = tally(func, step);
457        ran.did.push(Did { step, found, left, before, after, untouched: !did });
458    }
459    ran
460}
461
462/// How many instructions the function has, and how many of them are the kind this step answers for.
463///
464/// Both in one walk rather than one walk each, since the walk is the expensive part.
465fn tally(func: &Func, step: Step) -> (usize, usize) {
466    let wanted = step.opcodes();
467    let (mut all, mut mine) = (0, 0);
468    for block in func.blocks() {
469        for inst in func.insts(block) {
470            all += 1;
471            if wanted.contains(&func[inst].opcode) {
472                mine += 1;
473            }
474        }
475    }
476    (all, mine)
477}
478
479#[cfg(test)]
480mod tests {
481    use rucc_base::Interner;
482    use rucc_ir::{
483        Builder, Extra, Flags, Float, Func, InstData, MemInfo, MemOrder, Opcode, Restrict,
484        Signature, Type, Value,
485    };
486    use rucc_target::x86_64;
487
488    use super::{Goal, Lowerings, Ran, Step, group};
489
490    /// A function with a body somebody else writes, which is the same helper the passes being
491    /// grouped are each tested with.
492    fn one(
493        params: &[Type],
494        returns: &[Type],
495        body: impl FnOnce(&mut Builder<'_>, &[Value]),
496    ) -> (Interner, Func) {
497        let mut names = Interner::new();
498        let mut func = Func::new(
499            names.intern("f"),
500            Signature::new().with_params(params).with_returns(returns),
501        );
502        let entry = func.create_block();
503        let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
504        let mut build = Builder::new(&mut func, entry);
505        body(&mut build, &args);
506        (names, func)
507    }
508
509    fn run(func: &mut Func, names: &mut Interner) -> Ran {
510        group(func, names, &x86_64::SYSV, Goal::Speed, true)
511    }
512
513    fn i32() -> Type {
514        Type::int(32)
515    }
516
517    #[test]
518    fn the_group_is_the_passes_the_pipeline_used_to_call_one_line_at_a_time() {
519        // The list rather than the length, because a list checked only for its length is a list
520        // anybody can reorder without noticing, and the order is half of what this file is for.
521        let names: Vec<&str> = Step::GROUP.iter().map(|step| step.name()).collect();
522        assert_eq!(
523            names,
524            [
525                "switches",
526                "retries",
527                "orderings",
528                "overflows",
529                // Ahead of the integer splitting, because the calls it writes take and give back
530                // whole words that the splitting then has nothing left to say about.
531                "half-floats",
532                "halves",
533                "widths",
534                "bytes",
535                "counts",
536                "quads",
537                "floats",
538                "bulk",
539                "rounds",
540                "varargs",
541            ]
542        );
543    }
544
545    #[test]
546    fn every_step_says_what_it_is_for_and_no_two_say_the_same_thing() {
547        let mut names: Vec<&str> = Step::GROUP.iter().map(|step| step.name()).collect();
548        let mut constructs: Vec<&str> = Step::GROUP.iter().map(|step| step.construct()).collect();
549        assert!(constructs.iter().all(|construct| !construct.is_empty()));
550        for list in [&mut names, &mut constructs] {
551            let was = list.len();
552            list.sort_unstable();
553            list.dedup();
554            assert_eq!(list.len(), was, "two steps say the same thing");
555        }
556    }
557
558    #[test]
559    fn a_function_with_nothing_in_it_leaves_every_step_with_nothing_to_say() {
560        let (mut names, mut func) = one(&[], &[], |build, _| {
561            build.ret(&[]);
562        });
563        let ran = run(&mut func, &mut names);
564        assert_eq!(ran.did.len(), Step::GROUP.len());
565        assert!(ran.did.iter().all(|did| did.found == 0 && did.before == did.after));
566    }
567
568    #[test]
569    fn nothing_in_the_group_is_left_out_of_the_record() {
570        let (mut names, mut func) = one(&[], &[], |build, _| {
571            build.ret(&[]);
572        });
573        let ran = run(&mut func, &mut names);
574        let ordered: Vec<Step> = ran.did.iter().map(|did| did.step).collect();
575        assert_eq!(ordered, Step::GROUP);
576    }
577
578    /// `unsigned b(unsigned x) { return __builtin_bswap32(x); }`, which is one of the constructs
579    /// in the list and therefore one the group owes an answer for.
580    #[test]
581    fn a_byte_reversal_does_not_survive_the_group() {
582        let (mut names, mut func) = one(&[i32()], &[i32()], |build, args| {
583            let swapped = build.unary(Opcode::Bswap, args[0], i32());
584            build.ret(&[swapped]);
585        });
586        let ran = run(&mut func, &mut names);
587        let did = ran.of(Step::Bytes);
588        assert_eq!(did.found, 1);
589        assert_eq!(did.left, 0);
590        assert!(did.after > did.before, "one instruction became several");
591    }
592
593    /// `int c(unsigned x) { return __builtin_popcount(x); }`.
594    #[test]
595    fn a_bit_count_does_not_survive_the_group() {
596        let (mut names, mut func) = one(&[i32()], &[i32()], |build, args| {
597            let ones = build.unary(Opcode::Ctpop, args[0], i32());
598            build.ret(&[ones]);
599        });
600        let ran = run(&mut func, &mut names);
601        assert_eq!(ran.of(Step::Counts).found, 1);
602        assert_eq!(ran.of(Step::Counts).left, 0);
603    }
604
605    /// `double n(double x) { return -x; }`, which is a float rather than an integer and so reaches
606    /// a different member of the group.
607    #[test]
608    fn a_float_negation_does_not_survive_the_group() {
609        let f64 = Type::float(Float::F64);
610        let (mut names, mut func) = one(&[f64], &[f64], |build, args| {
611            let negated = build.unary(Opcode::FNeg, args[0], f64);
612            build.ret(&[negated]);
613        });
614        let ran = run(&mut func, &mut names);
615        assert_eq!(ran.of(Step::Floats).found, 1);
616        assert_eq!(ran.of(Step::Floats).left, 0);
617    }
618
619    /// `long a(long *p) { return __atomic_load_n(p, __ATOMIC_SEQ_CST); }`, which on this machine is
620    /// the same `mov` an ordinary read is, and which nothing below this step in the group knows the
621    /// name of.
622    #[test]
623    fn an_ordered_load_does_not_survive_the_group() {
624        let i64 = Type::int(64);
625        let (mut names, mut func) = one(&[Type::PTR], &[i64], |build, args| {
626            let info = MemInfo {
627                size: 8,
628                align: 8,
629                order: MemOrder::SeqCst,
630                tbaa: None,
631                owns: 0,
632                restrict: Restrict::NONE,
633            };
634            let value = build.atomic_load(i64, args[0], info, Flags::NONE);
635            build.ret(&[value]);
636        });
637        let ran = run(&mut func, &mut names);
638        assert_eq!(ran.of(Step::Orderings).found, 1);
639        assert_eq!(ran.of(Step::Orderings).left, 0);
640    }
641
642    /// Every construct with an opcode behind it, checked the same way in one loop, so that a
643    /// thirteenth member added to the group without an answer is a failure here rather than
644    /// something noticed later by the selector refusing it by name.
645    #[test]
646    fn nothing_the_group_names_an_opcode_for_is_still_there_afterwards() {
647        for step in Step::GROUP {
648            let Some((mut names, mut func)) = holding(*step) else {
649                continue;
650            };
651            let ran = run(&mut func, &mut names);
652            let did = ran.of(*step);
653            assert_eq!(did.found, 1, "{}: the construct was not built", step.name());
654            assert_eq!(did.left, 0, "{}: the construct survived the group", step.name());
655        }
656    }
657
658    /// One small function holding exactly one of the construct that step answers for, for the
659    /// steps whose construct is an opcode. The rest answer `None`: three of them are about a type
660    /// rather than an opcode, one rewrites an operand and takes nothing out, and the variable
661    /// argument list needs a whole calling convention around it to be worth building here.
662    fn holding(step: Step) -> Option<(Interner, Func)> {
663        let i32 = i32();
664        let i64 = Type::int(64);
665        let f64 = Type::float(Float::F64);
666        Some(match step {
667            Step::Bytes => one(&[i32], &[i32], |build, args| {
668                let swapped = build.unary(Opcode::Bswap, args[0], i32);
669                build.ret(&[swapped]);
670            }),
671            Step::Counts => one(&[i32], &[i32], |build, args| {
672                let ones = build.unary(Opcode::Ctlz, args[0], i32);
673                build.ret(&[ones]);
674            }),
675            Step::Floats => one(&[], &[f64], |build, _| {
676                let k = build.fconst(f64, 0x3ff8_0000_0000_0000);
677                build.ret(&[k]);
678            }),
679            Step::Orderings => one(&[Type::PTR], &[i64], |build, args| {
680                let info = MemInfo {
681                    size: 8,
682                    align: 8,
683                    order: MemOrder::SeqCst,
684                    tbaa: None,
685                    owns: 0,
686                    restrict: Restrict::NONE,
687                };
688                let value = build.atomic_load(i64, args[0], info, Flags::NONE);
689                build.ret(&[value]);
690            }),
691            Step::Overflows => one(&[i32, i32], &[i32], |build, args| {
692                let (sum, _) = build.checked(Opcode::UAddOverflow, args[0], args[1]);
693                build.ret(&[sum]);
694            }),
695            // `struct point { int x, y; } a, b; a = b;`, where the size and the alignment are on
696            // the access rather than in an operand, which is the shape the front end writes.
697            Step::Bulk => one(&[Type::PTR, Type::PTR], &[], |build, args| {
698                let info = MemInfo {
699                    size: 16,
700                    align: 8,
701                    order: MemOrder::NotAtomic,
702                    tbaa: None,
703                    owns: 0,
704                    restrict: Restrict::NONE,
705                };
706                let mem = build.func().add_mem(info);
707                let operands = build.func().push_values(&[args[0], args[1]]);
708                build.inst(
709                    InstData {
710                        args: operands,
711                        extra: Extra::Mem(mem),
712                        ..InstData::new(Opcode::Memcpy)
713                    },
714                    &[],
715                );
716                build.ret(&[]);
717            }),
718            _ => return None,
719        })
720    }
721
722    /// The cheap path, which is what a build that did not ask for the dump takes. The steps still
723    /// run and the function still comes out lowered, and what is skipped is a walk of the function
724    /// per step, which is not free on anything the size of a real translation unit.
725    #[test]
726    fn a_run_that_did_not_ask_for_the_dump_still_lowers_and_counts_nothing() {
727        let build = |build: &mut Builder<'_>, args: &[Value]| {
728            let swapped = build.unary(Opcode::Bswap, args[0], i32());
729            build.ret(&[swapped]);
730        };
731        let (mut names, mut func) = one(&[i32()], &[i32()], build);
732        let quiet = group(&mut func, &mut names, &x86_64::SYSV, Goal::Speed, false);
733        assert!(quiet.did.is_empty(), "nothing was counted");
734        assert_eq!(super::tally(&func, Step::Bytes), (super::tally(&func, Step::Bytes).0, 0));
735
736        // The same function through the counting path comes out the same size, so what the flag
737        // changes is what was written down and not what was done.
738        let (mut names, mut func) = one(&[i32()], &[i32()], build);
739        let loud = group(&mut func, &mut names, &x86_64::SYSV, Goal::Speed, true);
740        assert_eq!(loud.of(Step::Bytes).left, 0);
741        assert_eq!(
742            loud.did.last().expect("thirteen of them").after,
743            super::tally(&func, Step::Bytes).0
744        );
745    }
746
747    #[test]
748    fn nothing_is_recorded_for_a_run_that_did_not_ask() {
749        let mut quiet = Lowerings::new();
750        assert!(!quiet.wanted());
751        quiet.record("f", Ran::default());
752        assert_eq!(quiet.functions(), 1, "recording still works if somebody does it anyway");
753
754        let asked = Lowerings::asked(true);
755        assert!(asked.wanted());
756        assert_eq!(asked.listing(), "# rucc lowering: 0 functions\n");
757    }
758
759    #[test]
760    fn the_dump_names_every_step_whether_it_fired_or_not() {
761        // A dump listing only the steps that fired cannot tell a step that found nothing from a
762        // step somebody forgot to put in the group, which is the one thing it is read for.
763        let (mut names, mut func) = one(&[i32()], &[i32()], |build, args| {
764            let swapped = build.unary(Opcode::Bswap, args[0], i32());
765            build.ret(&[swapped]);
766        });
767        let ran = run(&mut func, &mut names);
768        let text = ran.render("f");
769        assert!(text.starts_with("lowering f\n"), "{text}");
770        for step in Step::GROUP {
771            assert!(text.contains(step.name()), "{} is missing from {text}", step.name());
772        }
773        assert!(text.contains("found 1, left 0"), "{text}");
774        assert_eq!(text.lines().count(), Step::GROUP.len() + 1);
775    }
776
777    #[test]
778    fn only_the_two_steps_that_retype_a_whole_function_ever_say_they_touched_nothing() {
779        // The rest work instruction by instruction and are never asked, so a `true` from one of
780        // them is not evidence of anything and the dump does not print it.
781        assert_eq!(
782            Step::GROUP.iter().filter(|step| step.whole_function()).copied().collect::<Vec<_>>(),
783            [Step::Halves, Step::Widths]
784        );
785        for step in Step::GROUP {
786            if step.whole_function() {
787                // Both of them are about the width on a value rather than about an opcode, so
788                // there is nothing for `found` and `left` to count.
789                assert!(step.opcodes().is_empty(), "{} counts opcodes", step.name());
790            }
791        }
792    }
793
794    #[test]
795    fn an_instruction_nothing_in_the_group_is_about_is_left_exactly_where_it_was() {
796        let (mut names, mut func) = one(&[i32()], &[i32()], |build, args| {
797            let seven = build.iconst(i32(), 7);
798            let sum = build.binary(Opcode::Add, args[0], seven, Flags::NONE);
799            build.ret(&[sum]);
800        });
801        let before = super::tally(&func, Step::Rounds).0;
802        let ran = run(&mut func, &mut names);
803        assert_eq!(super::tally(&func, Step::Rounds).0, before);
804        assert!(ran.did.iter().all(|did| did.found == 0));
805    }
806}