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.
313fn 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.
527fn clone_into(func: &mut Func, into: Block, inst: Inst, map: &mut HashMap<Value, Value>) {
528    let data = func[inst];
529    let args: Vec<Value> =
530        func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
531    let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
532    let span = func.span(inst);
533    let args = func.push_values(&args);
534    let fresh = func.create_inst(InstData { args, ..data }, &types, span);
535    func.append_inst(into, fresh);
536    for (old, new) in data.results().zip(func[fresh].results()) {
537        map.insert(old, new);
538    }
539}
540
541/// Copies the header's two way branch to the end of the copy, under the substitution.
542///
543/// The targets are the header's own. What that means for the graph is that the copy decides,
544/// before the loop, which of the two places the header would have gone control goes to, and the
545/// header is left deciding it for every iteration after the first.
546fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
547    let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
548    let cond = at(&func[func[term].args][0]);
549    let calls: Vec<BlockCall> = func.successors(term).collect();
550    let args: Vec<Vec<Value>> =
551        calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
552    Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
553}
554
555/// Gives the body a parameter for a value the header defines, and points the loop at it.
556///
557/// Three kinds of edge arrive at the body once the copy is in place. The header's carries what the
558/// header worked out, which is the value on every iteration after the first. The copy's carries
559/// what the copy worked out, which is the value on the first. Anything else is a block inside the
560/// loop, and what is current there is the parameter itself, which is available because the body
561/// dominates the whole loop the moment the copy is the only way in.
562fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
563    let param = func.append_param(job.body, func[value].ty);
564    for block in func.blocks().collect::<Vec<_>>() {
565        let Some(term) = func.terminator(block) else { continue };
566        let carry = if block == job.header {
567            value
568        } else if block == copy {
569            arrived
570        } else {
571            param
572        };
573        for at in func.target_list(term).iter() {
574            let call = func[at];
575            if call.block != job.body {
576                continue;
577            }
578            let args = func.append_arg(call.args, carry);
579            func.set_block_call(at, BlockCall { args, ..call });
580        }
581    }
582    // The names go where the readers go. The header still has the value and the rest of the loop
583    // has the parameter, so a declaration named on the value is the parameter as well, and a start
584    // anywhere but the header is a place the declaration was given the parameter. Left on the
585    // value, it would say the declaration holds whatever the header is handed once the loop has
586    // turned round, which is the next value rather than this one. tamnd/rucc#1810.
587    for decl in func.value_decls(value).collect::<Vec<u32>>() {
588        func.declare_value(param, decl);
589    }
590    let elsewhere: Vec<Start> = func
591        .value_starts(value)
592        .filter(|&start| {
593            func.start_place(start).map_or(start.block, |(block, _)| block) != job.header
594        })
595        .collect();
596    func.move_starts(value, param, &elsewhere);
597    // Everything the header used to reach reads the parameter now. The header itself does not:
598    // what it hands the body is still its own definition, and that is the edge the parameter was
599    // put there to distinguish.
600    for block in func.blocks().collect::<Vec<_>>() {
601        if block == job.header || block == copy {
602            continue;
603        }
604        for inst in func.insts(block).collect::<Vec<_>>() {
605            let swap = |had: Value| if had == value { param } else { had };
606            func.rewrite(func[inst].args, swap);
607            for at in func.target_list(inst).iter() {
608                func.rewrite(func[at].args, swap);
609            }
610        }
611    }
612}
613
614/// Asks the ranges whether the copied tests are settled, and takes out the ones that are.
615///
616/// This is section 26.6's point about the entry condition. A test that always holds leaves a loop
617/// known to run at least once, which is what document 07.5's trip count wanted. One that never
618/// holds leaves the loop unreachable, and taking it out is [`crate::simplify_cfg::sweep`]'s job
619/// rather than this one's.
620///
621/// Every copy the round made is asked from the one set of ranges, and the answers are acted on
622/// afterwards. That is sound because every question is about a different block's terminator and
623/// every answer is a fact about the values arriving there, which taking a branch out somewhere else
624/// cannot make untrue. Asking one at a time would mean a graph and a dominator tree per copy, which
625/// is the cost tamnd/rucc#1086 is about.
626///
627/// Answers whether it moved an edge, which the caller needs because the analyses this built are the
628/// ones the next round wants and they are only stale if a branch came out. The ranges settle the
629/// test on a minority of the loops here and the graph is the size of the function, so the rounds
630/// where nothing happens used to pay for a rebuild that changed nothing. tamnd/rucc#1045.
631fn settle(func: &mut Func, an: &mut Analyses, copies: &[Block], stats: &mut Stats) -> bool {
632    let mut out: Vec<(Inst, BlockCall, bool)> = Vec::new();
633    {
634        let cfg = an.cfg(func);
635        let dom = an.dominators(func);
636        let mut ranges = Ranges::new(func, cfg, dom);
637        for &copy in copies {
638            let Some(term) = func.terminator(copy) else { continue };
639            let cond = func[func[term].args][0];
640            let Some(taken) = prune::settled(func, &mut ranges, copy, cond) else {
641                stats.missed(UNDECIDED);
642                continue;
643            };
644            let calls: Vec<BlockCall> = func.successors(term).collect();
645            out.push((term, if taken { calls[0] } else { calls[1] }, taken));
646        }
647    }
648    if out.is_empty() {
649        return false;
650    }
651    for (term, call, taken) in out {
652        simplify_cfg::jump_to(func, term, call);
653        stats.optimized(if taken { ENTERED } else { SKIPPED });
654    }
655    true
656}
657
658#[cfg(test)]
659mod tests {
660    use rucc_base::Interner;
661    use rucc_ir::{
662        Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
663        Signature, Start, Type, verify_func,
664    };
665    use rucc_target::{TargetInfo, Triple};
666
667    use super::{HeaderCopy, SIZE, SPEED};
668    use crate::canon::Canon;
669    use crate::cfg::Cfg;
670    use crate::dom::Dominators;
671    use crate::loops::Loops;
672    use crate::stats::Kind;
673    use crate::{Fuel, Pass, Stats};
674
675    /// Canonicalizes and then copies, with as much fuel as both want.
676    ///
677    /// Both, because the pass is written against the shape [`Canon`] leaves and running it over
678    /// anything else is a test of a situation the pipeline does not produce. Section 26.7 puts the
679    /// two next to each other in that order and so does this.
680    fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
681        let mut an = crate::machine::fixtures::analyses();
682        Canon.run(func, &mut an, &mut Fuel::unlimited());
683        pass.run(func, &mut an, &mut Fuel::unlimited())
684    }
685
686    /// The forest of the function as it is now.
687    fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
688        let cfg = Cfg::new(func);
689        let dom = Dominators::new(&cfg);
690        let loops = Loops::new(&cfg, &dom);
691        (cfg, dom, loops)
692    }
693
694    /// Insists the function is one the rest of the compiler may believe.
695    ///
696    /// This is where most of the strength of these tests is. The copy gives the loop a second way
697    /// in, which is exactly the edit that breaks a definition's dominance over its uses, and the
698    /// verifier is what says whether the merge the pass wrote is the merge the graph needed.
699    fn sound(func: &Func, names: &mut Interner) {
700        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
701        let module = Module::new(names.intern("t.c"), &target);
702        if let Err(errors) = verify_func(&module, func, names) {
703            panic!("{errors:#?}");
704        }
705    }
706
707    /// A counted loop that tests at the top, which is what `while (i < n)` lowers to.
708    ///
709    /// ```text
710    /// entry: i0 = 0; jump head(i0)
711    /// head(i): t = i < n; br t -> body, done
712    /// body: next = i + 1; jump head(next)
713    /// done: ret i
714    /// ```
715    ///
716    /// `bound` is the limit as a constant, or nothing for a limit the function was handed and
717    /// which the ranges therefore cannot settle.
718    fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
719        let mut names = Interner::new();
720        let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
721        let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
722        let mut func = Func::new(names.intern("f"), signature);
723        let entry = func.create_block();
724        let head = func.create_block();
725        let body = func.create_block();
726        let done = func.create_block();
727        let limit = match bound {
728            Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
729            None => func.append_param(entry, Type::int(32)),
730        };
731        let i = func.append_param(head, Type::int(32));
732        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
733        Builder::new(&mut func, entry).jump(head, &[zero]);
734        let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
735        Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
736        let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
737        let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
738        Builder::new(&mut func, body).jump(head, &[next]);
739        Builder::new(&mut func, done).ret(&[i]);
740        (func, names, vec![entry, head, body, done])
741    }
742
743    /// Whether the header of the only loop leaves it, which is the question section 26.6 is about.
744    fn tests_at_the_top(func: &Func) -> bool {
745        let (cfg, dom, loops) = forest(func);
746        let _ = dom;
747        let id = loops.all().next().expect("there is a loop");
748        let header = loops.header(id);
749        cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
750    }
751
752    #[test]
753    fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
754        let (mut func, mut names, _) = counted(None);
755        assert!(tests_at_the_top(&func), "the shape this pass is for");
756
757        let stats = copied(&mut func, &SPEED);
758        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
759        assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
760        sound(&func, &mut names);
761    }
762
763    #[test]
764    fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
765        let (mut func, mut names, blocks) = counted(None);
766        let body = blocks[2];
767        assert!(func[body].params.is_empty(), "the body carries nothing to start with");
768
769        copied(&mut func, &SPEED);
770        assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
771        assert_eq!(
772            Cfg::new(&func).predecessors(body).len(),
773            2,
774            "one edge from the header and one from the copy"
775        );
776        sound(&func, &mut names);
777    }
778
779    /// `int j = i;` at the top of the body is a place `j` was given the counter, and once the body
780    /// has a parameter of its own that place is where `j` was given the parameter. The header is
781    /// handed the next value when the loop comes round, so a start left on its value would follow
782    /// that one instead.
783    #[test]
784    fn a_name_on_the_value_the_body_now_takes_as_a_parameter_goes_with_it() {
785        let (mut func, mut names, blocks) = counted(None);
786        let (head, body) = (blocks[1], blocks[2]);
787        let i = func[head].params[0];
788        let test = func.insts(head).next();
789        func.declare_value(i, 3);
790        func.declare_value_from(i, Start { decl: 4, block: body, after: None });
791        func.declare_value_from(i, Start { decl: 5, block: head, after: test });
792
793        copied(&mut func, &SPEED);
794        let param = func[body].params[0];
795        assert_eq!(func.value_decls(param).collect::<Vec<u32>>(), vec![3]);
796        assert_eq!(func.value_decls(i).collect::<Vec<u32>>(), vec![3], "still the header's");
797        let decls = |value| func.value_starts(value).map(|start| start.decl).collect::<Vec<u32>>();
798        assert_eq!(decls(param), vec![4], "the body's start is on the body's parameter");
799        assert_eq!(decls(i), vec![5], "and the header's stays on the header's value");
800        sound(&func, &mut names);
801    }
802
803    #[test]
804    fn an_entry_test_the_ranges_settle_is_taken_out() {
805        let (mut func, mut names, _) = counted(Some(10));
806
807        let stats = copied(&mut func, &SPEED);
808        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
809        assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
810        assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
811        sound(&func, &mut names);
812
813        let (cfg, _dom, loops) = forest(&func);
814        let id = loops.all().next().expect("the loop is still there");
815        let entry = func.entry().expect("there is an entry");
816        assert!(cfg.reaches(loops.header(id)), "and it is still reached");
817        assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
818    }
819
820    #[test]
821    fn a_loop_the_ranges_say_never_runs_is_removed() {
822        let (mut func, mut names, blocks) = counted(Some(0));
823
824        let stats = copied(&mut func, &SPEED);
825        assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
826        sound(&func, &mut names);
827
828        let (_cfg, _dom, loops) = forest(&func);
829        assert_eq!(loops.count(), 0, "there is no loop left");
830        assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
831    }
832
833    #[test]
834    fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
835        let (mut func, _names, _) = counted(None);
836
837        let stats = copied(&mut func, &SPEED);
838        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
839        assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
840        assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
841    }
842
843    #[test]
844    fn a_second_run_changes_nothing() {
845        let (mut func, mut names, _) = counted(None);
846        copied(&mut func, &SPEED);
847        let again =
848            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
849        assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
850        assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
851        sound(&func, &mut names);
852    }
853
854    #[test]
855    fn a_header_that_writes_to_memory_is_left_alone() {
856        let mut names = Interner::new();
857        let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
858        let mut func = Func::new(names.intern("f"), signature);
859        let entry = func.create_block();
860        let head = func.create_block();
861        let body = func.create_block();
862        let done = func.create_block();
863        let limit = func.append_param(entry, Type::int(32));
864        let addr = func.append_param(entry, Type::PTR);
865        let i = func.append_param(head, Type::int(32));
866        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
867        Builder::new(&mut func, entry).jump(head, &[zero]);
868        let access = MemInfo {
869            size: 4,
870            align: 4,
871            order: MemOrder::NotAtomic,
872            tbaa: None,
873            owns: 0,
874            restrict: Restrict::NONE,
875        };
876        Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
877        let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
878        Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
879        let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
880        let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
881        Builder::new(&mut func, body).jump(head, &[next]);
882        Builder::new(&mut func, done).ret(&[]);
883
884        let stats = copied(&mut func, &SPEED);
885        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
886        assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
887        assert!(tests_at_the_top(&func), "the loop is exactly as it was");
888    }
889
890    #[test]
891    fn a_header_larger_than_the_level_allows_is_left_alone() {
892        // Seven instructions in the header, which is over the size budget and well under the
893        // speed one, so the two instances of the pass disagree about the same function.
894        let stats = copied(&mut padded(6), &SIZE);
895        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
896        assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
897
898        let stats = copied(&mut padded(6), &SPEED);
899        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
900    }
901
902    /// The counted loop with that many more instructions in its header, which do nothing.
903    fn padded(extra: usize) -> Func {
904        let (mut func, _names, blocks) = counted(None);
905        let head = blocks[1];
906        let term = func.terminator(head).expect("the header branches");
907        for _ in 0..extra {
908            let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
909            let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
910            func.remove_inst(inst);
911            func.insert_before(inst, term);
912        }
913        func
914    }
915
916    #[test]
917    fn fuel_stops_the_copy_where_it_stands() {
918        let (mut func, _names, _) = counted(None);
919        let mut an = crate::machine::fixtures::analyses();
920        Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
921
922        let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
923        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
924        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
925        assert!(tests_at_the_top(&func), "and the loop is as it was");
926    }
927
928    #[test]
929    fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
930        // Straight to the copy, so loop-closed form has not been established and the counter the
931        // return names is still the header's own definition. That is the one value this pass will
932        // not merge, and section 26.7's answer is that [`Canon`] has already routed it by the time
933        // the pipeline gets here.
934        let (mut func, _names, _) = counted(None);
935
936        let stats =
937            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
938        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
939        assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
940        assert!(tests_at_the_top(&func), "and the loop is as it was");
941    }
942
943    #[test]
944    fn a_loop_with_no_preheader_is_declined() {
945        let mut names = Interner::new();
946        let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
947        let mut func = Func::new(names.intern("f"), signature);
948        let entry = func.create_block();
949        let one = func.create_block();
950        let two = func.create_block();
951        let head = func.create_block();
952        let body = func.create_block();
953        let done = func.create_block();
954        let c = func.append_param(entry, Type::int(1));
955        let limit = func.append_param(entry, Type::int(32));
956        let i = func.append_param(head, Type::int(32));
957        Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
958        let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
959        Builder::new(&mut func, one).jump(head, &[zero]);
960        let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
961        Builder::new(&mut func, two).jump(head, &[start]);
962        let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
963        Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
964        Builder::new(&mut func, body).jump(head, &[i]);
965        Builder::new(&mut func, done).ret(&[]);
966
967        let stats =
968            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
969        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
970        assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
971
972        // And with one, which is what the pipeline hands it, the same loop is copied.
973        let stats = copied(&mut func, &SPEED);
974        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
975        sound(&func, &mut names);
976    }
977
978    /// Two counted loops one after the other, which share no block and no preheader.
979    ///
980    /// ```text
981    /// entry(n): jump one(0)
982    /// one(i):  t = i < n; br t -> up, mid
983    /// up:      i2 = i + 1; jump one(i2)
984    /// mid:     jump two(0)
985    /// two(j):  u = j < n; br u -> down, done
986    /// down:    j2 = j + 1; jump two(j2)
987    /// done:    ret
988    /// ```
989    fn side_by_side() -> (Func, Interner, Vec<Block>) {
990        let mut names = Interner::new();
991        let signature = Signature::new().with_params(&[Type::int(32)]);
992        let mut func = Func::new(names.intern("f"), signature);
993        let entry = func.create_block();
994        let one = func.create_block();
995        let up = func.create_block();
996        let mid = func.create_block();
997        let two = func.create_block();
998        let down = func.create_block();
999        let done = func.create_block();
1000        let n = func.append_param(entry, Type::int(32));
1001        let i = func.append_param(one, Type::int(32));
1002        let j = func.append_param(two, Type::int(32));
1003        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1004        Builder::new(&mut func, entry).jump(one, &[zero]);
1005        let t = Builder::new(&mut func, one).icmp(IntPred::Slt, i, n);
1006        Builder::new(&mut func, one).br_if(t, up, &[], mid, &[]);
1007        let step = Builder::new(&mut func, up).iconst(Type::int(32), 1);
1008        let next = Builder::new(&mut func, up).binary(Opcode::Add, i, step, Flags::NONE);
1009        Builder::new(&mut func, up).jump(one, &[next]);
1010        let start = Builder::new(&mut func, mid).iconst(Type::int(32), 0);
1011        Builder::new(&mut func, mid).jump(two, &[start]);
1012        let u = Builder::new(&mut func, two).icmp(IntPred::Slt, j, n);
1013        Builder::new(&mut func, two).br_if(u, down, &[], done, &[]);
1014        let stride = Builder::new(&mut func, down).iconst(Type::int(32), 1);
1015        let after = Builder::new(&mut func, down).binary(Opcode::Add, j, stride, Flags::NONE);
1016        Builder::new(&mut func, down).jump(two, &[after]);
1017        Builder::new(&mut func, done).ret(&[]);
1018        (func, names, vec![up, down])
1019    }
1020
1021    #[test]
1022    fn two_loops_that_do_not_meet_are_both_copied() {
1023        let (mut func, mut names, bodies) = side_by_side();
1024
1025        let stats = copied(&mut func, &SPEED);
1026        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1027        sound(&func, &mut names);
1028
1029        let (_cfg, _dom, loops) = forest(&func);
1030        assert_eq!(loops.count(), 2, "both loops are still loops");
1031        for id in loops.all() {
1032            let header = loops.header(id);
1033            assert!(
1034                !Cfg::new(&func).successors(header).iter().any(|&to| !loops.contains(id, to)),
1035                "and neither of them tests at the top any more"
1036            );
1037        }
1038        for body in bodies {
1039            assert_eq!(func[body].params.len(), 1, "each body carries its own counter");
1040        }
1041    }
1042
1043    /// A counted loop with a counted loop inside it, where the inner preheader is an outer block.
1044    ///
1045    /// ```text
1046    /// entry(n): jump outer(0)
1047    /// outer(i): t = i < n; br t -> ahead, done
1048    /// ahead:    jump inner(0)
1049    /// inner(j): u = j < n; br u -> under, latch
1050    /// under:    j2 = j + 1; jump inner(j2)
1051    /// latch:    i2 = i + 1; jump outer(i2)
1052    /// done:     ret
1053    /// ```
1054    fn nested() -> (Func, Interner) {
1055        let mut names = Interner::new();
1056        let signature = Signature::new().with_params(&[Type::int(32)]);
1057        let mut func = Func::new(names.intern("f"), signature);
1058        let entry = func.create_block();
1059        let outer = func.create_block();
1060        let ahead = func.create_block();
1061        let inner = func.create_block();
1062        let under = func.create_block();
1063        let latch = func.create_block();
1064        let done = func.create_block();
1065        let n = func.append_param(entry, Type::int(32));
1066        let i = func.append_param(outer, Type::int(32));
1067        let j = func.append_param(inner, Type::int(32));
1068        let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
1069        Builder::new(&mut func, entry).jump(outer, &[zero]);
1070        let t = Builder::new(&mut func, outer).icmp(IntPred::Slt, i, n);
1071        Builder::new(&mut func, outer).br_if(t, ahead, &[], done, &[]);
1072        let start = Builder::new(&mut func, ahead).iconst(Type::int(32), 0);
1073        Builder::new(&mut func, ahead).jump(inner, &[start]);
1074        let u = Builder::new(&mut func, inner).icmp(IntPred::Slt, j, n);
1075        Builder::new(&mut func, inner).br_if(u, under, &[], latch, &[]);
1076        let stride = Builder::new(&mut func, under).iconst(Type::int(32), 1);
1077        let after = Builder::new(&mut func, under).binary(Opcode::Add, j, stride, Flags::NONE);
1078        Builder::new(&mut func, under).jump(inner, &[after]);
1079        let step = Builder::new(&mut func, latch).iconst(Type::int(32), 1);
1080        let next = Builder::new(&mut func, latch).binary(Opcode::Add, i, step, Flags::NONE);
1081        Builder::new(&mut func, latch).jump(outer, &[next]);
1082        Builder::new(&mut func, done).ret(&[]);
1083        (func, names)
1084    }
1085
1086    #[test]
1087    fn a_loop_and_the_loop_inside_it_are_copied_one_round_apart() {
1088        // The two share every block the inner one has, and the inner one's preheader is a block of
1089        // the outer one, so a round may hold at most one of them. Both are still copied, and the
1090        // point of the test is that the second is planned against a function the first has already
1091        // changed rather than against the plan the first was made from.
1092        let (mut func, mut names) = nested();
1093
1094        let stats = copied(&mut func, &SPEED);
1095        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
1096        sound(&func, &mut names);
1097
1098        let (cfg, _dom, loops) = forest(&func);
1099        assert_eq!(loops.count(), 2, "both loops survived the copy");
1100        for id in loops.all() {
1101            let header = loops.header(id);
1102            assert!(
1103                !cfg.successors(header).iter().any(|&to| !loops.contains(id, to)),
1104                "and both test at the bottom now"
1105            );
1106        }
1107    }
1108
1109    #[test]
1110    fn a_round_stops_where_the_fuel_does() {
1111        // Two loops a round may hold together, and one unit of fuel. The first is copied and the
1112        // second is left for a run with more, which is what taking fuel per job rather than per
1113        // round means.
1114        let (mut func, mut names) = {
1115            let (mut func, names, _) = side_by_side();
1116            let mut an = crate::machine::fixtures::analyses();
1117            Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
1118            (func, names)
1119        };
1120
1121        let stats =
1122            SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
1123        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
1124        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1125        sound(&func, &mut names);
1126    }
1127}