Skip to main content

rucc_opt/
header_copy.rs

1//! Copies a loop's header in front of the loop, so the test ends up at the bottom.
2//!
3//! Design: `spec/optimizer/26-loop-canonicalization.md` section 26.6, with 26.7 for where it sits
4//! and 26.8 for the two ways it goes wrong.
5//!
6//! [`crate::canon`] establishes the four properties every loop pass is allowed to assume and
7//! generates nothing on its own. This is the fifth property and it is the one that changes the
8//! program. A `while (c) { body }` tests at the top, so its header is a join and a branch at once
9//! and the test runs once more than the body does. Copying the header in front of the loop turns
10//! it into `if (c) { do { body } while (c); }`, which evaluates the condition exactly as often and
11//! leaves a loop whose body is a single region and whose exit test is at the bottom where the
12//! induction variable's last value is.
13//!
14//! # What it is really for
15//!
16//! Section 26.6 says the largest single benefit is not the shape. It is that after the copy the
17//! entry test stands in front of the loop where document 10's ranges can be asked about it, and
18//! where the ranges settle it the loop is known to run at least one iteration. That is what turns
19//! a trip count estimate into a bound, what lets hoisting move a computation out without proving
20//! it safe to speculate, and what saves the vectorizer a guard. So the range query is not a
21//! refinement on the copy, it is half of the reason to make it, and it happens here rather than
22//! being left to [`crate::prune`] because prune has already run by the time the loop pipeline
23//! opens.
24//!
25//! # One block, not a chain
26//!
27//! GCC copies as many blocks as its budget allows, walking down from the header while
28//! `should_duplicate_loop_header_p` keeps saying yes. This copies the header and stops. The header
29//! is where the exit test is, so one block is what the do-while form needs, and a chain buys the
30//! cases where the condition is spread over several blocks that nothing has managed to merge. The
31//! bound is the same either way and the second block can be added when the corpus says which
32//! programs want it.
33//!
34//! Copying a header could otherwise feed itself: the block the copy makes the new header of the
35//! loop may test and exit as well, and copying that one exposes a third. Every header this pass
36//! copies and every block it makes a header of are put aside, so each loop is looked at once per
37//! run and the growth is bounded by the loop count rather than by how the branches happen to nest.
38//!
39//! # Why the copy repeats nothing
40//!
41//! The copy runs exactly where the header's first execution used to, so nothing in the program
42//! happens a different number of times. That argument would let a store or a call be copied, and
43//! section 26.8 refuses both anyway, through document 17.1's whitelist, which is
44//! [`Opcode::has_effects`]. The reason to keep the refusal is that the argument above holds for
45//! one block and stops holding the moment the copy is a chain, and a pass whose correctness
46//! depends on a bound somebody may raise later is one that will be wrong later. Refusing here
47//! costs the headers with a load in them, which document 27's hoisting is the pass for.
48//!
49//! # What the copy owes the values
50//!
51//! The header used to dominate the whole loop. After the copy it does not: the body is reached
52//! from the copy as well, so a value the header defined and the body read has two definitions
53//! reaching it and needs a merge. The merge goes where the two paths meet, which is the body, as
54//! one more block parameter carrying the header's value on the back edge and the copy's on the
55//! way in.
56//!
57//! Values the header defines and something outside the loop reads are refused rather than merged.
58//! After [`crate::canon`] there are none, because loop-closed form has already routed them through
59//! the exit, so the case this declines is the one where somebody ran this pass without the
60//! canonicalizer and the answer to that is a missed optimization rather than a second merge
61//! written for a shape the pipeline does not produce.
62//!
63//! # Which level
64//!
65//! `-O1` and above at [`SPEED`]'s budget, which is GCC's twenty. `-Os` at [`SIZE`]'s, which is
66//! section 26.6's five, because the do-while form is slightly smaller in the steady state and the
67//! copy is what it costs. `-Oz` does not run it at all. Two passes rather than one with a knob,
68//! because a pass here is a name a `-f` flag spells and there is nowhere for a level to hand a
69//! pass a number.
70
71use std::collections::{HashMap, HashSet};
72
73use rucc_cost::heuristics;
74use rucc_ir::{
75    Block, BlockCall, Builder, ExtraKind, Func, Inst, InstData, Opcode, Start, Type, Value,
76    ValueList,
77};
78
79use crate::cfg::Cfg;
80use crate::dom::Dominators;
81use crate::loops::{LoopId, Loops};
82use crate::range::query::Ranges;
83use crate::{Analyses, Fuel, Pass, Preserved, Stats, prune, simplify_cfg};
84
85const COPIED: &str = "loop header copied in front of the loop so the test is at the bottom";
86const ENTERED: &str = "entry test removed, the value ranges say the loop runs";
87const SKIPPED: &str = "loop removed, the value ranges say the entry test never holds";
88const UNDECIDED: &str = "entry test kept, the value ranges do not settle whether the loop runs";
89const ALREADY: &str = "loop left as it was, it already tests at the bottom";
90const TOO_BIG: &str = "loop header not copied, it is larger than this level allows";
91const EFFECTS: &str = "loop header not copied, something in it may not be repeated";
92const SHAPE: &str = "loop header not copied, its exit is not a two way branch";
93const ESCAPES: &str = "loop header not copied, a value it defines is read outside the loop";
94const NO_PREHEADER: &str = "loop header not copied, the loop has not been canonicalized";
95const NO_FUEL: &str = "loop left as it was, the pass ran out of fuel";
96
97/// Section 26.6's transformation, at one budget.
98///
99/// The budget is a field rather than a constant because the two levels that run this want
100/// different ones, and it comes with the name for the same reason: the two instances are two
101/// entries in [`crate::pass::PASSES`] and a pipeline picks one by naming it.
102#[derive(Debug)]
103pub struct HeaderCopy {
104    /// What a `-f` flag spells.
105    name: &'static str,
106    /// How many instructions a header may hold and still be worth copying, which is
107    /// [`heuristics::LOOP_HEADER_INSNS_FOR_SPEED`] or the size one next to it.
108    budget: u32,
109}
110
111/// The instance `-O1`, `-O2` and `-O3` run, at GCC's budget.
112pub static SPEED: HeaderCopy =
113    HeaderCopy { name: "header-copy", budget: heuristics::LOOP_HEADER_INSNS_FOR_SPEED };
114
115/// The instance `-Os` runs, at section 26.6's smaller one.
116pub static SIZE: HeaderCopy =
117    HeaderCopy { name: "header-copy-small", budget: heuristics::LOOP_HEADER_INSNS_FOR_SIZE };
118
119impl Pass for HeaderCopy {
120    fn name(&self) -> &'static str {
121        self.name
122    }
123
124    fn describe(&self) -> &'static str {
125        "copies a loop header in front of the loop, turning a while into a do-while"
126    }
127
128    fn preserves(&self) -> Preserved {
129        // A block appears, two edges become four, and the body grows a parameter the loop carries.
130        Preserved::NONE
131    }
132
133    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
134        let mut stats = Stats::new();
135        if func.entry().is_none() {
136            return stats;
137        }
138        let mut done = HashSet::new();
139        let mut say = true;
140        let mut dry = false;
141        loop {
142            let jobs = self.plan(func, an, &done, &mut stats, say);
143            say = false;
144            if jobs.is_empty() {
145                break;
146            }
147            let mut copies = Vec::with_capacity(jobs.len());
148            for job in &jobs {
149                if !fuel.take() {
150                    stats.missed(NO_FUEL);
151                    dry = true;
152                    break;
153                }
154                done.insert(job.header);
155                done.insert(job.body);
156                copies.push(apply(func, job));
157                stats.optimized(COPIED);
158            }
159            an.clear();
160            if settle(func, an, &copies, &mut stats) {
161                an.clear();
162            }
163            if dry {
164                break;
165            }
166        }
167        if stats.changed() {
168            // Section 6.5 leaves the stranded blocks to whoever stranded them, and a loop whose
169            // entry test the ranges disproved is a loop nothing reaches any more.
170            simplify_cfg::sweep(func, an, &mut stats);
171        }
172        an.clear();
173        stats
174    }
175}
176
177/// One loop to copy the header of, worked out against the function as it stands.
178#[derive(Debug)]
179struct Job {
180    /// The loop, which is what [`independent`] asks about to decide whether two jobs meet.
181    id: LoopId,
182    /// The block holding the exit test.
183    header: Block,
184    /// The one block outside the loop the header is reached from.
185    entry: Block,
186    /// The header's successor inside the loop, which the copy makes the new header.
187    ///
188    /// The other one, which is where the loop leaves from, is not recorded. The copy branches to
189    /// both by copying the header's own terminator, and nothing after that has a question to ask
190    /// about the one that goes out.
191    body: Block,
192    /// The values the header defines and the rest of the loop reads, which need a merge at
193    /// [`Job::body`] once there are two ways to get there.
194    carried: Vec<Value>,
195}
196
197/// One loop the cheap checks accepted, waiting on the walk that says what it carries.
198#[derive(Debug)]
199struct Candidate {
200    /// The loop.
201    id: LoopId,
202    /// The block holding the exit test.
203    header: Block,
204    /// The one block outside the loop the header is reached from.
205    entry: Block,
206    /// The header's successor inside the loop.
207    body: Block,
208    /// Everything the header defines, which [`carried`] sorts into what the loop reads and what
209    /// nothing does.
210    defined: Vec<Value>,
211}
212
213impl HeaderCopy {
214    /// Every loop worth copying the header of that can be copied without looking again.
215    ///
216    /// A round rather than one at a time. A copy changes the shape of the loop it is made for, so
217    /// the forest this was read out of is wrong about that loop afterwards, and the answer used to
218    /// be to rebuild the graph, the dominator tree and the forest and ask again. On a function with
219    /// sixteen hundred loops that is two thousand rebuilds, which was most of what an optimized
220    /// build of tamnd/rucc#1086's test spent its time on. What the rebuild protects is one loop's
221    /// shape, so this takes the loops whose shapes do not touch and copies all of their headers
222    /// from the one look. [`independent`] is the argument for why that is the same edit.
223    ///
224    /// `say` is false on every call after the first so that a loop this declines is declined once
225    /// rather than once per round.
226    fn plan(
227        &self,
228        func: &Func,
229        an: &mut Analyses,
230        done: &HashSet<Block>,
231        stats: &mut Stats,
232        say: bool,
233    ) -> Vec<Job> {
234        let (cfg, dom, loops) = (an.cfg(func), an.dominators(func), an.loops(func));
235        let mut wanted = Vec::new();
236        for id in loops.all() {
237            let header = loops.header(id);
238            if done.contains(&header) {
239                continue;
240            }
241            match self.consider(func, cfg, loops, id, header) {
242                Ok(candidate) => wanted.push(candidate),
243                Err(why) if say && why == ALREADY => stats.note(ALREADY),
244                Err(why) if say => stats.missed(why),
245                Err(_) => (),
246            }
247        }
248        let jobs = carried(func, dom, loops, wanted, stats, say);
249        independent(loops, jobs)
250    }
251
252    /// Whether this loop can have its header copied, and why not when it cannot.
253    ///
254    /// Everything here is answered out of the loop itself. What the header defines and who reads it
255    /// is the one question that is about the whole function, and [`carried`] asks it for all the
256    /// candidates at once.
257    fn consider(
258        &self,
259        func: &Func,
260        cfg: &Cfg,
261        loops: &Loops,
262        id: LoopId,
263        header: Block,
264    ) -> Result<Candidate, &'static str> {
265        let leaves = cfg.successors(header).iter().any(|&to| !loops.contains(id, to));
266        if !leaves {
267            // The exit test is somewhere below, which is the shape this pass is trying to reach.
268            // GCC asks the same question the other way round in `do_while_loop_p`.
269            return Err(ALREADY);
270        }
271        let entry = loops.preheader(cfg, id).ok_or(NO_PREHEADER)?;
272        let term = func.terminator(header).ok_or(SHAPE)?;
273        if func[term].opcode != Opcode::BrIf {
274            return Err(SHAPE);
275        }
276        let calls: Vec<BlockCall> = func.successors(term).collect();
277        let [then_call, else_call] = calls[..].try_into().map_err(|_| SHAPE)?;
278        let body = match (loops.contains(id, then_call.block), loops.contains(id, else_call.block))
279        {
280            (true, false) => then_call.block,
281            (false, true) => else_call.block,
282            _ => return Err(SHAPE),
283        };
284        if body == header {
285            return Err(SHAPE);
286        }
287        let insts: Vec<Inst> = func.insts(header).filter(|&inst| inst != term).collect();
288        if insts.len() > self.budget as usize {
289            return Err(TOO_BIG);
290        }
291        for &inst in &insts {
292            if !repeatable(func, inst) {
293                return Err(EFFECTS);
294            }
295        }
296        let mut defined: Vec<Value> = func[header].params.clone();
297        for &inst in &insts {
298            defined.extend(func[inst].results());
299        }
300        Ok(Candidate { id, header, entry, body, defined })
301    }
302}
303
304/// Whether an instruction may stand in a second copy of the block it is in.
305///
306/// Two questions rather than one. [`Opcode::has_effects`] is document 17.1's whitelist and is what
307/// section 26.8 names. The second is about this pass rather than about the program: an instruction
308/// carrying a side table entry is copied here by copying the index, which is right for an
309/// immediate, a symbol and a comparison because those tables are written once and read for ever,
310/// and is not something to assume about a table nobody has checked. So the copy is restricted to
311/// the payloads it has been thought about, and an instruction with any other is declined the same
312/// way one with an effect is.
313pub(crate) fn repeatable(func: &Func, inst: Inst) -> bool {
314    let data = func[inst];
315    if data.opcode.has_effects() || func.carries_mem(inst) {
316        return false;
317    }
318    matches!(
319        data.extra.kind(),
320        ExtraKind::None
321            | ExtraKind::Imm
322            | ExtraKind::Symbol
323            | ExtraKind::IntPred
324            | ExtraKind::FloatPred
325    )
326}
327
328/// Turns the candidates into jobs by working out, for each, which of the values its header defines
329/// the rest of its loop reads.
330///
331/// Those are what the copy owes a merge at the body. A value read outside the loop takes the
332/// candidate out rather than joining the list, because merging it would need a second parameter at
333/// the exit and after [`crate::canon`] there is no such value to merge: loop-closed form has already
334/// routed it.
335///
336/// One walk of the function for every candidate at once. tamnd/rucc#1015 made this one walk per
337/// candidate instead of one per value, and tamnd/rucc#1086 is the same move one level up: a header
338/// defines a handful of values, the function it is in can be very large, and a function with sixteen
339/// hundred loops in it was paying for sixteen hundred walks per round. A value belongs to exactly
340/// one candidate, because two candidates are two loops and two loops have two headers, so one map
341/// from value to candidate is enough to share the walk.
342fn carried(
343    func: &Func,
344    dom: &Dominators,
345    loops: &Loops,
346    wanted: Vec<Candidate>,
347    stats: &mut Stats,
348    say: bool,
349) -> Vec<Job> {
350    let mut watched: HashMap<Value, usize> = HashMap::new();
351    for (which, candidate) in wanted.iter().enumerate() {
352        for &value in &candidate.defined {
353            watched.insert(value, which);
354        }
355    }
356    let mut read: Vec<HashSet<Value>> = vec![HashSet::new(); wanted.len()];
357    let mut escapes = vec![false; wanted.len()];
358    let mut names: Vec<usize> = Vec::new();
359    for block in func.blocks() {
360        names.clear();
361        for inst in func.insts(block) {
362            reads(func, inst, block, &wanted, &watched, &mut read, &mut names);
363        }
364        for &which in &names {
365            let candidate = &wanted[which];
366            if !loops.contains(candidate.id, block) || !dom.dominates(candidate.body, block) {
367                escapes[which] = true;
368            }
369        }
370    }
371    let mut jobs = Vec::new();
372    for (which, candidate) in wanted.into_iter().enumerate() {
373        if escapes[which] {
374            if say {
375                stats.missed(ESCAPES);
376            }
377            continue;
378        }
379        let taken = &read[which];
380        let carried = candidate.defined.into_iter().filter(|value| taken.contains(value)).collect();
381        jobs.push(Job {
382            id: candidate.id,
383            header: candidate.header,
384            entry: candidate.entry,
385            body: candidate.body,
386            carried,
387        });
388    }
389    jobs
390}
391
392/// Records every watched value this instruction names, as an operand or on an edge out of it, and
393/// notes which candidates the block named something of.
394///
395/// Which candidates rather than which values, because a block that reads one of these from the wrong
396/// place is an error for that candidate whichever of its values it read. A candidate's own header is
397/// left out on both counts: the header is where these values are defined and reading one there is
398/// neither a carry nor an escape.
399fn reads(
400    func: &Func,
401    inst: Inst,
402    block: Block,
403    wanted: &[Candidate],
404    watched: &HashMap<Value, usize>,
405    read: &mut [HashSet<Value>],
406    names: &mut Vec<usize>,
407) {
408    let mut note = |value: Value| {
409        let Some(&which) = watched.get(&value) else { return };
410        if block == wanted[which].header {
411            return;
412        }
413        read[which].insert(value);
414        if !names.contains(&which) {
415            names.push(which);
416        }
417    };
418    for &value in &func[func[inst].args] {
419        note(value);
420    }
421    for call in func.successors(inst) {
422        for &value in &func[call.args] {
423            note(value);
424        }
425    }
426}
427
428/// The jobs out of a round that may all be applied before the function is looked at again.
429///
430/// Two jobs are safe together when the loops they are about share no block and neither loop holds
431/// the other's preheader. The argument is that a job writes only inside its own loop and to its own
432/// preheader. The copy is a new block put on the edge into the header, and the only block outside
433/// the loop it edits is the preheader, whose one successor is the header by the definition
434/// [`Loops::preheader`] uses. The merge gives the body a parameter and hands a value over on every
435/// edge into the body, and every one of those edges comes from inside the loop, because a natural
436/// loop is entered at its header alone and the body is not the header. The rewrite that follows
437/// reaches only the blocks that read the carried values, and [`carried`] has already taken the job
438/// out if any of those is outside the loop. So two jobs whose loops and preheaders do not meet edit
439/// two disjoint sets of blocks, and applying both from one look at the function is the same function
440/// as applying one, looking again, and applying the other.
441///
442/// Loops that share a block at all are nested, so the loops a taken one rules out are the ones it is
443/// nested in and the ones nested in it.
444fn independent(loops: &Loops, jobs: Vec<Job>) -> Vec<Job> {
445    let mut blocked = vec![false; loops.count()];
446    let mut taken = vec![false; loops.count()];
447    let mut kept: Vec<Job> = Vec::new();
448    for job in jobs {
449        if blocked[job.id.index()] || inside(loops, &taken, job.entry) {
450            continue;
451        }
452        let mut up = Some(job.id);
453        while let Some(id) = up {
454            blocked[id.index()] = true;
455            up = loops.parent(id);
456        }
457        let mut down = vec![job.id];
458        while let Some(id) = down.pop() {
459            blocked[id.index()] = true;
460            down.extend(loops.children(id));
461        }
462        // And no later job may be about a loop this one's preheader sits in.
463        let mut around = loops.innermost(job.entry);
464        while let Some(id) = around {
465            blocked[id.index()] = true;
466            around = loops.parent(id);
467        }
468        taken[job.id.index()] = true;
469        kept.push(job);
470    }
471    kept
472}
473
474/// Whether any loop holding this block has been taken already.
475fn inside(loops: &Loops, taken: &[bool], block: Block) -> bool {
476    let mut walk = loops.innermost(block);
477    while let Some(id) = walk {
478        if taken[id.index()] {
479            return true;
480        }
481        walk = loops.parent(id);
482    }
483    false
484}
485
486/// Makes the copy, puts it on the edge into the loop, and returns it.
487fn apply(func: &mut Func, job: &Job) -> Block {
488    let term = func.terminator(job.header).expect("the plan read this terminator");
489    let entry_term = func.terminator(job.entry).expect("a preheader ends in a jump");
490    // The header's parameters stand for whatever the one edge in hands them, so the copy is
491    // written in terms of those arguments and needs no parameters of its own.
492    let incoming = edge_args(func, entry_term, job.header);
493    let mut map: HashMap<Value, Value> = HashMap::new();
494    for (&param, &arg) in func[job.header].params.clone().iter().zip(&incoming) {
495        map.insert(param, arg);
496    }
497    let copy = func.create_block();
498    let insts: Vec<Inst> = func.insts(job.header).filter(|&inst| inst != term).collect();
499    for inst in insts {
500        clone_into(func, copy, inst, &mut map);
501    }
502    clone_branch(func, copy, term, &map);
503    for at in func.target_list(entry_term).iter() {
504        let call = func[at];
505        if call.block == job.header {
506            func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY, ..call });
507        }
508    }
509    for &value in &job.carried {
510        let arrived = map.get(&value).copied().unwrap_or(value);
511        merge(func, job, copy, value, arrived);
512    }
513    copy
514}
515
516/// The arguments a terminator hands one of its targets.
517fn edge_args(func: &Func, term: Inst, to: Block) -> Vec<Value> {
518    for call in func.successors(term) {
519        if call.block == to {
520            return func[call.args].to_vec();
521        }
522    }
523    Vec::new()
524}
525
526/// Copies one instruction to the end of a block, under the substitution, and records its results.
527pub(crate) fn clone_into(
528    func: &mut Func,
529    into: Block,
530    inst: Inst,
531    map: &mut HashMap<Value, Value>,
532) {
533    let data = func[inst];
534    let args: Vec<Value> =
535        func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
536    let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
537    let span = func.span(inst);
538    let args = func.push_values(&args);
539    let fresh = func.create_inst(InstData { args, ..data }, &types, span);
540    func.append_inst(into, fresh);
541    for (old, new) in data.results().zip(func[fresh].results()) {
542        map.insert(old, new);
543    }
544}
545
546/// Copies the header's two way branch to the end of the copy, under the substitution.
547///
548/// The targets are the header's own. What that means for the graph is that the copy decides,
549/// before the loop, which of the two places the header would have gone control goes to, and the
550/// header is left deciding it for every iteration after the first.
551fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
552    let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
553    let cond = at(&func[func[term].args][0]);
554    let calls: Vec<BlockCall> = func.successors(term).collect();
555    let args: Vec<Vec<Value>> =
556        calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
557    Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
558}
559
560/// Gives the body a parameter for a value the header defines, and points the loop at it.
561///
562/// Three kinds of edge arrive at the body once the copy is in place. The header's carries what the
563/// header worked out, which is the value on every iteration after the first. The copy's carries
564/// what the copy worked out, which is the value on the first. Anything else is a block inside the
565/// loop, and what is current there is the parameter itself, which is available because the body
566/// dominates the whole loop the moment the copy is the only way in.
567fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
568    let param = func.append_param(job.body, func[value].ty);
569    for block in func.blocks().collect::<Vec<_>>() {
570        let Some(term) = func.terminator(block) else { continue };
571        let carry = if block == job.header {
572            value
573        } else if block == copy {
574            arrived
575        } else {
576            param
577        };
578        for at in func.target_list(term).iter() {
579            let call = func[at];
580            if call.block != job.body {
581                continue;
582            }
583            let args = func.append_arg(call.args, carry);
584            func.set_block_call(at, BlockCall { args, ..call });
585        }
586    }
587    // The names go where the readers go. The header still has the value and the rest of the loop
588    // has the parameter, so a declaration named on the value is the parameter as well, and a start
589    // anywhere but the header is a place the declaration was given the parameter. Left on the
590    // value, it would say the declaration holds whatever the header is handed once the loop has
591    // turned round, which is the next value rather than this one. tamnd/rucc#1810.
592    for decl in func.value_decls(value).collect::<Vec<u32>>() {
593        func.declare_value(param, decl);
594    }
595    let elsewhere: Vec<Start> = func
596        .value_starts(value)
597        .filter(|&start| {
598            func.start_place(start).map_or(start.block, |(block, _)| block) != job.header
599        })
600        .collect();
601    func.move_starts(value, param, &elsewhere);
602    // Everything the header used to reach reads the parameter now. The header itself does not:
603    // what it hands the body is still its own definition, and that is the edge the parameter was
604    // put there to distinguish.
605    for block in func.blocks().collect::<Vec<_>>() {
606        if block == job.header || block == copy {
607            continue;
608        }
609        for inst in func.insts(block).collect::<Vec<_>>() {
610            let swap = |had: Value| if had == value { param } else { had };
611            func.rewrite(func[inst].args, swap);
612            for at in func.target_list(inst).iter() {
613                func.rewrite(func[at].args, swap);
614            }
615        }
616    }
617}
618
619/// Asks the ranges whether the copied tests are settled, and takes out the ones that are.
620///
621/// This is section 26.6's point about the entry condition. A test that always holds leaves a loop
622/// known to run at least once, which is what document 07.5's trip count wanted. One that never
623/// holds leaves the loop unreachable, and taking it out is [`crate::simplify_cfg::sweep`]'s job
624/// rather than this one's.
625///
626/// Every copy the round made is asked from the one set of ranges, and the answers are acted on
627/// afterwards. That is sound because every question is about a different block's terminator and
628/// every answer is a fact about the values arriving there, which taking a branch out somewhere else
629/// cannot make untrue. Asking one at a time would mean a graph and a dominator tree per copy, which
630/// is the cost tamnd/rucc#1086 is about.
631///
632/// Answers whether it moved an edge, which the caller needs because the analyses this built are the
633/// ones the next round wants and they are only stale if a branch came out. The ranges settle the
634/// test on a minority of the loops here and the graph is the size of the function, so the rounds
635/// where nothing happens used to pay for a rebuild that changed nothing. tamnd/rucc#1045.
636fn settle(func: &mut Func, an: &mut Analyses, copies: &[Block], stats: &mut Stats) -> bool {
637    let mut out: Vec<(Inst, BlockCall, bool)> = Vec::new();
638    {
639        let cfg = an.cfg(func);
640        let dom = an.dominators(func);
641        let mut ranges = Ranges::new(func, cfg, dom);
642        for &copy in copies {
643            let Some(term) = func.terminator(copy) else { continue };
644            let cond = func[func[term].args][0];
645            let Some(taken) = prune::settled(func, &mut ranges, copy, cond) else {
646                stats.missed(UNDECIDED);
647                continue;
648            };
649            let calls: Vec<BlockCall> = func.successors(term).collect();
650            out.push((term, if taken { calls[0] } else { calls[1] }, taken));
651        }
652    }
653    if out.is_empty() {
654        return false;
655    }
656    for (term, call, taken) in out {
657        simplify_cfg::jump_to(func, term, call);
658        stats.optimized(if taken { ENTERED } else { SKIPPED });
659    }
660    true
661}
662
663#[cfg(test)]
664mod tests {
665    use rucc_base::Interner;
666    use rucc_ir::{
667        Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
668        Signature, Start, Type, verify_func,
669    };
670    use rucc_target::{TargetInfo, Triple};
671
672    use super::{HeaderCopy, SIZE, SPEED};
673    use crate::canon::Canon;
674    use crate::cfg::Cfg;
675    use crate::dom::Dominators;
676    use crate::loops::Loops;
677    use crate::stats::Kind;
678    use crate::{Fuel, Pass, Stats};
679
680    /// Canonicalizes and then copies, with as much fuel as both want.
681    ///
682    /// Both, because the pass is written against the shape [`Canon`] leaves and running it over
683    /// anything else is a test of a situation the pipeline does not produce. Section 26.7 puts the
684    /// two next to each other in that order and so does this.
685    fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
686        let mut an = crate::machine::fixtures::analyses();
687        Canon.run(func, &mut an, &mut Fuel::unlimited());
688        pass.run(func, &mut an, &mut Fuel::unlimited())
689    }
690
691    /// The forest of the function as it is now.
692    fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
693        let cfg = Cfg::new(func);
694        let dom = Dominators::new(&cfg);
695        let loops = Loops::new(&cfg, &dom);
696        (cfg, dom, loops)
697    }
698
699    /// Insists the function is one the rest of the compiler may believe.
700    ///
701    /// This is where most of the strength of these tests is. The copy gives the loop a second way
702    /// in, which is exactly the edit that breaks a definition's dominance over its uses, and the
703    /// verifier is what says whether the merge the pass wrote is the merge the graph needed.
704    fn sound(func: &Func, names: &mut Interner) {
705        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
706        let module = Module::new(names.intern("t.c"), &target);
707        if let Err(errors) = verify_func(&module, func, names) {
708            panic!("{errors:#?}");
709        }
710    }
711
712    /// A counted loop that tests at the top, which is what `while (i < n)` lowers to.
713    ///
714    /// ```text
715    /// entry: i0 = 0; jump head(i0)
716    /// head(i): t = i < n; br t -> body, done
717    /// body: next = i + 1; jump head(next)
718    /// done: ret i
719    /// ```
720    ///
721    /// `bound` is the limit as a constant, or nothing for a limit the function was handed and
722    /// which the ranges therefore cannot settle.
723    fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
724        let mut names = Interner::new();
725        let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
726        let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
727        let mut func = Func::new(names.intern("f"), signature);
728        let entry = func.create_block();
729        let head = func.create_block();
730        let body = func.create_block();
731        let done = func.create_block();
732        let limit = match bound {
733            Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
734            None => func.append_param(entry, Type::int(32)),
735        };
736        let i = func.append_param(head, Type::int(32));
737        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
738        Builder::new(&mut func, entry).jump(head, &[zero]);
739        let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
740        Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
741        let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
742        let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
743        Builder::new(&mut func, body).jump(head, &[next]);
744        Builder::new(&mut func, done).ret(&[i]);
745        (func, names, vec![entry, head, body, done])
746    }
747
748    /// Whether the header of the only loop leaves it, which is the question section 26.6 is about.
749    fn tests_at_the_top(func: &Func) -> bool {
750        let (cfg, dom, loops) = forest(func);
751        let _ = dom;
752        let id = loops.all().next().expect("there is a loop");
753        let header = loops.header(id);
754        cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
755    }
756
757    #[test]
758    fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
759        let (mut func, mut names, _) = counted(None);
760        assert!(tests_at_the_top(&func), "the shape this pass is for");
761
762        let stats = copied(&mut func, &SPEED);
763        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
764        assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
765        sound(&func, &mut names);
766    }
767
768    #[test]
769    fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
770        let (mut func, mut names, blocks) = counted(None);
771        let body = blocks[2];
772        assert!(func[body].params.is_empty(), "the body carries nothing to start with");
773
774        copied(&mut func, &SPEED);
775        assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
776        assert_eq!(
777            Cfg::new(&func).predecessors(body).len(),
778            2,
779            "one edge from the header and one from the copy"
780        );
781        sound(&func, &mut names);
782    }
783
784    /// `int j = i;` at the top of the body is a place `j` was given the counter, and once the body
785    /// has a parameter of its own that place is where `j` was given the parameter. The header is
786    /// handed the next value when the loop comes round, so a start left on its value would follow
787    /// that one instead.
788    #[test]
789    fn a_name_on_the_value_the_body_now_takes_as_a_parameter_goes_with_it() {
790        let (mut func, mut names, blocks) = counted(None);
791        let (head, body) = (blocks[1], blocks[2]);
792        let i = func[head].params[0];
793        let test = func.insts(head).next();
794        func.declare_value(i, 3);
795        func.declare_value_from(i, Start { decl: 4, block: body, after: None });
796        func.declare_value_from(i, Start { decl: 5, block: head, after: test });
797
798        copied(&mut func, &SPEED);
799        let param = func[body].params[0];
800        assert_eq!(func.value_decls(param).collect::<Vec<u32>>(), vec![3]);
801        assert_eq!(func.value_decls(i).collect::<Vec<u32>>(), vec![3], "still the header's");
802        let decls = |value| func.value_starts(value).map(|start| start.decl).collect::<Vec<u32>>();
803        assert_eq!(decls(param), vec![4], "the body's start is on the body's parameter");
804        assert_eq!(decls(i), vec![5], "and the header's stays on the header's value");
805        sound(&func, &mut names);
806    }
807
808    #[test]
809    fn an_entry_test_the_ranges_settle_is_taken_out() {
810        let (mut func, mut names, _) = counted(Some(10));
811
812        let stats = copied(&mut func, &SPEED);
813        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
814        assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
815        assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
816        sound(&func, &mut names);
817
818        let (cfg, _dom, loops) = forest(&func);
819        let id = loops.all().next().expect("the loop is still there");
820        let entry = func.entry().expect("there is an entry");
821        assert!(cfg.reaches(loops.header(id)), "and it is still reached");
822        assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
823    }
824
825    #[test]
826    fn a_loop_the_ranges_say_never_runs_is_removed() {
827        let (mut func, mut names, blocks) = counted(Some(0));
828
829        let stats = copied(&mut func, &SPEED);
830        assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
831        sound(&func, &mut names);
832
833        let (_cfg, _dom, loops) = forest(&func);
834        assert_eq!(loops.count(), 0, "there is no loop left");
835        assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
836    }
837
838    #[test]
839    fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
840        let (mut func, _names, _) = counted(None);
841
842        let stats = copied(&mut func, &SPEED);
843        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
844        assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
845        assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
846    }
847
848    #[test]
849    fn a_second_run_changes_nothing() {
850        let (mut func, mut names, _) = counted(None);
851        copied(&mut func, &SPEED);
852        let again =
853            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
854        assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
855        assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
856        sound(&func, &mut names);
857    }
858
859    #[test]
860    fn a_header_that_writes_to_memory_is_left_alone() {
861        let mut names = Interner::new();
862        let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
863        let mut func = Func::new(names.intern("f"), signature);
864        let entry = func.create_block();
865        let head = func.create_block();
866        let body = func.create_block();
867        let done = func.create_block();
868        let limit = func.append_param(entry, Type::int(32));
869        let addr = func.append_param(entry, Type::PTR);
870        let i = func.append_param(head, Type::int(32));
871        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
872        Builder::new(&mut func, entry).jump(head, &[zero]);
873        let access = MemInfo {
874            size: 4,
875            align: 4,
876            order: MemOrder::NotAtomic,
877            tbaa: None,
878            owns: 0,
879            restrict: Restrict::NONE,
880        };
881        Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
882        let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
883        Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
884        let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
885        let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
886        Builder::new(&mut func, body).jump(head, &[next]);
887        Builder::new(&mut func, done).ret(&[]);
888
889        let stats = copied(&mut func, &SPEED);
890        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
891        assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
892        assert!(tests_at_the_top(&func), "the loop is exactly as it was");
893    }
894
895    #[test]
896    fn a_header_larger_than_the_level_allows_is_left_alone() {
897        // Seven instructions in the header, which is over the size budget and well under the
898        // speed one, so the two instances of the pass disagree about the same function.
899        let stats = copied(&mut padded(6), &SIZE);
900        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
901        assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
902
903        let stats = copied(&mut padded(6), &SPEED);
904        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
905    }
906
907    /// The counted loop with that many more instructions in its header, which do nothing.
908    fn padded(extra: usize) -> Func {
909        let (mut func, _names, blocks) = counted(None);
910        let head = blocks[1];
911        let term = func.terminator(head).expect("the header branches");
912        for _ in 0..extra {
913            let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
914            let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
915            func.remove_inst(inst);
916            func.insert_before(inst, term);
917        }
918        func
919    }
920
921    #[test]
922    fn fuel_stops_the_copy_where_it_stands() {
923        let (mut func, _names, _) = counted(None);
924        let mut an = crate::machine::fixtures::analyses();
925        Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
926
927        let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
928        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
929        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
930        assert!(tests_at_the_top(&func), "and the loop is as it was");
931    }
932
933    #[test]
934    fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
935        // Straight to the copy, so loop-closed form has not been established and the counter the
936        // return names is still the header's own definition. That is the one value this pass will
937        // not merge, and section 26.7's answer is that [`Canon`] has already routed it by the time
938        // the pipeline gets here.
939        let (mut func, _names, _) = counted(None);
940
941        let stats =
942            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
943        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
944        assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
945        assert!(tests_at_the_top(&func), "and the loop is as it was");
946    }
947
948    #[test]
949    fn a_loop_with_no_preheader_is_declined() {
950        let mut names = Interner::new();
951        let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
952        let mut func = Func::new(names.intern("f"), signature);
953        let entry = func.create_block();
954        let one = func.create_block();
955        let two = func.create_block();
956        let head = func.create_block();
957        let body = func.create_block();
958        let done = func.create_block();
959        let c = func.append_param(entry, Type::int(1));
960        let limit = func.append_param(entry, Type::int(32));
961        let i = func.append_param(head, Type::int(32));
962        Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
963        let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
964        Builder::new(&mut func, one).jump(head, &[zero]);
965        let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
966        Builder::new(&mut func, two).jump(head, &[start]);
967        let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
968        Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
969        Builder::new(&mut func, body).jump(head, &[i]);
970        Builder::new(&mut func, done).ret(&[]);
971
972        let stats =
973            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
974        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
975        assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
976
977        // And with one, which is what the pipeline hands it, the same loop is copied.
978        let stats = copied(&mut func, &SPEED);
979        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
980        sound(&func, &mut names);
981    }
982
983    /// Two counted loops one after the other, which share no block and no preheader.
984    ///
985    /// ```text
986    /// entry(n): jump one(0)
987    /// one(i):  t = i < n; br t -> up, mid
988    /// up:      i2 = i + 1; jump one(i2)
989    /// mid:     jump two(0)
990    /// two(j):  u = j < n; br u -> down, done
991    /// down:    j2 = j + 1; jump two(j2)
992    /// done:    ret
993    /// ```
994    fn side_by_side() -> (Func, Interner, Vec<Block>) {
995        let mut names = Interner::new();
996        let signature = Signature::new().with_params(&[Type::int(32)]);
997        let mut func = Func::new(names.intern("f"), signature);
998        let entry = func.create_block();
999        let one = func.create_block();
1000        let up = func.create_block();
1001        let mid = func.create_block();
1002        let two = func.create_block();
1003        let down = func.create_block();
1004        let done = func.create_block();
1005        let n = func.append_param(entry, Type::int(32));
1006        let i = func.append_param(one, Type::int(32));
1007        let j = func.append_param(two, Type::int(32));
1008        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1009        Builder::new(&mut func, entry).jump(one, &[zero]);
1010        let t = Builder::new(&mut func, one).icmp(IntPred::Slt, i, n);
1011        Builder::new(&mut func, one).br_if(t, up, &[], mid, &[]);
1012        let step = Builder::new(&mut func, up).iconst(Type::int(32), 1);
1013        let next = Builder::new(&mut func, up).binary(Opcode::Add, i, step, Flags::NONE);
1014        Builder::new(&mut func, up).jump(one, &[next]);
1015        let start = Builder::new(&mut func, mid).iconst(Type::int(32), 0);
1016        Builder::new(&mut func, mid).jump(two, &[start]);
1017        let u = Builder::new(&mut func, two).icmp(IntPred::Slt, j, n);
1018        Builder::new(&mut func, two).br_if(u, down, &[], done, &[]);
1019        let stride = Builder::new(&mut func, down).iconst(Type::int(32), 1);
1020        let after = Builder::new(&mut func, down).binary(Opcode::Add, j, stride, Flags::NONE);
1021        Builder::new(&mut func, down).jump(two, &[after]);
1022        Builder::new(&mut func, done).ret(&[]);
1023        (func, names, vec![up, down])
1024    }
1025
1026    #[test]
1027    fn two_loops_that_do_not_meet_are_both_copied() {
1028        let (mut func, mut names, bodies) = side_by_side();
1029
1030        let stats = copied(&mut func, &SPEED);
1031        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1032        sound(&func, &mut names);
1033
1034        let (_cfg, _dom, loops) = forest(&func);
1035        assert_eq!(loops.count(), 2, "both loops are still loops");
1036        for id in loops.all() {
1037            let header = loops.header(id);
1038            assert!(
1039                !Cfg::new(&func).successors(header).iter().any(|&to| !loops.contains(id, to)),
1040                "and neither of them tests at the top any more"
1041            );
1042        }
1043        for body in bodies {
1044            assert_eq!(func[body].params.len(), 1, "each body carries its own counter");
1045        }
1046    }
1047
1048    /// A counted loop with a counted loop inside it, where the inner preheader is an outer block.
1049    ///
1050    /// ```text
1051    /// entry(n): jump outer(0)
1052    /// outer(i): t = i < n; br t -> ahead, done
1053    /// ahead:    jump inner(0)
1054    /// inner(j): u = j < n; br u -> under, latch
1055    /// under:    j2 = j + 1; jump inner(j2)
1056    /// latch:    i2 = i + 1; jump outer(i2)
1057    /// done:     ret
1058    /// ```
1059    fn nested() -> (Func, Interner) {
1060        let mut names = Interner::new();
1061        let signature = Signature::new().with_params(&[Type::int(32)]);
1062        let mut func = Func::new(names.intern("f"), signature);
1063        let entry = func.create_block();
1064        let outer = func.create_block();
1065        let ahead = func.create_block();
1066        let inner = func.create_block();
1067        let under = func.create_block();
1068        let latch = func.create_block();
1069        let done = func.create_block();
1070        let n = func.append_param(entry, Type::int(32));
1071        let i = func.append_param(outer, Type::int(32));
1072        let j = func.append_param(inner, Type::int(32));
1073        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1074        Builder::new(&mut func, entry).jump(outer, &[zero]);
1075        let t = Builder::new(&mut func, outer).icmp(IntPred::Slt, i, n);
1076        Builder::new(&mut func, outer).br_if(t, ahead, &[], done, &[]);
1077        let start = Builder::new(&mut func, ahead).iconst(Type::int(32), 0);
1078        Builder::new(&mut func, ahead).jump(inner, &[start]);
1079        let u = Builder::new(&mut func, inner).icmp(IntPred::Slt, j, n);
1080        Builder::new(&mut func, inner).br_if(u, under, &[], latch, &[]);
1081        let stride = Builder::new(&mut func, under).iconst(Type::int(32), 1);
1082        let after = Builder::new(&mut func, under).binary(Opcode::Add, j, stride, Flags::NONE);
1083        Builder::new(&mut func, under).jump(inner, &[after]);
1084        let step = Builder::new(&mut func, latch).iconst(Type::int(32), 1);
1085        let next = Builder::new(&mut func, latch).binary(Opcode::Add, i, step, Flags::NONE);
1086        Builder::new(&mut func, latch).jump(outer, &[next]);
1087        Builder::new(&mut func, done).ret(&[]);
1088        (func, names)
1089    }
1090
1091    #[test]
1092    fn a_loop_and_the_loop_inside_it_are_copied_one_round_apart() {
1093        // The two share every block the inner one has, and the inner one's preheader is a block of
1094        // the outer one, so a round may hold at most one of them. Both are still copied, and the
1095        // point of the test is that the second is planned against a function the first has already
1096        // changed rather than against the plan the first was made from.
1097        let (mut func, mut names) = nested();
1098
1099        let stats = copied(&mut func, &SPEED);
1100        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1101        sound(&func, &mut names);
1102
1103        let (cfg, _dom, loops) = forest(&func);
1104        assert_eq!(loops.count(), 2, "both loops survived the copy");
1105        for id in loops.all() {
1106            let header = loops.header(id);
1107            assert!(
1108                !cfg.successors(header).iter().any(|&to| !loops.contains(id, to)),
1109                "and both test at the bottom now"
1110            );
1111        }
1112    }
1113
1114    #[test]
1115    fn a_round_stops_where_the_fuel_does() {
1116        // Two loops a round may hold together, and one unit of fuel. The first is copied and the
1117        // second is left for a run with more, which is what taking fuel per job rather than per
1118        // round means.
1119        let (mut func, mut names) = {
1120            let (mut func, names, _) = side_by_side();
1121            let mut an = crate::machine::fixtures::analyses();
1122            Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
1123            (func, names)
1124        };
1125
1126        let stats =
1127            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1128        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
1129        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1130        sound(&func, &mut names);
1131    }
1132}