Skip to main content

rucc_opt/
loop_delete.rs

1//! Takes out a loop that runs a known number of times, working out what it left behind.
2//!
3//! Design: `spec/optimizer/17-dce.md` for what makes a thing removable and
4//! `spec/optimizer/28-induction-variables.md` for where the trip count comes from. This is
5//! 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 trip count that is a number rather than an estimate. The count is
19//! what says the loop terminates, which is the third question and the one a person is most likely
20//! to forget: a loop that computes nothing and never comes back still cannot be taken out, because
21//! not coming back is what it does. [`crate::scev::Bound::under_undefined_overflow`] is the same
22//! accessor [`crate::unroll`] reads for the same reason, and section 7.5's distinction between a
23//! bound and an estimate is exactly this: an estimate decides whether a transformation pays and a
24//! bound decides what the program does.
25//!
26//! Every instruction inside has to be one whose not happening nothing can tell. That is the
27//! predicate [`crate::dce`] already has, so it is read from there rather than written again, and
28//! it means a plain load may be inside the loop and a `volatile` one may not. A call is allowed
29//! when the purity analysis says it reads memory at most and comes back, which is the same rule
30//! that lets a call whose result nothing reads go.
31//!
32//! # What the loop leaves behind
33//!
34//! A loop whose total somebody reads afterwards is a loop that hands something over, and most
35//! loops worth writing are that kind. Sometimes what it hands over can be worked out without
36//! running it. A value that goes up by the same amount every time round is `{base, +, step}` in
37//! [`crate::scev`]'s terms, the loop is left on the iteration the exit test first fails, and that
38//! iteration's number is the count, so what the loop leaves behind is `base + step * count`. The
39//! preheader works that out in one go and hands it over instead, and then nothing outside reads
40//! anything the loop computed and the loop goes.
41//!
42//! Handing it over is two different edits, because a value defined in the loop reaches the code
43//! after it by two different roads. It may be an argument on the edge out, landing in a parameter
44//! of the block the loop leaves to, which is the shape [`crate::canon`] puts things in. Or the
45//! block after the loop may simply name it, which is legal wherever the definition dominates the
46//! use and is what is actually there by the time this runs, since the block loop closed form put in
47//! the way is one [`crate::simplify_cfg`] has every reason to fold away again. So both are looked
48//! for, and a use of the second kind is rewritten where it stands.
49//!
50//! No overflow argument is needed for this and it is worth saying why, because the neighbouring
51//! transformation in section 28.4 does need one. A value that steps by a fixed amount evolves in
52//! its own type, which is to say modulo two to the width, and addition modulo two to the width is
53//! associative, so adding `step` to `base` `count` times and working out `base + step * count` the
54//! same way are the same number whatever either of them does to the top bit. Section 28.4's rewrite
55//! is a different claim, that one comparison holds exactly where another does, and that one does
56//! turn on whether the limit overflows. So the arithmetic written here carries neither `nsw` nor
57//! `nuw`, and the promise the loop's own increment carried is not copied onto it, because that
58//! promise is about the sequence and says nothing about this.
59//!
60//! What is written down is the whole expression rather than three instructions to be folded later,
61//! because this pass is the last one in the pipeline and there is no later. `base` and `step` are
62//! both [`crate::scev::Invariant`], which is `value * scale + offset` with the arithmetic on it
63//! already, so `base + step * count` is worked out in that form first and only what is left of it
64//! reaches the function. A loop adding one a million times leaves a constant behind and a loop
65//! adding an invariant `n` a million times leaves one multiply.
66//!
67//! It is only done when it lets the loop go, which is a cost rule rather than a correctness one.
68//! Writing the final value down where the loop stays behind costs a multiply in the preheader and
69//! saves nothing, because the loop still carries the value round its own back edge and nothing in
70//! rucc yet takes out a block parameter whose only reader is the argument it passes to itself.
71//! [`crate::dce`]'s own notes call that out as a transformation worth having and a different one
72//! from what it does. When there is one, this gate is the thing to reconsider.
73//!
74//! # What it does
75//!
76//! Works out in the preheader whatever the loop was going to leave behind, puts those values where
77//! the loop's own were read, points the preheader at the block the loop left to with whatever the
78//! edge out was already carrying from outside, and lets the sweep in [`crate::simplify_cfg`] take
79//! the blocks nothing reaches. Every value named in any of it is asserted to dominate the preheader
80//! rather than assumed to: a value defined outside the loop that reaches the exit test has to
81//! dominate the preheader, and an assertion is cheaper than being wrong about why.
82
83use std::collections::{HashMap, HashSet};
84
85use rucc_ir::{Block, Builder, Func, Inst, InstData, Opcode, Type, Value};
86
87use crate::cfg::Cfg;
88use crate::dom::Dominators;
89use crate::loops::{LoopId, Loops};
90use crate::purity::Facts;
91use crate::scev::{Bound, Count, Invariant, Scev};
92use crate::{Analyses, Fuel, Pass, Preserved, Stats};
93
94const DELETED: &str = "loop taken out, it runs a known number of times and leaves nothing behind";
95const WRITTEN: &str = "what the loop was going to leave behind worked out in front of it instead";
96const NO_COUNT: &str = "loop left as it was, how many times it runs is not a number known here";
97const SHAPE: &str =
98    "loop left as it was, it has no preheader or it leaves from more than one place";
99const EFFECTS: &str = "loop left as it was, something in it does more than work out a value";
100const NO_FORM: &str =
101    "loop left as it was, what it leaves behind is not a thing this can work out in front of it";
102const ENTRIES: &str = "loop left as it was, it is reached somewhere other than at its header";
103const NO_FUEL: &str = "loop left as it was, the pass ran out of fuel";
104
105/// Section 17's dead code elimination, asked about a loop rather than about an instruction.
106#[derive(Debug)]
107pub struct LoopDelete;
108
109impl Pass for LoopDelete {
110    fn name(&self) -> &'static str {
111        "loop-delete"
112    }
113
114    fn describe(&self) -> &'static str {
115        "a loop that runs a known number of times and leaves nothing behind is taken out"
116    }
117
118    fn preserves(&self) -> Preserved {
119        // The loop goes, and its blocks with it.
120        Preserved::NONE
121    }
122
123    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
124        let mut stats = Stats::new();
125        if func.entry().is_none() {
126            return stats;
127        }
128        let mut done: HashSet<Block> = HashSet::new();
129        let mut say = true;
130        while let Some(job) = plan(func, an, &done, &mut stats, say) {
131            say = false;
132            if !fuel.take() {
133                stats.missed(NO_FUEL);
134                break;
135            }
136            done.insert(job.header);
137            for _ in 0..apply(func, &job) {
138                stats.optimized(WRITTEN);
139            }
140            stats.optimized(DELETED);
141            an.clear();
142            crate::simplify_cfg::sweep(func, an, &mut stats);
143        }
144        an.clear();
145        stats
146    }
147}
148
149/// One loop to take out, worked out against the function as it stands.
150#[derive(Debug)]
151struct Job {
152    /// The block the loop is entered at, which is what says which loop this was.
153    header: Block,
154    /// The one block outside the loop with an edge to the header.
155    preheader: Block,
156    /// The block outside the loop the one exit edge arrives at.
157    exit: Block,
158    /// The blocks the loop is made of, which is what says which uses are the ones outside it.
159    inside: HashSet<Block>,
160    /// What the edge out was going to carry, which the preheader carries instead.
161    args: Vec<Value>,
162    /// Every value the loop defines that anything outside it reads, and what it ends up holding.
163    ends: Vec<(Value, Leaves)>,
164}
165
166/// What a value the loop defines holds by the time anything outside it looks.
167#[derive(Clone, Copy, Debug)]
168struct Leaves {
169    /// The type it evolved in, which is the type the arithmetic is done in.
170    ty: Type,
171    /// `base + step * count`, as far as it goes without writing anything down.
172    end: Invariant,
173}
174
175/// The innermost loop that can go, and what it would take.
176///
177/// One at a time, for the reason [`crate::unroll::plan`] takes one at a time: taking a loop out
178/// invalidates the forest the next answer would be read out of. `say` is false after the first
179/// round so that a loop this declines is declined once rather than once per round.
180fn plan(
181    func: &Func,
182    an: &mut Analyses,
183    done: &HashSet<Block>,
184    stats: &mut Stats,
185    say: bool,
186) -> Option<Job> {
187    let facts = an.purity();
188    let cfg = an.cfg(func);
189    let doms = an.dominators(func);
190    let loops = an.loops(func);
191    let mut scev = Scev::new(func, cfg, loops);
192    let mut found: Option<(u32, Job)> = None;
193    for id in loops.all() {
194        if done.contains(&loops.header(id)) {
195            continue;
196        }
197        match consider(func, cfg, doms, loops, facts, &mut scev, id) {
198            Ok(job) => {
199                let depth = loops.depth(id);
200                if found.as_ref().is_none_or(|(had, _)| depth > *had) {
201                    found = Some((depth, job));
202                }
203            }
204            Err(why) if say => stats.missed(why),
205            Err(_) => (),
206        }
207    }
208    found.map(|(_, job)| job)
209}
210
211/// Whether this loop can go, and why not when it cannot.
212fn consider(
213    func: &Func,
214    cfg: &Cfg,
215    doms: &Dominators,
216    loops: &Loops,
217    facts: &Facts,
218    scev: &mut Scev<'_>,
219    id: LoopId,
220) -> Result<Job, &'static str> {
221    let header = loops.header(id);
222    let preheader = loops.preheader(cfg, id).ok_or(SHAPE)?;
223    let [only] = loops.exits(id) else {
224        return Err(SHAPE);
225    };
226
227    let blocks = loops.blocks(id).to_vec();
228    let inside: HashSet<Block> = blocks.iter().copied().collect();
229    for &block in &blocks {
230        // The same reducibility check unrolling makes. A block of the loop reached from outside
231        // the loop is a region this has no right to reason about as one piece.
232        if block != header && cfg.predecessors(block).iter().any(|at| !inside.contains(at)) {
233            return Err(ENTRIES);
234        }
235        for inst in func.insts(block) {
236            if func.is_terminator(inst) {
237                // A terminator that leaves the function or goes somewhere worked out at run time
238                // is not an edge the forest accounted for, so the one exit counted above is not
239                // the only way out.
240                if !matches!(func[inst].opcode, Opcode::Jump | Opcode::BrIf) {
241                    return Err(EFFECTS);
242                }
243                continue;
244            }
245            if !crate::dce::removable(func, inst, facts) {
246                return Err(EFFECTS);
247            }
248        }
249    }
250    // The count is what says the loop comes back. A loop that computes nothing and runs forever
251    // still does something, which is run forever. A count worked out from a value the loop does
252    // not change says that as well as a number does: whatever that value is, the loop gets to it,
253    // and nothing in here needs to know how many steps that took.
254    let count =
255        scev.bound(id).as_ref().and_then(Bound::under_undefined_overflow).ok_or(NO_COUNT)?;
256
257    let term = func.terminator(only.from).ok_or(SHAPE)?;
258    let leaving = func.successors(term).find(|call| call.block == only.to).ok_or(SHAPE)?;
259    let args = func[leaving.args].to_vec();
260
261    let mut wanted = read_outside(func, &blocks, &inside);
262    for &arg in &args {
263        if loops.is_invariant(func, id, arg) {
264            debug_assert!(
265                doms.dominates(defined_in(func, arg), preheader),
266                "a value outside the loop that reaches the exit test dominates the preheader"
267            );
268            continue;
269        }
270        if !wanted.contains(&arg) {
271            wanted.push(arg);
272        }
273    }
274
275    let mut ends = Vec::with_capacity(wanted.len());
276    for value in wanted {
277        let end = ending(func, scev, id, value, count).ok_or(NO_FORM)?;
278        debug_assert!(
279            named(end).is_none_or(|on| doms.dominates(defined_in(func, on), preheader)),
280            "a value the loop does not change is defined outside it and so dominates the preheader"
281        );
282        ends.push((value, end));
283    }
284    Ok(Job { header, preheader, exit: only.to, inside, args, ends })
285}
286
287/// Every value the loop defines that a block outside it names, in the order they turn up.
288///
289/// [`crate::unroll::escapes`] asks whether there is one of these and stops there, because a loop
290/// with one is a loop it will not copy. Here they are the work rather than the reason to stop, so
291/// the answer has to be which ones.
292fn read_outside(func: &Func, blocks: &[Block], inside: &HashSet<Block>) -> Vec<Value> {
293    let mut defined: HashSet<Value> = HashSet::new();
294    for &block in blocks {
295        defined.extend(func[block].params.iter().copied());
296        for inst in func.insts(block) {
297            defined.extend(func[inst].results());
298        }
299    }
300    let mut found = Vec::new();
301    for block in func.blocks() {
302        if inside.contains(&block) {
303            continue;
304        }
305        for inst in func.insts(block) {
306            let reads = func[func[inst].args].iter().copied();
307            let passes = func.successors(inst).flat_map(|call| func[call.args].to_vec());
308            for value in reads.chain(passes) {
309                if defined.contains(&value) && !found.contains(&value) {
310                    found.push(value);
311                }
312            }
313        }
314    }
315    found
316}
317
318/// What the loop leaves in a value it hands over, or `None` when that is not a thing to write down.
319///
320/// The count has to be a number here rather than an expression, which is a narrower rule than the
321/// one the loop itself is kept under. A count worked out from something the loop does not change
322/// says the loop ends, which is all the loop needs, but multiplying by it means writing it down in
323/// the counter's own type and under the reading its exit test took, and getting that wrong turns a
324/// loop over three billion elements into a loop that runs no times. Section 7.7's warning is about
325/// exactly that, so until there is a reason to, this takes the count it can count.
326fn ending(
327    func: &Func,
328    scev: &mut Scev<'_>,
329    id: LoopId,
330    value: Value,
331    count: Count,
332) -> Option<Leaves> {
333    let Count::Exact(trips) = count else {
334        return None;
335    };
336    let trips = i128::try_from(trips).ok()?;
337    let chrec = scev.evolution(id, value).chrec()?;
338    let end = chrec.step.times(Invariant::number(trips)).and_then(|all| chrec.base.plus(all))?;
339    // Asked before anything is written, so that a refusal is a refusal rather than a preheader with
340    // half an expression in it. There is no undo here and there should not need to be.
341    let plain = end.plain()?;
342    if plain.read.is_some() {
343        return None;
344    }
345    if plain.value.is_some_and(|named| func[named].ty != chrec.ty) {
346        return None;
347    }
348    Some(Leaves { ty: chrec.ty, end })
349}
350
351/// The one value an expression is built on, when it is built on one.
352fn named(leaves: Leaves) -> Option<Value> {
353    leaves.end.plain().and_then(|plain| plain.value.filter(|_| plain.scale != 0))
354}
355
356/// The block a value is defined in.
357fn defined_in(func: &Func, value: Value) -> Block {
358    match func[value].def {
359        rucc_ir::Def::Result { inst, .. } => {
360            func.block_of(inst).expect("a value in use is defined in a block")
361        }
362        rucc_ir::Def::Param { block, .. } => block,
363    }
364}
365
366/// Points the preheader past the loop, working out on the way what the loop was going to leave.
367///
368/// Answers how many of those there were, which is what the report counts.
369fn apply(func: &mut Func, job: &Job) -> usize {
370    let term = func.terminator(job.preheader).expect("a preheader ends in a jump to the header");
371    let mut instead: HashMap<Value, Value> = HashMap::new();
372    for &(value, leaves) in &job.ends {
373        let worked = write(func, term, leaves.ty, leaves.end);
374        instead.insert(value, worked);
375    }
376    swap_in(func, job, &instead);
377    let args: Vec<Value> =
378        job.args.iter().map(|arg| instead.get(arg).copied().unwrap_or(*arg)).collect();
379    func.remove_inst(term);
380    Builder::new(func, job.preheader).jump(job.exit, &args);
381    instead.len()
382}
383
384/// Puts the worked out values where the loop's own were read.
385///
386/// Only outside the loop, because inside it the loop's own values are still the right answer right
387/// up until the blocks go. The preheader is outside and gets walked with the rest, which is
388/// harmless and better than a special case: what was just written into it names nothing the loop
389/// defines.
390fn swap_in(func: &mut Func, job: &Job, instead: &HashMap<Value, Value>) {
391    if instead.is_empty() {
392        return;
393    }
394    let outside: Vec<Block> = func.blocks().filter(|at| !job.inside.contains(at)).collect();
395    for block in outside {
396        for inst in func.insts(block).collect::<Vec<_>>() {
397            let mut lists = vec![func[inst].args];
398            lists.extend(func.successors(inst).map(|call| call.args));
399            for list in lists {
400                func.rewrite(list, |value| instead.get(&value).copied().unwrap_or(value));
401            }
402        }
403    }
404}
405
406/// Works an expression out in front of an instruction.
407///
408/// `value * scale + offset`, with the parts that are nothing left out, so a scale of one is no
409/// multiply and an offset of zero is no add and an expression built on no value at all is one
410/// constant. That is what makes a loop adding one a million times leave a number behind rather than
411/// three instructions nothing is going to fold, this being the last pass there is.
412fn write(func: &mut Func, before: Inst, ty: Type, end: Invariant) -> Value {
413    let plain = end.plain().expect("consider refused anything this cannot write");
414    let Some(value) = plain.value.filter(|_| plain.scale != 0) else {
415        return crate::ivopts::number(func, before, ty, plain.offset);
416    };
417    let mut so_far = value;
418    if plain.scale != 1 {
419        let by = crate::ivopts::number(func, before, ty, plain.scale);
420        so_far = arith(func, before, Opcode::Mul, so_far, by, ty);
421    }
422    if plain.offset != 0 {
423        let by = crate::ivopts::number(func, before, ty, plain.offset);
424        so_far = arith(func, before, Opcode::Add, so_far, by, ty);
425    }
426    so_far
427}
428
429/// One arithmetic instruction, worked out in front of another one and promising nothing.
430///
431/// Neither `nsw` nor `nuw`, which is the point rather than an omission. The module notes say why:
432/// what the loop did is the same arithmetic modulo two to the width as many times as it ran, and
433/// `base + step * count` worked out the same way is the same number. A flag the loop's own
434/// increment carried is a fact about that sequence, and putting it here would be inventing one.
435fn arith(
436    func: &mut Func,
437    before: Inst,
438    opcode: Opcode,
439    left: Value,
440    right: Value,
441    ty: Type,
442) -> Value {
443    let span = func.span(before);
444    let args = func.push_values(&[left, right]);
445    let inst = func.create_inst(InstData { args, ..InstData::new(opcode) }, &[ty], span);
446    func.insert_before(inst, before);
447    func[inst].first_result.expect("one result was asked for")
448}
449
450#[cfg(test)]
451mod tests {
452    use rucc_base::Interner;
453    use rucc_ir::{
454        Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
455        Signature, Type, Value, verify_func,
456    };
457    use rucc_target::{TargetInfo, Triple};
458
459    use super::{DELETED, EFFECTS, LoopDelete, NO_COUNT, NO_FORM, NO_FUEL, WRITTEN};
460    use crate::stats::Kind;
461    use crate::{Fuel, Pass, Stats};
462
463    /// Runs the pass over the function as it stands.
464    fn delete(func: &mut Func, fuel: &mut Fuel) -> Stats {
465        LoopDelete.run(func, &mut crate::machine::fixtures::analyses(), fuel)
466    }
467
468    /// Insists the function is one the rest of the compiler may believe.
469    ///
470    /// Pointing a block at a different successor is the edit that hands a block the wrong number
471    /// of arguments and strands a definition its uses still name, so this is where most of the
472    /// strength of these tests is.
473    fn sound(func: &Func, names: &mut Interner) {
474        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
475        let module = Module::new(names.intern("t.c"), &target);
476        if let Err(errors) = verify_func(&module, func, names) {
477            panic!("{errors:#?}");
478        }
479    }
480
481    /// How many instructions of that opcode the whole function holds.
482    fn tally(func: &Func, opcode: Opcode) -> usize {
483        func.blocks()
484            .flat_map(|block| func.insts(block))
485            .filter(|&inst| func[inst].opcode == opcode)
486            .count()
487    }
488
489    /// The one value now handed to the block the loop used to leave to.
490    fn handed_value(func: &Func, done: &Block) -> Option<Value> {
491        let cfg = crate::cfg::Cfg::new(func);
492        let [only] = cfg.predecessors(*done) else {
493            return None;
494        };
495        let term = func.terminator(*only)?;
496        let call = func.successors(term).find(|call| call.block == *done)?;
497        let args = func[call.args].to_vec();
498        let [arg] = args[..] else {
499            return None;
500        };
501        Some(arg)
502    }
503
504    /// What the value handed over is a multiple of, when it is a multiple of something.
505    fn handed(func: &Func, done: &Block) -> Option<i128> {
506        let value = handed_value(func, done)?;
507        let Def::Result { inst, .. } = func[value].def else {
508            return None;
509        };
510        if func[inst].opcode != Opcode::Mul {
511            return None;
512        }
513        let args = func[func[inst].args].to_vec();
514        let (imm, ty) = crate::fold::constant(func, args[1])?;
515        Some(imm.signed(ty))
516    }
517
518    /// How many loops are left.
519    fn loops(func: &Func) -> usize {
520        let cfg = crate::cfg::Cfg::new(func);
521        let doms = crate::dom::Dominators::new(&cfg);
522        crate::loops::Loops::new(&cfg, &doms).count()
523    }
524
525    /// A four byte write with nothing said about what it aliases.
526    fn plain() -> MemInfo {
527        MemInfo {
528            size: 4,
529            align: 4,
530            order: MemOrder::NotAtomic,
531            tbaa: None,
532            owns: 0,
533            restrict: Restrict::NONE,
534        }
535    }
536
537    /// What the limit of the exit test is.
538    enum Limit {
539        /// A number written in the program.
540        Number(i128),
541        /// A value the function was handed, which the loop does not change.
542        Given,
543    }
544
545    /// What the loop does, which is the whole of what decides whether it can go.
546    #[derive(Clone, Copy, PartialEq)]
547    enum What {
548        /// Adds up a number nothing ever reads.
549        Nothing,
550        /// Writes each running total to the pointer it was handed.
551        Writes,
552        /// Hands the block it leaves to a total that went up by the same amount every time.
553        HandsOut,
554        /// Hands out a total that went up by one every time, so there is no multiply to write.
555        HandsOne,
556        /// Hands out a total that went up by a different amount every time round.
557        HandsSquare,
558        /// Leaves its total to be read after the loop by a road other than the edge out.
559        ReadAfter,
560    }
561
562    impl What {
563        /// Whether the total goes out on the edge the loop leaves by.
564        fn hands_out(self) -> bool {
565            matches!(self, What::HandsOut | What::HandsOne | What::HandsSquare)
566        }
567    }
568
569    struct Shape {
570        names: Interner,
571        func: Func,
572        entry: Block,
573        done: Block,
574    }
575
576    /// A counted loop in the shape `crate::canon` and `crate::header_copy` leave a `for` in.
577    ///
578    /// ```text
579    /// entry(p, n): jump head(0, 0)
580    /// head(i, sum): jump body(i, sum)
581    /// body(c, r): total = r + n; next = c + 1; test = next < limit
582    ///             br test -> head(next, total), done()
583    /// done: ret
584    /// ```
585    ///
586    /// Two blocks in the loop rather than one, so that taking it out has more than one block to get
587    /// rid of, and a running total carried round, so that there is something inside worth asking
588    /// whether anybody reads. The total goes up by `n` each time round, which is the shape of
589    /// `total += seed` in the issue: a value that goes up by the same amount every time, where the
590    /// amount is not a number anything here knows.
591    fn shaped(limit: Limit, what: What) -> Shape {
592        let mut names = Interner::new();
593        let signature = Signature::new().with_params(&[Type::PTR, Type::int(32)]);
594        let mut func = Func::new(names.intern("f"), signature);
595        let entry = func.create_block();
596        let head = func.create_block();
597        let body = func.create_block();
598        let done = func.create_block();
599        let place = func.append_param(entry, Type::PTR);
600        let given = func.append_param(entry, Type::int(32));
601        let i = func.append_param(head, Type::int(32));
602        let sum = func.append_param(head, Type::int(32));
603        let carried = func.append_param(body, Type::int(32));
604        let running = func.append_param(body, Type::int(32));
605        if what.hands_out() {
606            func.append_param(done, Type::int(32));
607        }
608
609        let mut build = Builder::new(&mut func, entry);
610        let zero = build.iconst(Type::int(32), 0);
611        build.jump(head, &[zero, zero]);
612        Builder::new(&mut func, head).jump(body, &[i, sum]);
613
614        let mut build = Builder::new(&mut func, body);
615        let one = build.iconst(Type::int(32), 1);
616        let by = match what {
617            What::HandsOne => one,
618            What::HandsSquare => carried,
619            _ => given,
620        };
621        let total = build.binary(Opcode::Add, running, by, Flags::NSW);
622        if what == What::Writes {
623            build.store(total, place, plain(), Flags::NONE);
624        }
625        let next = build.binary(Opcode::Add, carried, one, Flags::NSW);
626        let stop = match limit {
627            Limit::Number(n) => build.iconst(Type::int(32), n),
628            Limit::Given => given,
629        };
630        let test = build.icmp(IntPred::Slt, next, stop);
631        let out: Vec<Value> = if what.hands_out() { vec![total] } else { Vec::new() };
632        build.br_if(test, head, &[next, total], done, &out);
633        let mut build = Builder::new(&mut func, done);
634        if what == What::ReadAfter {
635            build.store(total, place, plain(), Flags::NONE);
636        }
637        build.ret(&[]);
638        Shape { names, func, entry, done }
639    }
640
641    #[test]
642    fn a_loop_that_leaves_nothing_behind_is_taken_out() {
643        let mut it = shaped(Limit::Number(1000), What::Nothing);
644        let stats = delete(&mut it.func, &mut Fuel::unlimited());
645        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
646        assert_eq!(loops(&it.func), 0);
647        assert_eq!(tally(&it.func, Opcode::Add), 0, "the counter and the total go with it");
648        assert_eq!(tally(&it.func, Opcode::BrIf), 0);
649        sound(&it.func, &mut it.names);
650    }
651
652    #[test]
653    fn the_blocks_it_took_out_are_swept_rather_than_left_unreachable() {
654        let mut it = shaped(Limit::Number(1000), What::Nothing);
655        delete(&mut it.func, &mut Fuel::unlimited());
656        let left: Vec<Block> = it.func.blocks().collect();
657        assert_eq!(left, vec![it.entry, it.done], "the header and the body are gone");
658        sound(&it.func, &mut it.names);
659    }
660
661    /// A count that rests on more than the front end already promised is not a proof.
662    ///
663    /// The loop here counts up to a value handed to the function, and the bound for it comes back
664    /// [`crate::scev::Count::Symbolic`] with two assumptions on it rather than one. The overflow
665    /// one the front end already promised. [`crate::scev::Assumption::Approaching`] it did not:
666    /// nothing here has shown the counter lands on that limit rather than stepping past it. So the
667    /// pass is not entitled to say the loop ends, and a loop that might not end is a loop that does
668    /// something. Reading the count through [`crate::scev::Bound::under_undefined_overflow`] is
669    /// what makes that the answer, rather than a thing this pass would have to check for itself.
670    ///
671    /// A symbolic count with nothing but the overflow assumption on it is fine and would be taken.
672    /// This is about which assumptions are left, not about the count being a number.
673    #[test]
674    fn a_count_that_rests_on_more_than_signed_overflow_is_not_enough() {
675        let mut it = shaped(Limit::Given, What::Nothing);
676        let stats = delete(&mut it.func, &mut Fuel::unlimited());
677        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
678        assert_eq!(stats.count(Kind::Missed, NO_COUNT), 1);
679        assert_eq!(loops(&it.func), 1);
680        sound(&it.func, &mut it.names);
681    }
682
683    #[test]
684    fn a_loop_that_writes_to_memory_is_left_alone() {
685        let mut it = shaped(Limit::Number(1000), What::Writes);
686        let stats = delete(&mut it.func, &mut Fuel::unlimited());
687        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
688        assert_eq!(stats.count(Kind::Missed, EFFECTS), 1);
689        assert_eq!(loops(&it.func), 1);
690        assert_eq!(tally(&it.func, Opcode::Store), 1);
691        sound(&it.func, &mut it.names);
692    }
693
694    /// A total read after the loop without going through a parameter of the block that reads it.
695    ///
696    /// This is the shape that is actually there by the time the pass runs, rather than the loop
697    /// closed form one, because the block loop closed form put in the way is one `simplify-cfg`
698    /// folds back out. The value is defined in the loop and named in a block the loop dominates,
699    /// which is legal and is what a `for` loop adding to a total and printing it afterwards comes
700    /// out as. The worked out total goes where the loop's own was read.
701    #[test]
702    fn a_total_read_after_the_loop_by_another_road_is_worked_out_too() {
703        let mut it = shaped(Limit::Number(1000), What::ReadAfter);
704        let stats = delete(&mut it.func, &mut Fuel::unlimited());
705        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
706        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
707        assert_eq!(loops(&it.func), 0);
708        assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the trip count");
709        assert_eq!(tally(&it.func, Opcode::Store), 1, "and the store that read it is still there");
710        sound(&it.func, &mut it.names);
711    }
712
713    /// The total the loop was going to hand over, worked out without running the loop.
714    ///
715    /// The loop adds `n` to a running total a thousand times, so the total it hands over is
716    /// `n * 1000`, and what is left of the function is that multiply. The count is 999 rather than
717    /// 1000 and the base is `n` rather than zero, because the exit edge is taken on the iteration
718    /// the test first fails and the total has already been added to by then: `n + n * 999`. Doing
719    /// the arithmetic on [`crate::scev::Invariant`] before writing anything down is what turns that
720    /// into one instruction rather than three nothing would fold, this being the last pass run.
721    #[test]
722    fn a_total_the_loop_hands_over_is_worked_out_in_front_of_it() {
723        let mut it = shaped(Limit::Number(1000), What::HandsOut);
724        let stats = delete(&mut it.func, &mut Fuel::unlimited());
725        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
726        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
727        assert_eq!(loops(&it.func), 0);
728        assert_eq!(tally(&it.func, Opcode::Mul), 1, "one multiply, by the trip count");
729        assert_eq!(tally(&it.func, Opcode::Add), 0, "and nothing to add to it");
730        assert_eq!(handed(&it.func, &it.done), Some(1000), "n times a thousand");
731        sound(&it.func, &mut it.names);
732    }
733
734    /// The same thing where the amount is one, which leaves a number rather than a multiply.
735    #[test]
736    fn a_total_that_went_up_by_one_is_left_as_a_number() {
737        let mut it = shaped(Limit::Number(1000), What::HandsOne);
738        let stats = delete(&mut it.func, &mut Fuel::unlimited());
739        assert_eq!(stats.count(Kind::Optimized, DELETED), 1);
740        assert_eq!(stats.count(Kind::Optimized, WRITTEN), 1);
741        assert_eq!(tally(&it.func, Opcode::Mul), 0);
742        assert_eq!(tally(&it.func, Opcode::Add), 0);
743        let handed = handed_value(&it.func, &it.done).expect("the total is handed over");
744        let (imm, ty) = crate::fold::constant(&it.func, handed).expect("and it is a number");
745        assert_eq!(imm.signed(ty), 1000);
746        sound(&it.func, &mut it.names);
747    }
748
749    /// A total that went up by a different amount every time is not a thing to write down.
750    ///
751    /// Here the total goes up by the counter rather than by a fixed amount, so what it holds after
752    /// `k` times round is a square number and [`crate::scev`] rightly has no affine form for it.
753    /// The loop ends and does nothing to memory, so the only thing keeping it is the total, and the
754    /// pass says so rather than guessing at it.
755    #[test]
756    fn a_total_that_went_up_by_a_different_amount_each_time_leaves_the_loop_alone() {
757        let mut it = shaped(Limit::Number(1000), What::HandsSquare);
758        let stats = delete(&mut it.func, &mut Fuel::unlimited());
759        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
760        assert_eq!(stats.count(Kind::Missed, NO_FORM), 1);
761        assert_eq!(loops(&it.func), 1);
762        sound(&it.func, &mut it.names);
763    }
764
765    /// A loop that comes back but not after a number of steps anything here can work out.
766    ///
767    /// ```text
768    /// entry(p, n): jump head(1)
769    /// head(c): next = c + c; test = next < 1000; br test -> head(next), done()
770    /// done: ret
771    /// ```
772    ///
773    /// The counter doubles, so it is not a value that goes up by the same amount every time and
774    /// there is no count to be had. It does terminate, which is the point: the pass is not allowed
775    /// to lean on a loop looking harmless, only on the count that says it ends.
776    fn doubling() -> Shape {
777        let mut names = Interner::new();
778        let signature = Signature::new().with_params(&[Type::PTR, Type::int(32)]);
779        let mut func = Func::new(names.intern("f"), signature);
780        let entry = func.create_block();
781        let head = func.create_block();
782        let done = func.create_block();
783        func.append_param(entry, Type::PTR);
784        func.append_param(entry, Type::int(32));
785        let carried = func.append_param(head, Type::int(32));
786
787        let mut build = Builder::new(&mut func, entry);
788        let one = build.iconst(Type::int(32), 1);
789        build.jump(head, &[one]);
790
791        let mut build = Builder::new(&mut func, head);
792        let next = build.binary(Opcode::Add, carried, carried, Flags::NSW);
793        let stop = build.iconst(Type::int(32), 1000);
794        let test = build.icmp(IntPred::Slt, next, stop);
795        build.br_if(test, head, &[next], done, &[]);
796        Builder::new(&mut func, done).ret(&[]);
797        Shape { names, func, entry, done }
798    }
799
800    #[test]
801    fn a_loop_whose_count_is_not_known_is_left_alone() {
802        let mut it = doubling();
803        let stats = delete(&mut it.func, &mut Fuel::unlimited());
804        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
805        assert_eq!(stats.count(Kind::Missed, NO_COUNT), 1);
806        assert_eq!(loops(&it.func), 1);
807        sound(&it.func, &mut it.names);
808    }
809
810    #[test]
811    fn the_pass_stops_when_the_fuel_runs_out() {
812        let mut it = shaped(Limit::Number(1000), What::Nothing);
813        let stats = delete(&mut it.func, &mut Fuel::of(0));
814        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
815        assert_eq!(stats.count(Kind::Missed, NO_FUEL), 1);
816        assert_eq!(loops(&it.func), 1);
817        sound(&it.func, &mut it.names);
818    }
819
820    #[test]
821    fn a_function_with_no_body_is_not_a_problem() {
822        let mut names = Interner::new();
823        let mut func = Func::new(names.intern("f"), Signature::new());
824        let stats = delete(&mut func, &mut Fuel::unlimited());
825        assert_eq!(stats.count(Kind::Optimized, DELETED), 0);
826    }
827}