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