Skip to main content

rucc_opt/
loop_delete.rs

1//! Takes out a loop that comes back, working out what it left behind.
2//!
3//! Design: `spec/optimizer/17-dce.md` for what makes a thing removable and section 28.9 for the
4//! closed form, the off by one in it, and why the two questions this pass asks want different
5//! things from the trip count. This is tamnd/rucc#1631.
6//!
7//! [`crate::dce`] cannot do this and the reason is worth stating, because it looks at first like a
8//! gap in that pass. An empty counted loop has a counter, an add, a compare and a branch, and
9//! every one of them is used: the add feeds the compare, the compare feeds the branch, and the
10//! branch feeds the block parameter the add reads. Nothing in it has a use count of zero, so a
11//! pass driven by use counts correctly leaves all of it alone. The question that gets the loop out
12//! is not asked of an instruction, it is asked of the loop: does anything outside read what it
13//! computes, does it do anything to memory, and does it come back. Three yeses and the loop is a
14//! way of spending time.
15//!
16//! # Which loops
17//!
18//! A preheader, one exit, and a bound rather than an estimate. The bound is what says the loop
19//! terminates, which is the third question and the one a person is most likely to forget: a loop
20//! that computes nothing and never comes back still cannot be taken out, because not coming back
21//! is what it does. Section 7.5's distinction between a bound and an estimate is exactly this: an
22//! estimate decides whether a transformation pays and a bound decides what the program does.
23//!
24//! The bound is read through [`crate::scev::Bound::comes_back`] rather than through the accessor
25//! [`crate::unroll`] uses, and the difference is worth a sentence. Unrolling multiplies by the
26//! count, so it needs the count to be the right number. Deleting only needs there to be a last
27//! iteration, and `for (i = 0; i < n; i++)` has one whatever `n` turns out to be, so a count
28//! worked out from a value the loop does not change is as good as a number here. It stops being
29//! as good the moment anything reads what the loop left behind, which is why the two questions
30//! are asked in that order and with different accessors.
31//!
32//! Every instruction inside has to be one whose not happening nothing can tell. That is the
33//! predicate [`crate::dce`] already has, so it is read from there rather than written again, and
34//! it means a plain load may be inside the loop and a `volatile` one may not. A call is allowed
35//! when the purity analysis says it reads memory at most and comes back, which is the same rule
36//! that lets a call whose result nothing reads go.
37//!
38//! # What the loop leaves behind
39//!
40//! A loop whose total somebody reads afterwards is a loop that hands something over, and most
41//! loops worth writing are that kind. Sometimes what it hands over can be worked out without
42//! running it. A value that goes up by the same amount every time round is `{base, +, step}` in
43//! [`crate::scev`]'s terms, the loop is left on the iteration the exit test first fails, and that
44//! iteration's number is the count, so what the loop leaves behind is `base + step * count`. The
45//! preheader works that out in one go and hands it over instead, and then nothing outside reads
46//! anything the loop computed and the loop goes.
47//!
48//! Handing it over is two different edits, because a value defined in the loop reaches the code
49//! after it by two different roads. It may be an argument on the edge out, landing in a parameter
50//! of the block the loop leaves to, which is the shape [`crate::canon`] puts things in. Or the
51//! block after the loop may simply name it, which is legal wherever the definition dominates the
52//! use and is what is actually there by the time this runs, since the block loop closed form put in
53//! the way is one [`crate::simplify_cfg`] has every reason to fold away again. So both are looked
54//! for, and a use of the second kind is rewritten where it stands.
55//!
56//! No overflow argument is needed for this and it is worth saying why, because the neighbouring
57//! transformation in section 28.4 does need one. A value that steps by a fixed amount evolves in
58//! its own type, which is to say modulo two to the width, and addition modulo two to the width is
59//! associative, so adding `step` to `base` `count` times and working out `base + step * count` the
60//! same way are the same number whatever either of them does to the top bit. Section 28.4's rewrite
61//! is a different claim, that one comparison holds exactly where another does, and that one does
62//! turn on whether the limit overflows. So the arithmetic written here carries neither `nsw` nor
63//! `nuw`, and the promise the loop's own increment carried is not copied onto it, because that
64//! promise is about the sequence and says nothing about this.
65//!
66//! What is written down is the whole expression rather than three instructions to be folded later,
67//! because this pass is the last one in the pipeline and there is no later. `base` and `step` are
68//! both [`crate::scev::Invariant`], which is `value * scale + offset` with the arithmetic on it
69//! already, so `base + step * count` is worked out in that form first and only what is left of it
70//! reaches the function. A loop adding one a million times leaves a constant behind and a loop
71//! adding an invariant `n` a million times leaves one multiply.
72//!
73//! A count that is an expression rather than a number is written as `base + step * max(count, 0)`,
74//! and the two things bolted onto it there are two assumptions paid for rather than believed. The
75//! clamp is [`crate::scev::Assumption::Entered`]. A count that comes out negative is a loop whose
76//! test failed the first time it ran, which is a loop that took its back edge no times and handed
77//! over what one pass through its body left, and zero is the count that says exactly that. The
78//! widening is the reading the exit test took, a sign extension for a signed test and a zero
79//! extension for an unsigned one. Section 7.7 is the warning about getting that one wrong: a limit
80//! past the middle of a thirty two bit type is a large number to an unsigned test and a negative
81//! one to a signed test, so sign extending what an unsigned test compared would clamp to zero and
82//! turn a loop over three billion elements into one that ran no times.
83//!
84//! The clamp is done in sixty four bits and the arithmetic in the value's own type, and the cut
85//! between the two is exact rather than close enough. Multiplying modulo two to the width and then
86//! cutting to a narrower width is the same number as cutting first and then multiplying, so a count
87//! worked out wide and truncated is the count. Widening it instead is a zero extension, because the
88//! clamp has already made it a number that is not negative.
89//!
90//! It is only done when it lets the loop go, which is a cost rule rather than a correctness one.
91//! Writing the final value down where the loop stays behind costs a multiply in the preheader and
92//! saves nothing, because the loop still carries the value round its own back edge and nothing in
93//! rucc yet takes out a block parameter whose only reader is the argument it passes to itself.
94//! [`crate::dce`]'s own notes call that out as a transformation worth having and a different one
95//! from what it does. When there is one, this gate is the thing to reconsider.
96//!
97//! # What it does
98//!
99//! Works out in the preheader whatever the loop was going to leave behind, puts those values where
100//! the loop's own were read, points the preheader at the block the loop left to with whatever the
101//! edge out was already carrying from outside, and lets the sweep in [`crate::simplify_cfg`] take
102//! the blocks nothing reaches. Every value named in any of it is asserted to dominate the preheader
103//! rather than assumed to: a value defined outside the loop that reaches the exit test has to
104//! dominate the preheader, and an assertion is cheaper than being wrong about why.
105
106use std::collections::{HashMap, HashSet};
107
108use rucc_ir::{Block, Builder, Extra, Func, Inst, InstData, IntPred, Opcode, Type, Value};
109
110use crate::cfg::Cfg;
111use crate::dom::Dominators;
112use crate::loops::{LoopId, Loops};
113use crate::purity::Facts;
114use crate::scev::{Count, Invariant, Plain, Reading, Scev};
115use crate::{Analyses, Fuel, Pass, Preserved, Stats};
116
117const DELETED: &str = "loop taken out, it comes back and leaves nothing behind";
118const WRITTEN: &str = "what the loop was going to leave behind worked out in front of it instead";
119const NO_COUNT: &str = "loop left as it was, nothing here says it comes back";
120const SHAPE: &str =
121    "loop left as it was, it has no preheader or it leaves from more than one place";
122const EFFECTS: &str = "loop left as it was, something in it does more than work out a value";
123const NO_FORM: &str =
124    "loop left as it was, what it leaves behind is not a thing this can work out in front of it";
125const ENTRIES: &str = "loop left as it was, it is reached somewhere other than at its header";
126const NO_FUEL: &str = "loop left as it was, the pass ran out of fuel";
127
128/// Section 17's dead code elimination, asked about a loop rather than about an instruction.
129#[derive(Debug)]
130pub struct LoopDelete;
131
132impl Pass for LoopDelete {
133    fn name(&self) -> &'static str {
134        "loop-delete"
135    }
136
137    fn describe(&self) -> &'static str {
138        "a loop that comes back and leaves nothing behind is taken out"
139    }
140
141    fn preserves(&self) -> Preserved {
142        // The loop goes, and its blocks with it.
143        Preserved::NONE
144    }
145
146    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
147        let mut stats = Stats::new();
148        if func.entry().is_none() {
149            return stats;
150        }
151        let mut done: HashSet<Block> = HashSet::new();
152        let mut say = true;
153        while let Some(job) = plan(func, an, &done, &mut stats, say) {
154            say = false;
155            if !fuel.take() {
156                stats.missed(NO_FUEL);
157                break;
158            }
159            done.insert(job.header);
160            for _ in 0..apply(func, &job) {
161                stats.optimized(WRITTEN);
162            }
163            stats.optimized(DELETED);
164            an.clear();
165            crate::simplify_cfg::sweep(func, an, &mut stats);
166        }
167        an.clear();
168        stats
169    }
170}
171
172/// One loop to take out, worked out against the function as it stands.
173#[derive(Debug)]
174struct Job {
175    /// The block the loop is entered at, which is what says which loop this was.
176    header: Block,
177    /// The one block outside the loop with an edge to the header.
178    preheader: Block,
179    /// The block outside the loop the one exit edge arrives at.
180    exit: Block,
181    /// The blocks the loop is made of, which is what says which uses are the ones outside it.
182    inside: HashSet<Block>,
183    /// What the edge out was going to carry, which the preheader carries instead.
184    args: Vec<Value>,
185    /// Every value the loop defines that anything outside it reads, and what it ends up holding.
186    ends: Vec<(Value, Leaves)>,
187}
188
189/// What a value the loop defines holds by the time anything outside it looks.
190///
191/// Two shapes for the two shapes a count comes in, and what separates them is how much of the
192/// answer is settled before anything reaches the function. A count that is a number settles all of
193/// it, so what is left is one expression and often one constant. A count that is an expression
194/// settles none of it and the preheader does the work.
195#[derive(Clone, Copy, Debug)]
196enum Leaves {
197    /// `base + step * count`, worked out in full because the count is a number.
198    Worked {
199        /// The type it evolved in, which is the type the arithmetic is done in.
200        ty: Type,
201        /// The whole of it, as far as it goes without writing anything down.
202        end: Invariant,
203    },
204    /// `base + step * max(count, 0)`, built in the preheader because the count is an expression.
205    Built {
206        /// The type it evolved in, which is the type the arithmetic is done in.
207        ty: Type,
208        /// What the value holds the first time anything outside could have looked.
209        base: Invariant,
210        /// How much it goes up by each time round.
211        step: Invariant,
212        /// How many times the back edge is taken, before the clamp the module notes describe.
213        count: Plain,
214        /// How the exit test read what the count is built on, which is the widening owed.
215        reading: Reading,
216    },
217}
218
219/// The innermost loop that can go, and what it would take.
220///
221/// One at a time, for the reason [`crate::unroll::plan`] takes one at a time: taking a loop out
222/// invalidates the forest the next answer would be read out of. `say` is false after the first
223/// round so that a loop this declines is declined once rather than once per round.
224fn plan(
225    func: &Func,
226    an: &mut Analyses,
227    done: &HashSet<Block>,
228    stats: &mut Stats,
229    say: bool,
230) -> Option<Job> {
231    let facts = an.purity();
232    let cfg = an.cfg(func);
233    let doms = an.dominators(func);
234    let loops = an.loops(func);
235    let mut scev = Scev::new(func, cfg, loops);
236    let mut found: Option<(u32, Job)> = None;
237    for id in loops.all() {
238        if done.contains(&loops.header(id)) {
239            continue;
240        }
241        match consider(func, cfg, doms, loops, facts, &mut scev, id) {
242            Ok(job) => {
243                let depth = loops.depth(id);
244                if found.as_ref().is_none_or(|(had, _)| depth > *had) {
245                    found = Some((depth, job));
246                }
247            }
248            Err(why) if say => stats.missed(why),
249            Err(_) => (),
250        }
251    }
252    found.map(|(_, job)| job)
253}
254
255/// Whether this loop can go, and why not when it cannot.
256fn consider(
257    func: &Func,
258    cfg: &Cfg,
259    doms: &Dominators,
260    loops: &Loops,
261    facts: &Facts,
262    scev: &mut Scev<'_>,
263    id: LoopId,
264) -> Result<Job, &'static str> {
265    let header = loops.header(id);
266    let preheader = loops.preheader(cfg, id).ok_or(SHAPE)?;
267    let [only] = loops.exits(id) else {
268        return Err(SHAPE);
269    };
270
271    let blocks = loops.blocks(id).to_vec();
272    let inside: HashSet<Block> = blocks.iter().copied().collect();
273    for &block in &blocks {
274        // The same reducibility check unrolling makes. A block of the loop reached from outside
275        // the loop is a region this has no right to reason about as one piece.
276        if block != header && cfg.predecessors(block).iter().any(|at| !inside.contains(at)) {
277            return Err(ENTRIES);
278        }
279        for inst in func.insts(block) {
280            if func.is_terminator(inst) {
281                // A terminator that leaves the function or goes somewhere worked out at run time
282                // is not an edge the forest accounted for, so the one exit counted above is not
283                // the only way out.
284                if !matches!(func[inst].opcode, Opcode::Jump | Opcode::BrIf) {
285                    return Err(EFFECTS);
286                }
287                continue;
288            }
289            if !crate::dce::removable(func, inst, facts) {
290                return Err(EFFECTS);
291            }
292        }
293    }
294    // The bound is what says the loop comes back. A loop that computes nothing and runs forever
295    // still does something, which is run forever. A count worked out from a value the loop does
296    // not change says that as well as a number does: whatever that value is, the loop gets to it,
297    // and nothing in here needs to know how many steps that took. What is not allowed is a loop
298    // ending on `!=` whose counter may step past its limit, and that is the one assumption
299    // [`crate::scev::Bound::comes_back`] holds back.
300    let bound = scev.bound(id).ok_or(NO_COUNT)?;
301    // Taken here rather than inside [`ending`] because it belongs to the exit test rather than to
302    // any one value the loop hands over, so every one of them owes the same widening.
303    let reading = bound.reading();
304    let count = bound.comes_back().ok_or(NO_COUNT)?;
305
306    let term = func.terminator(only.from).ok_or(SHAPE)?;
307    let leaving = func.successors(term).find(|call| call.block == only.to).ok_or(SHAPE)?;
308    let args = func[leaving.args].to_vec();
309
310    let mut wanted = read_outside(func, &blocks, &inside);
311    for &arg in &args {
312        if loops.is_invariant(func, id, arg) {
313            debug_assert!(
314                doms.dominates(defined_in(func, arg), preheader),
315                "a value outside the loop that reaches the exit test dominates the preheader"
316            );
317            continue;
318        }
319        if !wanted.contains(&arg) {
320            wanted.push(arg);
321        }
322    }
323
324    let mut ends = Vec::with_capacity(wanted.len());
325    for value in wanted {
326        let end = ending(func, scev, id, value, count, reading).ok_or(NO_FORM)?;
327        debug_assert!(
328            names(end).iter().all(|&on| doms.dominates(defined_in(func, on), preheader)),
329            "a value the loop does not change is defined outside it and so dominates the preheader"
330        );
331        ends.push((value, end));
332    }
333    Ok(Job { header, preheader, exit: only.to, inside, args, ends })
334}
335
336/// Every value the loop defines that a block outside it names, in the order they turn up.
337///
338/// [`crate::unroll::escapes`] asks whether there is one of these and stops there, because a loop
339/// with one is a loop it will not copy. Here they are the work rather than the reason to stop, so
340/// the answer has to be which ones.
341fn read_outside(func: &Func, blocks: &[Block], inside: &HashSet<Block>) -> Vec<Value> {
342    let mut defined: HashSet<Value> = HashSet::new();
343    for &block in blocks {
344        defined.extend(func[block].params.iter().copied());
345        for inst in func.insts(block) {
346            defined.extend(func[inst].results());
347        }
348    }
349    let mut found = Vec::new();
350    for block in func.blocks() {
351        if inside.contains(&block) {
352            continue;
353        }
354        for inst in func.insts(block) {
355            let reads = func[func[inst].args].iter().copied();
356            let passes = func.successors(inst).flat_map(|call| func[call.args].to_vec());
357            for value in reads.chain(passes) {
358                if defined.contains(&value) && !found.contains(&value) {
359                    found.push(value);
360                }
361            }
362        }
363    }
364    found
365}
366
367/// What the loop leaves in a value it hands over, or `None` when that is not a thing to write down.
368///
369/// Every refusal here is asked before anything is written, so that a refusal is a refusal rather
370/// than a preheader with half an expression in it. There is no undo and there should not need to
371/// be.
372fn ending(
373    func: &Func,
374    scev: &mut Scev<'_>,
375    id: LoopId,
376    value: Value,
377    count: Count,
378    reading: Reading,
379) -> Option<Leaves> {
380    let chrec = scev.evolution(id, value).chrec()?;
381    // The arithmetic below is integer arithmetic in one lane. A chrec over anything else is not a
382    // thing this knows how to write down, whatever the count turned out to be.
383    if !chrec.ty.is_int() || chrec.ty.is_vector() {
384        return None;
385    }
386    match count {
387        Count::Exact(trips) => {
388            let trips = i128::try_from(trips).ok()?;
389            let all = chrec.step.times(Invariant::number(trips))?;
390            let end = chrec.base.plus(all)?;
391            writable(func, end, chrec.ty)?;
392            Some(Leaves::Worked { ty: chrec.ty, end })
393        }
394        Count::Symbolic(count) => {
395            writable(func, chrec.base, chrec.ty)?;
396            writable(func, chrec.step, chrec.ty)?;
397            let count = count.plain()?;
398            // A count built on a value that is itself read through an extension carries a widening
399            // of its own, and which of that one and the exit test's should be spent is not a
400            // question with an answer here. Refused rather than guessed at, the same way
401            // [`crate::trip::counted`] refuses it.
402            if count.read.is_some() {
403                return None;
404            }
405            // Room for the clamp to happen in. A count built on something already as wide as the
406            // arithmetic that carries it has nowhere to be negative.
407            if count.value.filter(|_| count.scale != 0).is_none_or(|on| func[on].ty.bits() > 64) {
408                return None;
409            }
410            Some(Leaves::Built { ty: chrec.ty, base: chrec.base, step: chrec.step, count, reading })
411        }
412    }
413}
414
415/// Whether [`write`] can put an expression in front of the loop in the type given.
416fn writable(func: &Func, part: Invariant, ty: Type) -> Option<Plain> {
417    let plain = part.plain()?;
418    if plain.read.is_some() {
419        return None;
420    }
421    if plain.value.is_some_and(|named| func[named].ty != ty) {
422        return None;
423    }
424    Some(plain)
425}
426
427/// Every value an expression is built on, which is what the dominance assertion is asked of.
428fn names(leaves: Leaves) -> Vec<Value> {
429    let on =
430        |part: Invariant| part.plain().and_then(|plain| plain.value.filter(|_| plain.scale != 0));
431    match leaves {
432        Leaves::Worked { end, .. } => on(end).into_iter().collect(),
433        Leaves::Built { base, step, count, .. } => {
434            [on(base), on(step), count.value].into_iter().flatten().collect()
435        }
436    }
437}
438
439/// The block a value is defined in.
440fn defined_in(func: &Func, value: Value) -> Block {
441    match func[value].def {
442        rucc_ir::Def::Result { inst, .. } => {
443            func.block_of(inst).expect("a value in use is defined in a block")
444        }
445        rucc_ir::Def::Param { block, .. } => block,
446    }
447}
448
449/// Points the preheader past the loop, working out on the way what the loop was going to leave.
450///
451/// Answers how many of those there were, which is what the report counts.
452fn apply(func: &mut Func, job: &Job) -> usize {
453    let term = func.terminator(job.preheader).expect("a preheader ends in a jump to the header");
454    let mut instead: HashMap<Value, Value> = HashMap::new();
455    // One clamp for the whole loop rather than one per value, since the count belongs to the loop
456    // and the values differ only in what they do with it.
457    let mut times: Option<Value> = None;
458    for &(value, leaves) in &job.ends {
459        let worked = match leaves {
460            Leaves::Worked { ty, end } => write(func, term, ty, end),
461            Leaves::Built { ty, base, step, count, reading } => {
462                let all = match times {
463                    Some(had) => had,
464                    None => *times.insert(clamped(func, term, count, reading)),
465                };
466                built(func, term, ty, base, step, all)
467            }
468        };
469        instead.insert(value, worked);
470    }
471    swap_in(func, job, &instead);
472    let args: Vec<Value> =
473        job.args.iter().map(|arg| instead.get(arg).copied().unwrap_or(*arg)).collect();
474    func.remove_inst(term);
475    Builder::new(func, job.preheader).jump(job.exit, &args);
476    instead.len()
477}
478
479/// Puts the worked out values where the loop's own were read.
480///
481/// Only outside the loop, because inside it the loop's own values are still the right answer right
482/// up until the blocks go. The preheader is outside and gets walked with the rest, which is
483/// harmless and better than a special case: what was just written into it names nothing the loop
484/// defines.
485fn swap_in(func: &mut Func, job: &Job, instead: &HashMap<Value, Value>) {
486    if instead.is_empty() {
487        return;
488    }
489    let outside: Vec<Block> = func.blocks().filter(|at| !job.inside.contains(at)).collect();
490    for block in outside {
491        for inst in func.insts(block).collect::<Vec<_>>() {
492            let mut lists = vec![func[inst].args];
493            lists.extend(func.successors(inst).map(|call| call.args));
494            for list in lists {
495                func.rewrite(list, |value| instead.get(&value).copied().unwrap_or(value));
496            }
497        }
498    }
499}
500
501/// Works an expression out in front of an instruction.
502///
503/// `value * scale + offset`, with the parts that are nothing left out, so a scale of one is no
504/// multiply and an offset of zero is no add and an expression built on no value at all is one
505/// constant. That is what makes a loop adding one a million times leave a number behind rather than
506/// three instructions nothing is going to fold, this being the last pass there is.
507fn write(func: &mut Func, before: Inst, ty: Type, end: Invariant) -> Value {
508    let plain = end.plain().expect("consider refused anything this cannot write");
509    let Some(value) = plain.value.filter(|_| plain.scale != 0) else {
510        return crate::ivopts::number(func, before, ty, plain.offset);
511    };
512    let mut so_far = value;
513    if plain.scale != 1 {
514        let by = crate::ivopts::number(func, before, ty, plain.scale);
515        so_far = arith(func, before, Opcode::Mul, so_far, by, ty);
516    }
517    if plain.offset != 0 {
518        let by = crate::ivopts::number(func, before, ty, plain.offset);
519        so_far = arith(func, before, Opcode::Add, so_far, by, ty);
520    }
521    so_far
522}
523
524/// How many times the back edge is taken, worked out in front of the loop and clamped at zero.
525///
526/// `max(read(value) * scale + offset, 0)`, in sixty four bits whatever the count's own type is, and
527/// the module notes say what each of those two is paying for. The clamp is a `select` rather than a
528/// branch because the whole of this has to be straight line code in a preheader, and nothing on any
529/// of it promises anything about overflow, since the count came out of a subtraction the analysis
530/// already reasoned about rather than out of anything written here.
531///
532/// [`crate::ivopts`] writes the same clamp in front of the same kind of loop, because it is
533/// discharging the same assumption in the same place, and two of these would be two things to keep
534/// in step. It lives here because this is where it was written and where the argument for it is.
535pub(crate) fn clamped(func: &mut Func, before: Inst, count: Plain, reading: Reading) -> Value {
536    let word = Type::int(64);
537    let on = count.value.expect("a count that is an expression is built on a value");
538    let mut wide = on;
539    if func[on].ty.bits() < 64 {
540        let widen = match reading {
541            Reading::Signed => Opcode::SExt,
542            Reading::Unsigned => Opcode::ZExt,
543        };
544        wide = cast(func, before, widen, wide, word);
545    }
546    if count.scale != 1 {
547        let by = crate::ivopts::number(func, before, word, count.scale);
548        wide = arith(func, before, Opcode::Mul, wide, by, word);
549    }
550    if count.offset != 0 {
551        let by = crate::ivopts::number(func, before, word, count.offset);
552        wide = arith(func, before, Opcode::Add, wide, by, word);
553    }
554    let none = crate::ivopts::number(func, before, word, 0);
555    let args = func.push_values(&[wide, none]);
556    let test =
557        InstData { args, extra: Extra::IntPred(IntPred::Sgt), ..InstData::new(Opcode::ICmp) };
558    let entered = made(func, before, test, word.with_lane(Type::I1));
559    let args = func.push_values(&[entered, wide, none]);
560    made(func, before, InstData { args, ..InstData::new(Opcode::Select) }, word)
561}
562
563/// `base + step * times`, worked out in front of the loop in the type the value evolved in.
564///
565/// The trivial parts are left out where the numbers make them trivial, for the reason [`write`]
566/// leaves them out: nothing after this pass folds a multiply by one, so a loop counting by ones
567/// would otherwise leave one in every preheader.
568fn built(
569    func: &mut Func,
570    before: Inst,
571    ty: Type,
572    base: Invariant,
573    step: Invariant,
574    times: Value,
575) -> Value {
576    if step.as_number() == Some(0) {
577        return write(func, before, ty, base);
578    }
579    let narrow = resize(func, before, times, ty);
580    let mut so_far = narrow;
581    if step.as_number() != Some(1) {
582        let by = write(func, before, ty, step);
583        so_far = arith(func, before, Opcode::Mul, by, narrow, ty);
584    }
585    if base.as_number() != Some(0) {
586        let from = write(func, before, ty, base);
587        so_far = arith(func, before, Opcode::Add, from, so_far, ty);
588    }
589    so_far
590}
591
592/// The clamped count in the type the arithmetic is done in.
593///
594/// A truncation where that type is narrower, which loses nothing that matters: cutting a product
595/// modulo two to the width and multiplying a cut are the same number. A zero extension where it is
596/// wider, which is exact because the clamp has already made the count a number that is not
597/// negative. Neither where the widths agree.
598fn resize(func: &mut Func, before: Inst, times: Value, ty: Type) -> Value {
599    let had = func[times].ty.bits();
600    if had == ty.bits() {
601        return times;
602    }
603    let either = if ty.bits() < had { Opcode::Trunc } else { Opcode::ZExt };
604    cast(func, before, either, times, ty)
605}
606
607/// One widening or narrowing, worked out in front of another instruction.
608fn cast(func: &mut Func, before: Inst, opcode: Opcode, arg: Value, ty: Type) -> Value {
609    let args = func.push_values(&[arg]);
610    made(func, before, InstData { args, ..InstData::new(opcode) }, ty)
611}
612
613/// One arithmetic instruction, worked out in front of another one and promising nothing.
614///
615/// Neither `nsw` nor `nuw`, which is the point rather than an omission. The module notes say why:
616/// what the loop did is the same arithmetic modulo two to the width as many times as it ran, and
617/// `base + step * count` worked out the same way is the same number. A flag the loop's own
618/// increment carried is a fact about that sequence, and putting it here would be inventing one.
619pub(crate) fn arith(
620    func: &mut Func,
621    before: Inst,
622    opcode: Opcode,
623    left: Value,
624    right: Value,
625    ty: Type,
626) -> Value {
627    let args = func.push_values(&[left, right]);
628    made(func, before, InstData { args, ..InstData::new(opcode) }, ty)
629}
630
631/// One instruction with one result, put in front of another one and given its source location.
632fn made(func: &mut Func, before: Inst, data: InstData, ty: Type) -> Value {
633    let span = func.span(before);
634    let inst = func.create_inst(data, &[ty], span);
635    func.insert_before(inst, before);
636    func[inst].first_result.expect("one result was asked for")
637}
638
639#[cfg(test)]
640mod tests {
641    use rucc_base::Interner;
642    use rucc_ir::{
643        Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
644        Signature, Type, Value, verify_func,
645    };
646    use rucc_target::{TargetInfo, Triple};
647
648    use super::{DELETED, EFFECTS, LoopDelete, NO_COUNT, NO_FORM, NO_FUEL, WRITTEN};
649    use crate::stats::Kind;
650    use crate::{Fuel, Pass, Stats};
651
652    /// Runs the pass over the function as it stands.
653    fn delete(func: &mut Func, fuel: &mut Fuel) -> Stats {
654        LoopDelete.run(func, &mut crate::machine::fixtures::analyses(), fuel)
655    }
656
657    /// Insists the function is one the rest of the compiler may believe.
658    ///
659    /// Pointing a block at a different successor is the edit that hands a block the wrong number
660    /// of arguments and strands a definition its uses still name, so this is where most of the
661    /// strength of these tests is.
662    fn sound(func: &Func, names: &mut Interner) {
663        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
664        let module = Module::new(names.intern("t.c"), &target);
665        if let Err(errors) = verify_func(&module, func, names) {
666            panic!("{errors:#?}");
667        }
668    }
669
670    /// How many instructions of that opcode the whole function holds.
671    fn tally(func: &Func, opcode: Opcode) -> usize {
672        func.blocks()
673            .flat_map(|block| func.insts(block))
674            .filter(|&inst| func[inst].opcode == opcode)
675            .count()
676    }
677
678    /// The one value now handed to the block the loop used to leave to.
679    fn handed_value(func: &Func, done: &Block) -> Option<Value> {
680        let cfg = crate::cfg::Cfg::new(func);
681        let [only] = cfg.predecessors(*done) else {
682            return None;
683        };
684        let term = func.terminator(*only)?;
685        let call = func.successors(term).find(|call| call.block == *done)?;
686        let args = func[call.args].to_vec();
687        let [arg] = args[..] else {
688            return None;
689        };
690        Some(arg)
691    }
692
693    /// What the value handed over is a multiple of, when it is a multiple of something.
694    fn handed(func: &Func, done: &Block) -> Option<i128> {
695        let value = handed_value(func, done)?;
696        let Def::Result { inst, .. } = func[value].def else {
697            return None;
698        };
699        if func[inst].opcode != Opcode::Mul {
700            return None;
701        }
702        let args = func[func[inst].args].to_vec();
703        let (imm, ty) = crate::fold::constant(func, args[1])?;
704        Some(imm.signed(ty))
705    }
706
707    /// How many loops are left.
708    fn loops(func: &Func) -> usize {
709        let cfg = crate::cfg::Cfg::new(func);
710        let doms = crate::dom::Dominators::new(&cfg);
711        crate::loops::Loops::new(&cfg, &doms).count()
712    }
713
714    /// A four byte write with nothing said about what it aliases.
715    fn plain() -> MemInfo {
716        MemInfo {
717            size: 4,
718            align: 4,
719            order: MemOrder::NotAtomic,
720            tbaa: None,
721            owns: 0,
722            restrict: Restrict::NONE,
723        }
724    }
725
726    /// What the limit of the exit test is.
727    #[derive(Clone, Copy)]
728    enum Limit {
729        /// A number written in the program.
730        Number(i128),
731        /// A value the function was handed, which the loop does not change.
732        Given,
733        /// The same value, waited for with `!=` rather than counted up to with an ordering.
734        Landing,
735    }
736
737    /// What the loop does, which is the whole of what decides whether it can go.
738    #[derive(Clone, Copy, PartialEq)]
739    enum What {
740        /// Adds up a number nothing ever reads.
741        Nothing,
742        /// Writes each running total to the pointer it was handed.
743        Writes,
744        /// Hands the block it leaves to a total that went up by the same amount every time.
745        HandsOut,
746        /// Hands out a total that went up by one every time, so there is no multiply to write.
747        HandsOne,
748        /// Hands out a total that went up by a different amount every time round.
749        HandsSquare,
750        /// Leaves its total to be read after the loop by a road other than the edge out.
751        ReadAfter,
752    }
753
754    impl What {
755        /// Whether the total goes out on the edge the loop leaves by.
756        fn hands_out(self) -> bool {
757            matches!(self, What::HandsOut | What::HandsOne | What::HandsSquare)
758        }
759    }
760
761    struct Shape {
762        names: Interner,
763        func: Func,
764        entry: Block,
765        done: Block,
766    }
767
768    /// A counted loop in the shape `crate::canon` and `crate::header_copy` leave a `for` in.
769    ///
770    /// ```text
771    /// entry(p, n): jump head(0, 0)
772    /// head(i, sum): jump body(i, sum)
773    /// body(c, r): total = r + n; next = c + 1; test = next < limit
774    ///             br test -> head(next, total), done()
775    /// done: ret
776    /// ```
777    ///
778    /// Two blocks in the loop rather than one, so that taking it out has more than one block to get
779    /// rid of, and a running total carried round, so that there is something inside worth asking
780    /// whether anybody reads. The total goes up by `n` each time round, which is the shape of
781    /// `total += seed` in the issue: a value that goes up by the same amount every time, where the
782    /// amount is not a number anything here knows.
783    fn shaped(limit: Limit, what: What) -> Shape {
784        let mut names = Interner::new();
785        let signature = Signature::new().with_params(&[Type::PTR, Type::int(32)]);
786        let mut func = Func::new(names.intern("f"), signature);
787        let entry = func.create_block();
788        let head = func.create_block();
789        let body = func.create_block();
790        let done = func.create_block();
791        let place = func.append_param(entry, Type::PTR);
792        let given = func.append_param(entry, Type::int(32));
793        let i = func.append_param(head, Type::int(32));
794        let sum = func.append_param(head, Type::int(32));
795        let carried = func.append_param(body, Type::int(32));
796        let running = func.append_param(body, Type::int(32));
797        if what.hands_out() {
798            func.append_param(done, Type::int(32));
799        }
800
801        let mut build = Builder::new(&mut func, entry);
802        let zero = build.iconst(Type::int(32), 0);
803        build.jump(head, &[zero, zero]);
804        Builder::new(&mut func, head).jump(body, &[i, sum]);
805
806        let mut build = Builder::new(&mut func, body);
807        let one = build.iconst(Type::int(32), 1);
808        let by = match what {
809            What::HandsOne => one,
810            What::HandsSquare => carried,
811            _ => given,
812        };
813        let total = build.binary(Opcode::Add, running, by, Flags::NSW);
814        if what == What::Writes {
815            build.store(total, place, plain(), Flags::NONE);
816        }
817        let next = build.binary(Opcode::Add, carried, one, Flags::NSW);
818        let stop = match limit {
819            Limit::Number(n) => build.iconst(Type::int(32), n),
820            Limit::Given | Limit::Landing => given,
821        };
822        let pred = match limit {
823            Limit::Landing => IntPred::Ne,
824            _ => IntPred::Slt,
825        };
826        let test = build.icmp(pred, next, stop);
827        let out: Vec<Value> = if what.hands_out() { vec![total] } else { Vec::new() };
828        build.br_if(test, head, &[next, total], done, &out);
829        let mut build = Builder::new(&mut func, done);
830        if what == What::ReadAfter {
831            build.store(total, place, plain(), Flags::NONE);
832        }
833        build.ret(&[]);
834        Shape { names, func, entry, done }
835    }
836
837    #[test]
838    fn a_loop_that_leaves_nothing_behind_is_taken_out() {
839        let mut it = shaped(Limit::Number(1000), What::Nothing);
840        let stats = delete(&mut it.func, &mut Fuel::unlimited());
841        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
842        assert_eq!(loops(&it.func), 0);
843        assert_eq!(tally(&it.func, Opcode::Add), 0, "the counter and the total go with it");
844        assert_eq!(tally(&it.func, Opcode::BrIf), 0);
845        sound(&it.func, &mut it.names);
846    }
847
848    #[test]
849    fn the_blocks_it_took_out_are_swept_rather_than_left_unreachable() {
850        let mut it = shaped(Limit::Number(1000), What::Nothing);
851        delete(&mut it.func, &mut Fuel::unlimited());
852        let left: Vec<Block> = it.func.blocks().collect();
853        assert_eq!(left, vec![it.entry, it.done], "the header and the body are gone");
854        sound(&it.func, &mut it.names);
855    }
856
857    /// A loop counting up to a value nothing here knows still has a last iteration.
858    ///
859    /// The bound comes back [`crate::scev::Count::Symbolic`] with two assumptions on it rather
860    /// than one. The overflow one the front end already promised.
861    /// [`crate::scev::Assumption::Entered`] it did not, and what that one says is whether the
862    /// count is the distance to the limit or zero. Both of those are numbers of times a loop goes
863    /// round, so the question this pass asks, which is whether there is a last time, has been
864    /// answered whichever of them it turns out to be. Nothing outside reads what this loop
865    /// computes, so the count is never multiplied by and the assumption is never spent.
866    ///
867    /// This is the `for (i = 0; i < n; i++)` of tamnd/rucc#1631 and of the corpus rows the report
868    /// had rucc losing on. Reading the bound through [`crate::scev::Bound::comes_back`] is what
869    /// makes it the answer.
870    #[test]
871    fn a_loop_counting_up_to_a_value_handed_in_is_taken_out() {
872        let mut it = shaped(Limit::Given, What::Nothing);
873        let stats = delete(&mut it.func, &mut Fuel::unlimited());
874        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
875        assert_eq!(loops(&it.func), 0);
876        assert_eq!(tally(&it.func, Opcode::Add), 0, "the counter and the total go with it");
877        sound(&it.func, &mut it.names);
878    }
879
880    /// A loop that may step over the value it is waiting for is a loop that may not come back.
881    ///
882    /// `!=` ends a loop on the one iteration where the counter is the limit, so a counter that
883    /// starts past the limit, or that steps over it, goes round until it wraps. That is
884    /// [`crate::scev::Assumption::Approaching`], the one
885    /// [`crate::scev::Bound::comes_back`] holds back, and it is held back because document 17.2
886    /// says rucc does not take out a loop that might not end. The step here is one and the loop
887    /// would in fact arrive, which is the point: the pass refuses on what it has been shown rather
888    /// than on what happens to be true.
889    #[test]
890    fn a_loop_that_may_step_over_the_value_it_waits_for_is_left_alone() {
891        let mut it = shaped(Limit::Landing, What::Nothing);
892        let stats = delete(&mut it.func, &mut Fuel::unlimited());
893        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
894        assert_eq!(stats.count(Kind::Missed, NO_COUNT), 1);
895        assert_eq!(loops(&it.func), 1);
896        sound(&it.func, &mut it.names);
897    }
898
899    #[test]
900    fn a_loop_that_writes_to_memory_is_left_alone() {
901        let mut it = shaped(Limit::Number(1000), What::Writes);
902        let stats = delete(&mut it.func, &mut Fuel::unlimited());
903        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
904        assert_eq!(stats.count(Kind::Missed, EFFECTS), 1);
905        assert_eq!(loops(&it.func), 1);
906        assert_eq!(tally(&it.func, Opcode::Store), 1);
907        sound(&it.func, &mut it.names);
908    }
909
910    /// A total read after the loop without going through a parameter of the block that reads it.
911    ///
912    /// This is the shape that is actually there by the time the pass runs, rather than the loop
913    /// closed form one, because the block loop closed form put in the way is one `simplify-cfg`
914    /// folds back out. The value is defined in the loop and named in a block the loop dominates,
915    /// which is legal and is what a `for` loop adding to a total and printing it afterwards comes
916    /// out as. The worked out total goes where the loop's own was read.
917    #[test]
918    fn a_total_read_after_the_loop_by_another_road_is_worked_out_too() {
919        let mut it = shaped(Limit::Number(1000), What::ReadAfter);
920        let stats = delete(&mut it.func, &mut Fuel::unlimited());
921        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
922        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
923        assert_eq!(loops(&it.func), 0);
924        assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the trip count");
925        assert_eq!(tally(&it.func, Opcode::Store), 1, "and the store that read it is still there");
926        sound(&it.func, &mut it.names);
927    }
928
929    /// The total the loop was going to hand over, worked out without running the loop.
930    ///
931    /// The loop adds `n` to a running total a thousand times, so the total it hands over is
932    /// `n * 1000`, and what is left of the function is that multiply. The count is 999 rather than
933    /// 1000 and the base is `n` rather than zero, because the exit edge is taken on the iteration
934    /// the test first fails and the total has already been added to by then: `n + n * 999`. Doing
935    /// the arithmetic on [`crate::scev::Invariant`] before writing anything down is what turns that
936    /// into one instruction rather than three nothing would fold, this being the last pass run.
937    #[test]
938    fn a_total_the_loop_hands_over_is_worked_out_in_front_of_it() {
939        let mut it = shaped(Limit::Number(1000), What::HandsOut);
940        let stats = delete(&mut it.func, &mut Fuel::unlimited());
941        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
942        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
943        assert_eq!(loops(&it.func), 0);
944        assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the trip count");
945        assert_eq!(tally(&it.func, Opcode::Add), 0, "and nothing to add to it");
946        assert_eq!(handed(&it.func, &it.done), Some(1000), "n times a thousand");
947        sound(&it.func, &mut it.names);
948    }
949
950    /// The same thing where the amount is one, which leaves a number rather than a multiply.
951    #[test]
952    fn a_total_that_went_up_by_one_is_left_as_a_number() {
953        let mut it = shaped(Limit::Number(1000), What::HandsOne);
954        let stats = delete(&mut it.func, &mut Fuel::unlimited());
955        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
956        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
957        assert_eq!(tally(&it.func, Opcode::Mul), 0);
958        assert_eq!(tally(&it.func, Opcode::Add), 0);
959        let handed = handed_value(&it.func, &it.done).expect("the total is handed over");
960        let (imm, ty) = crate::fold::constant(&it.func, handed).expect("and it is a number");
961        assert_eq!(imm.signed(ty), 1000);
962        sound(&it.func, &mut it.names);
963    }
964
965    /// The total handed over by a loop counting up to a value nothing here knows.
966    ///
967    /// The loop adds `n` to a running total until the counter arrives at `n`, so what it hands over
968    /// is `n + n * max(n - 1, 0)` and every part of that is written down in front of where the loop
969    /// was. The `select` is the clamp. [`crate::scev::Assumption::Entered`] says the count is
970    /// either the distance to the limit or zero, and taking the larger of the two is that
971    /// assumption paid for rather than leaned on. The sign extension in front of it is the exit
972    /// test's own reading of the value, which is `<` on signed values here.
973    #[test]
974    fn a_total_from_a_loop_counting_up_to_a_value_handed_in_is_worked_out() {
975        let mut it = shaped(Limit::Given, What::HandsOut);
976        let stats = delete(&mut it.func, &mut Fuel::unlimited());
977        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
978        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
979        assert_eq!(loops(&it.func), 0);
980        assert_eq!(tally(&it.func, Opcode::Select), 1, "the clamp at zero");
981        assert_eq!(tally(&it.func, Opcode::ICmp), 1, "and the test it picks on");
982        let read = "the count read the way the exit test read it";
983        assert_eq!(tally(&it.func, Opcode::SExt), 1, "{read}");
984        assert_eq!(tally(&it.func, Opcode::Trunc), 1, "and cut back to what the total is added in");
985        assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the count");
986        assert_eq!(tally(&it.func, Opcode::Add), 2, "the off by one on the count and the base");
987        sound(&it.func, &mut it.names);
988    }
989
990    /// The same total read after the loop rather than handed over, which is the corpus row.
991    ///
992    /// `loop-deletion.u32.1000000.unknown.read-back` is this shape, a bound nothing can see and a
993    /// total read once the loop is done. It is the row rucc ran a million times and gcc 16 ran no
994    /// times at all. The store stays and what it stores is worked out where the loop used to be.
995    #[test]
996    fn a_total_read_after_a_loop_counting_up_to_a_value_handed_in_is_worked_out() {
997        let mut it = shaped(Limit::Given, What::ReadAfter);
998        let stats = delete(&mut it.func, &mut Fuel::unlimited());
999        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
1000        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
1001        assert_eq!(loops(&it.func), 0);
1002        assert_eq!(tally(&it.func, Opcode::Select), 1, "the clamp at zero");
1003        assert_eq!(tally(&it.func, Opcode::Store), 1, "and the store that read it is still there");
1004        sound(&it.func, &mut it.names);
1005    }
1006
1007    /// A total that went up by a different amount every time is not a thing to write down.
1008    ///
1009    /// Here the total goes up by the counter rather than by a fixed amount, so what it holds after
1010    /// `k` times round is a square number and [`crate::scev`] rightly has no affine form for it.
1011    /// The loop ends and does nothing to memory, so the only thing keeping it is the total, and the
1012    /// pass says so rather than guessing at it.
1013    #[test]
1014    fn a_total_that_went_up_by_a_different_amount_each_time_leaves_the_loop_alone() {
1015        let mut it = shaped(Limit::Number(1000), What::HandsSquare);
1016        let stats = delete(&mut it.func, &mut Fuel::unlimited());
1017        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1018        assert_eq!(stats.count(Kind::Missed, NO_FORM), 1);
1019        assert_eq!(loops(&it.func), 1);
1020        sound(&it.func, &mut it.names);
1021    }
1022
1023    /// A loop that comes back but not after a number of steps anything here can work out.
1024    ///
1025    /// ```text
1026    /// entry(p, n): jump head(1)
1027    /// head(c): next = c + c; test = next < 1000; br test -> head(next), done()
1028    /// done: ret
1029    /// ```
1030    ///
1031    /// The counter doubles, so it is not a value that goes up by the same amount every time and
1032    /// there is no count to be had. It does terminate, which is the point: the pass is not allowed
1033    /// to lean on a loop looking harmless, only on the count that says it ends.
1034    fn doubling() -> Shape {
1035        let mut names = Interner::new();
1036        let signature = Signature::new().with_params(&[Type::PTR, Type::int(32)]);
1037        let mut func = Func::new(names.intern("f"), signature);
1038        let entry = func.create_block();
1039        let head = func.create_block();
1040        let done = func.create_block();
1041        func.append_param(entry, Type::PTR);
1042        func.append_param(entry, Type::int(32));
1043        let carried = func.append_param(head, Type::int(32));
1044
1045        let mut build = Builder::new(&mut func, entry);
1046        let one = build.iconst(Type::int(32), 1);
1047        build.jump(head, &[one]);
1048
1049        let mut build = Builder::new(&mut func, head);
1050        let next = build.binary(Opcode::Add, carried, carried, Flags::NSW);
1051        let stop = build.iconst(Type::int(32), 1000);
1052        let test = build.icmp(IntPred::Slt, next, stop);
1053        build.br_if(test, head, &[next], done, &[]);
1054        Builder::new(&mut func, done).ret(&[]);
1055        Shape { names, func, entry, done }
1056    }
1057
1058    #[test]
1059    fn a_loop_whose_count_is_not_known_is_left_alone() {
1060        let mut it = doubling();
1061        let stats = delete(&mut it.func, &mut Fuel::unlimited());
1062        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1063        assert_eq!(stats.count(Kind::Missed, NO_COUNT), 1);
1064        assert_eq!(loops(&it.func), 1);
1065        sound(&it.func, &mut it.names);
1066    }
1067
1068    #[test]
1069    fn the_pass_stops_when_the_fuel_runs_out() {
1070        let mut it = shaped(Limit::Number(1000), What::Nothing);
1071        let stats = delete(&mut it.func, &mut Fuel::of(0));
1072        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1073        assert_eq!(stats.count(Kind::Missed, NO_FUEL), 1);
1074        assert_eq!(loops(&it.func), 1);
1075        sound(&it.func, &mut it.names);
1076    }
1077
1078    #[test]
1079    fn a_function_with_no_body_is_not_a_problem() {
1080        let mut names = Interner::new();
1081        let mut func = Func::new(names.intern("f"), Signature::new());
1082        let stats = delete(&mut func, &mut Fuel::unlimited());
1083        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
1084    }
1085}