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