Skip to main content

rucc_opt/
thread.rs

1//! Jump threading, with and without a copy of the block threaded past.
2//!
3//! Design: `spec/optimizer/23-jump-threading.md`. If, on the path through block A into block B, the
4//! condition B tests is already decided, then A should branch straight to the arm B was going to
5//! take and skip B's test. On real C that removes more branches than anything else in the compiler,
6//! because C is full of conditions that are redundant along some paths and not along others.
7//!
8//! It is also the pass most likely to explode, because the general form works by copying B, and a
9//! copy grows the function, and the growth compounds because each thread makes new paths on which
10//! further threading is possible. Section 23.4 is four separate limits on that growth and section
11//! 23.6 names the subset where there is none: the case where the block being threaded past does not
12//! have to be copied at all, which is pure edge redirection. That subset is [`FREE`], and [`COPY`]
13//! is that plus section 23.1's copy, under the limits of section 23.4.
14//!
15//! # What decides a branch here, and what does not
16//!
17//! Arguments in this IR live on the edge rather than in the block, so a block parameter is a value
18//! that arrives differently depending on which way control came. Bind a block's parameters to what
19//! one edge carries and its terminator may resolve on that edge while resolving on no other, which
20//! is the whole of the path sensitivity this pass has. It covers section 23.3's example directly:
21//!
22//! ```c
23//! if (a) x = 1; else x = 2;
24//! if (x == 1) ...
25//! ```
26//!
27//! Nothing dominating the second test decides it, so the forward threader of section 23.2 cannot
28//! see it and neither can `simplify-cfg`. But `x` arrives at the second test as a block parameter,
29//! it is 1 along one edge and 2 along the other, and both edges resolve. Both are threaded, nothing
30//! is left reaching the block, and the second branch goes.
31//!
32//! What is not here is section 23.3's backward search with the path-sensitive range solver. This
33//! asks about one edge and not about a path of them, so a condition decided two blocks back and not
34//! one is a condition this does not see. The range machinery for that exists in [`crate::range`] and
35//! the search is the larger half of the document.
36//!
37//! # Why no block has to be copied
38//!
39//! Section 23.1 quotes GCC's six step surgery, whose first step is a copy of B. The copy exists so
40//! that B's side effects still happen on the threaded path and so that the values B defines are
41//! available to the arm the thread lands on. Where neither is needed, neither is the copy, and this
42//! pass threads exactly the edges where neither is needed:
43//!
44//! - Every instruction in B other than its terminator has no effects, so a path that skips them
45//!   skips nothing that had to happen. That is the same predicate [`crate::dce`] deletes an
46//!   instruction under, which is the point: an instruction it would delete outright is one a path
47//!   can walk past.
48//! - Nothing outside B reads a value B defines. Those are the values the copy would have existed to
49//!   compute, and both the arm's arguments and the blocks further down are asking for them.
50//!
51//! The second condition has to be about the whole function and not just about the arm. An argument
52//! is how a value crosses into a block that B does not dominate, but a block B does dominate reads
53//! what B defined with no argument at all, because dominance is the only permission a use needs.
54//! Threading an edge past B takes that dominance away, and the read is then of a value that was
55//! never computed on the path taken. Checking only the arm's arguments misses exactly that, which is
56//! what `a_value_the_block_defines_and_something_below_it_reads_needs_the_copy` is about.
57//!
58//! B's parameters are covered by the same rule, since a parameter is a value B defines. Along the
59//! edge being redirected they are known, so B's own reads of them are substituted rather than
60//! refused, but a read from below is a read of a value that is about to stop existing. And a value
61//! the arm carries that is defined outside B dominates the block it is being carried out of, so it
62//! dominates the predecessor as well: it is on every path to B, the predecessor has an edge to B, so
63//! it is on every path to the predecessor. That is section 23.1's "the values must still dominate",
64//! and it is the same argument `spec/optimizer/21-cfg-simplification.md` section 21.4 needs for
65//! forwarder removal.
66//!
67//! # The copy, and what it owes the values
68//!
69//! On the corpus the free subset threads one edge out of the 623 that decide the branch they arrive
70//! at, and 619 of the rest are refused because a block below reads a value the block defines. So
71//! the copy is there to make values, not to repeat effects, and [`COPY`] only copies a block the
72//! free subset would already walk past: nothing in it has an effect, and nothing in it carries a
73//! side table entry [`crate::header_copy`] has not been checked against. What changes is that its
74//! values may be read below and carried on the arm.
75//!
76//! The copy is made on the one edge being threaded. It has no parameters, since along that edge
77//! they are the arguments the edge carries, it holds the block's instructions with those arguments
78//! put in, and it ends in a jump to the arm the edge decides. The edge is pointed at it. The arm's
79//! arguments come from the copy, so a value the block worked out is the copy's version of it.
80//!
81//! What that leaves is every read of the block's values from somewhere else. The block no longer
82//! dominates them, since the copy reaches some of them as well, so each value now has two
83//! definitions and a read below needs whichever one reached it. That is SSA construction for one
84//! variable with two definitions, and it is done the classical way: a block parameter goes on each
85//! block in the iterated dominance frontier of the block and its copy where the value is still
86//! wanted, the edges into it carry what reached the end of the block they leave, and every read is
87//! of what reached the start of the block it is in. What reached a block is the nearest of the
88//! block, the copy and the new parameters up the dominator tree, which is why the parameters go
89//! where the frontier says: those are exactly the places where the nearest one up the tree is not
90//! the only one that can arrive. Liveness is what keeps the parameters to the ones something reads,
91//! and is also what makes it safe to skip the parameters of every other block in the frontier.
92//!
93//! # The limits
94//!
95//! Section 23.4 adopts GCC's four and they are all here, in `rucc_cost::heuristics`. A block of
96//! more than fifteen instructions is not copied. A thread whose arm goes back to the header of a
97//! loop the block is in counts each instruction twice. A copy whose edge came out of an earlier
98//! copy is the next block of one path, and a path may not copy more than a hundred instructions in
99//! all. And one run makes at most sixty four copies in one function, which is GCC's bound on paths
100//! turned from the paths a backward search looks at into the paths that are actually copied, since
101//! there is no backward search here. The last two are what stop threading from feeding on itself,
102//! since every copy is a new edge into the arm and the arm may be the next block this walk threads
103//! past.
104//!
105//! # The loop rules, which are refusals and not scores
106//!
107//! Section 23.5. Threading a path into a loop somewhere other than its header makes an irreducible
108//! loop, and document 06.4 established that rucc does not split nodes and gives up on irreducible
109//! regions instead. So the rule here is stronger than GCC's, where it is one input to a cost model:
110//! a thread that would do it is refused, at every level. A predecessor that is a latch is refused
111//! too, because moving a latch's edge is how the single latch property document 07.3 wants stops
112//! being true. And a block already in an irreducible region is left alone entirely, since the loop
113//! forest has given up on it and the two checks above would be reading an answer nobody stands
114//! behind.
115//!
116//! Without a copy no new cycle can appear. The new edge from A goes where the edge out of B went, so
117//! a path along it is a path that was already there with B taken out of the middle. With one the
118//! same is true of the path through the copy, which is B's path with B's test taken out, so loops
119//! can still only be destroyed. The loop forest is rebuilt after each thread anyway, which is what
120//! keeps the next decision honest.
121//!
122//! # Which level this runs at
123//!
124//! [`FREE`] runs at `-Os` and `-Oz`. Section 23.6 restricts threading at those two to the case where
125//! the block is empty, on the ground that it is the only part that is free, and [`FREE`] is that
126//! part generalized: a block whose instructions all have no effects and whose outgoing arguments do
127//! not come from it costs the same as an empty one, which is nothing. [`COPY`] runs at `-O1` and
128//! above, as GCC's `-fthread-jumps` does.
129//!
130//! Once, and not to a fixed point. Threading enables threading, and section 23.7 says the answer to
131//! that is a fixed number of instances rather than a loop, because threading is the pass where
132//! adversarial input is easiest to construct. Section 23.5 asks for two instances at `-O2`, an early
133//! one and a late one after the loop pipeline and SCCP. There is one here, in the early position.
134//! The late one wants the passes that are not written yet.
135//!
136//! # What it counts
137//!
138//! Every refusal is recorded, and they are the measurement section 23.8 asks this document for.
139//! Three of them count edges that decide a branch [`FREE`] cannot thread without the copy, split by
140//! which part of the copy is in the way: something in the block that has to happen, a value the arm
141//! carries that the block worked out, and a value the block defines that a block below it reads.
142//! [`COPY`] threads the last two and records a limit instead where one stops it. One more counts
143//! edges refused on loop structure, which is the price of document 06.4's position on irreducible
144//! regions stated as a number rather than as an argument.
145//!
146//! On the 1461 programs of the corpus at `-O2`, 623 edges decide the branch they arrive at and one
147//! of them is threadable without a copy. 619 are blocked on a value the block defines being read
148//! below it, 4 on the arm carrying one, and none at all on the block doing something that has to
149//! happen. That split is why the copy here is the copy of a block with no effects in it.
150
151use std::collections::{HashMap, HashSet};
152
153use rucc_base::Idx;
154use rucc_cost::heuristics;
155use rucc_ir::{Block, BlockCall, Builder, Def, Func, Inst, Opcode, Start, Value, ValueList};
156
157use crate::frontier::Frontiers;
158use crate::header_copy::{clone_into, repeatable};
159use crate::simplify_cfg::{Bindings, Edges, incoming, sweep, taken};
160use crate::{Analyses, Cfg, Dominators, Fuel, Loops, Pass, Preserved, Stats, uses};
161
162/// Recorded once for each edge that was pointed past a branch it decides.
163const THREADED: &str =
164    "edge pointed straight at the arm of the branch it arrives at that it decides";
165
166/// Recorded for an edge that would have been threaded if there had been fuel for it.
167const NO_FUEL: &str = "edge left on a branch it decides, the pass ran out of fuel";
168
169/// Recorded for an edge whose block does something a path through it cannot skip.
170const WOULD_COPY_EFFECT: &str =
171    "edge decides the branch it arrives at, but something in the block has to happen on the way";
172
173/// Recorded for an edge whose block defines a value read below it.
174const WOULD_COPY_READ_BELOW: &str =
175    "edge decides the branch it arrives at, but a block below reads a value this one defines";
176
177/// Recorded for an edge whose arm carries a value the block itself computed.
178const WOULD_COPY_CARRIED: &str =
179    "edge decides the branch it arrives at, but the arm carries a value the block works out";
180
181/// Recorded for an edge that decides a branch but whose thread would spoil the loop forest.
182const WOULD_BREAK_A_LOOP: &str =
183    "edge decides the branch it arrives at, but threading it would give a loop a second way in";
184
185/// Recorded once for each edge pointed at a copy of the block it arrived at.
186const COPIED: &str =
187    "edge pointed at a copy of the block it arrives at that goes straight to the arm it decides";
188
189/// Recorded for an edge whose block has something in it this pass does not copy.
190const ODD: &str =
191    "edge decides the branch it arrives at, but the block has something in it that is not copied";
192
193/// Recorded for an edge whose block is larger than section 23.4 lets a thread copy.
194const TOO_BIG: &str =
195    "edge decides the branch it arrives at, but the block is larger than a thread may copy";
196
197/// Recorded for an edge whose copy would make the path of copies it is on too long.
198const TOO_LONG: &str =
199    "edge decides the branch it arrives at, but the path of copies it is on would be too long";
200
201/// Recorded for an edge left once the function has had as many copies as one run makes.
202const TOO_MANY: &str =
203    "edge decides the branch it arrives at, but this function has had its 64 copies";
204
205/// The pass, with how many instructions it may copy a block of.
206#[derive(Debug)]
207pub struct Thread {
208    /// What a `-f` flag spells.
209    name: &'static str,
210    /// The most instructions a block threaded past may hold, and zero for a pass that copies none.
211    budget: u32,
212}
213
214/// The instance `-Os` and `-Oz` run, which threads an edge only where nothing has to be copied.
215pub static FREE: Thread = Thread { name: "thread", budget: 0 };
216
217/// The instance `-O1` and above run, which copies a block of up to section 23.4's fifteen.
218pub static COPY: Thread =
219    Thread { name: "thread-copy", budget: heuristics::JUMP_THREAD_DUPLICATION_INSNS };
220
221impl Pass for Thread {
222    fn name(&self) -> &'static str {
223        self.name
224    }
225
226    fn describe(&self) -> &'static str {
227        if self.budget == 0 {
228            "an edge that already decides the branch it arrives at is pointed at the arm that \
229             branch would have taken"
230        } else {
231            "an edge that already decides the branch it arrives at is pointed at the arm that \
232             branch would have taken, through a copy of the block if the block's values are read"
233        }
234    }
235
236    fn preserves(&self) -> Preserved {
237        // Nothing. An edge moves, so every analysis built on the graph was built on a different
238        // graph, which is the same answer `simplify-cfg` gives for the same reason.
239        Preserved::NONE
240    }
241
242    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
243        let mut stats = Stats::new();
244        let Some(entry) = func.entry() else { return stats };
245        // The edges are kept here rather than asked for as a graph, because this pass moves them
246        // as it goes and a cached graph would be about the shape the function had one thread ago.
247        // A block call rather than a predecessor, since redirecting an edge wants the slot in the
248        // pool and there is no finding it again from the block the edge used to arrive at.
249        let mut edges: Edges = incoming(func);
250        let mut leaks = leaky(func);
251        let unbound = Bindings::new();
252        let mut threaded = false;
253        // What each copy made in this run has cost the path it is on, which is what the path limit
254        // of section 23.4 adds up, and how many copies there have been.
255        let mut paths: HashMap<Block, u32> = HashMap::new();
256        let mut copies = 0;
257        'blocks: for block in func.blocks().collect::<Vec<Block>>() {
258            if block == entry || func[block].params.is_empty() {
259                continue;
260            }
261            let Some(term) = func.terminator(block) else { continue };
262            if !matches!(func[term].opcode, Opcode::BrIf | Opcode::Switch) {
263                continue;
264            }
265            // A branch that goes the same way whichever edge control arrived on is `simplify-cfg`'s
266            // to fold, and folding it once is cheaper than pointing every edge into the block at the
267            // same arm separately.
268            if taken(func, term, &unbound).is_some() {
269                continue;
270            }
271            // Something that has to happen stops every edge into the block, since nothing here
272            // copies an effect, so it is settled once here and not once per edge below.
273            let effect = !skippable(func, block);
274            for (from, at) in edges.get(&block).cloned().unwrap_or_default() {
275                // A block that branches to itself, where the branch resolves, is a loop that does
276                // not end, and redirecting its own edge is not a description of anything a person
277                // wrote. The block below refuses it as well, since the block is its own latch.
278                if from == block {
279                    continue;
280                }
281                let subst = bind(func, block, at);
282                let Some(call) = taken(func, term, &subst) else { continue };
283                if call.block == block {
284                    continue;
285                }
286                if effect {
287                    stats.missed(WOULD_COPY_EFFECT);
288                    continue;
289                }
290                // The block's own values read below are what the leaky set is about, and the ones
291                // the arm carries are what `carried` is about. Either needs the copy.
292                let (free, why) = if leaks.contains(&block) {
293                    (None, WOULD_COPY_READ_BELOW)
294                } else {
295                    (carried(func, block, call, &subst), WOULD_COPY_CARRIED)
296                };
297                let path = if free.is_some() {
298                    0
299                } else {
300                    match self.path(func, an.loops(func), block, from, call.block, &paths, copies) {
301                        Ok(path) => path,
302                        Err(reason) => {
303                            stats.missed(reason.unwrap_or(why));
304                            continue;
305                        }
306                    }
307                };
308                if !allowed(an.loops(func), from, call.block) {
309                    stats.missed(WOULD_BREAK_A_LOOP);
310                    continue;
311                }
312                if !fuel.take() {
313                    // Where the pass stops rather than where it starts skipping, because a budget
314                    // that has reached zero will not have anything in it at the next block either
315                    // and the two refusals above are the counts worth being true.
316                    stats.missed(NO_FUEL);
317                    break 'blocks;
318                }
319                // The record has to follow the edge, so that a block further down the walk sees the
320                // predecessor it now has. That is what lets one thread make the next one possible
321                // within the single walk this pass is.
322                if let Some(list) = edges.get_mut(&block) {
323                    list.retain(|&(_, slot)| slot != at);
324                }
325                if let Some(args) = free {
326                    let args = func.push_values(&args);
327                    func.set_block_call(at, BlockCall { args, ..call });
328                    edges.entry(call.block).or_default().push((from, at));
329                    stats.optimized(THREADED);
330                } else {
331                    let (copy, out) = copy(func, block, at, call, &subst);
332                    edges.entry(call.block).or_default().push((copy, out));
333                    paths.insert(copy, path);
334                    copies += 1;
335                    // The merges put parameters on blocks further down, and a parameter something
336                    // reads from below is exactly what the leaky set is about. It went stale in the
337                    // safe direction before there were copies. It does not now.
338                    leaks = leaky(func);
339                    stats.optimized(COPIED);
340                }
341                // The loop forest was about the function as it was a moment ago, and the manager
342                // clears the cache after the pass returns, which is too late for the next edge.
343                an.clear();
344                threaded = true;
345            }
346        }
347        if threaded {
348            // Threading every edge into a block leaves nothing arriving at it, and section 6.5
349            // makes taking an unreachable block out the standing obligation of whichever pass
350            // stranded it rather than something the next pass tidies up. The verifier holds every
351            // pass to that, so this is not a courtesy.
352            sweep(func, an, &mut stats);
353        }
354        stats
355    }
356}
357
358impl Thread {
359    /// What copying `block` onto the edge from `from` costs the path it is on, or why it may not.
360    ///
361    /// `None` for the reason is the instance that copies nothing, which leaves the caller to say
362    /// which part of the copy was wanted. The limits are section 23.4's, in the order a block
363    /// fails them: what is in it, how big it is, how long the path of copies it is on has become,
364    /// and how many copies this run has already made.
365    #[allow(clippy::too_many_arguments)]
366    fn path(
367        &self,
368        func: &Func,
369        loops: &Loops,
370        block: Block,
371        from: Block,
372        into: Block,
373        paths: &HashMap<Block, u32>,
374        copies: u32,
375    ) -> Result<u32, Option<&'static str>> {
376        if self.budget == 0 {
377            return Err(None);
378        }
379        let body: Vec<Inst> = func.insts(block).filter(|&inst| !func.is_terminator(inst)).collect();
380        if !body.iter().all(|&inst| repeatable(func, inst)) {
381            return Err(Some(ODD));
382        }
383        let mut cost = u32::try_from(body.len()).unwrap_or(u32::MAX);
384        if back_edge(loops, block, into) {
385            cost = cost.saturating_mul(heuristics::JUMP_THREAD_BACK_EDGE_SCALE);
386        }
387        if cost > self.budget {
388            return Err(Some(TOO_BIG));
389        }
390        let path = paths.get(&from).copied().unwrap_or(0).saturating_add(cost);
391        if path > heuristics::JUMP_THREAD_PATH_INSNS {
392            return Err(Some(TOO_LONG));
393        }
394        if copies >= heuristics::JUMP_THREAD_PATHS {
395            return Err(Some(TOO_MANY));
396        }
397        Ok(path)
398    }
399}
400
401/// Whether the edge from `from` to `into` goes back to the header of a loop `from` is in.
402fn back_edge(loops: &Loops, from: Block, into: Block) -> bool {
403    let mut id = loops.innermost(from);
404    while let Some(loop_id) = id {
405        if loops.header(loop_id) == into {
406            return true;
407        }
408        id = loops.parent(loop_id);
409    }
410    false
411}
412
413/// Section 23.1's surgery on one edge: a copy of `block` that goes straight to `call`, the edge at
414/// `at` pointed at it, and every read of `block`'s values from below given the one that reaches it.
415///
416/// Returns the copy and the slot of the edge out of it, which the caller's record of edges needs.
417fn copy(
418    func: &mut Func,
419    block: Block,
420    at: Idx<BlockCall>,
421    call: BlockCall,
422    subst: &Bindings,
423) -> (Block, Idx<BlockCall>) {
424    let term = func.terminator(block).expect("the block was chosen for its terminator");
425    let mut map = subst.clone();
426    let copy = func.create_block();
427    let insts: Vec<Inst> = func.insts(block).filter(|&inst| inst != term).collect();
428    for inst in insts {
429        clone_into(func, copy, inst, &mut map);
430    }
431    let args: Vec<Value> =
432        func[call.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
433    let jump = Builder::new(func, copy).jump(call.block, &args);
434    let out = func.target_list(jump).iter().next().expect("a jump has one edge");
435    let edge = func[at];
436    func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY, ..edge });
437    repair(func, block, copy, &map);
438    (copy, out)
439}
440
441/// Gives every read of a value `block` defines, from anywhere but `block`, the definition that
442/// reaches it now that `copy` defines the value as well.
443///
444/// The frontier and the dominator tree are of the graph with the edge already moved, and they are
445/// shared by every value, since the merges only add parameters and a parameter moves no edge.
446fn repair(func: &mut Func, block: Block, copy: Block, map: &Bindings) {
447    let values = read_outside(func, block, copy);
448    if values.is_empty() {
449        return;
450    }
451    let cfg = Cfg::new(func);
452    let dom = Dominators::new(&cfg);
453    let frontiers = Frontiers::new(&cfg, &dom);
454    let mut joins: HashSet<Block> = HashSet::new();
455    let mut work = vec![block, copy];
456    while let Some(at) = work.pop() {
457        for &join in frontiers.of(at) {
458            if joins.insert(join) {
459                work.push(join);
460            }
461        }
462    }
463    for value in values {
464        let copied = map.get(&value).copied().expect("the copy defines every value the block does");
465        let mut reaching = Reaching {
466            dom: &dom,
467            block,
468            copy,
469            value,
470            copied,
471            params: HashMap::new(),
472            memo: HashMap::new(),
473        };
474        merge(func, &cfg, &joins, &mut reaching);
475    }
476}
477
478/// The values `block` defines that something outside it and outside its copy reads.
479fn read_outside(func: &Func, block: Block, copy: Block) -> Vec<Value> {
480    let mut seen = HashSet::new();
481    let mut out = Vec::new();
482    for other in func.blocks() {
483        if other == block || other == copy {
484            continue;
485        }
486        for inst in func.insts(other) {
487            uses::operands(func, inst, |value| {
488                if defined_in(func, value) == Some(block) && seen.insert(value) {
489                    out.push(value);
490                }
491            });
492        }
493    }
494    out
495}
496
497/// Which definition of one value reaches each block, once the merges are in.
498struct Reaching<'a> {
499    /// The tree the answer is read off.
500    dom: &'a Dominators,
501    /// The block the value was defined in first.
502    block: Block,
503    /// The copy of it, which defines the value as well.
504    copy: Block,
505    /// The value as `block` defines it.
506    value: Value,
507    /// The value as `copy` defines it.
508    copied: Value,
509    /// The parameter each merge block was given for the value.
510    params: HashMap<Block, Value>,
511    /// What reached the start of each block already asked about.
512    memo: HashMap<Block, Value>,
513}
514
515impl Reaching<'_> {
516    /// What reaches the start of a block that is neither the block nor its copy.
517    ///
518    /// The nearest definition up the dominator tree, where a merge block's parameter is a
519    /// definition. The frontier put a parameter everywhere two could meet, so nothing between here
520    /// and the nearest one can have had another arrive. A block the tree does not reach is one the
521    /// program does not reach either, and it keeps the value it had.
522    fn start(&mut self, of: Block) -> Value {
523        let mut chain = Vec::new();
524        let mut at = of;
525        let found = loop {
526            if let Some(&param) = self.params.get(&at) {
527                break param;
528            }
529            if let Some(&known) = self.memo.get(&at) {
530                break known;
531            }
532            chain.push(at);
533            match self.dom.immediate_dominator(at) {
534                Some(up) if up == self.block => break self.value,
535                Some(up) if up == self.copy => break self.copied,
536                Some(up) => at = up,
537                None => break self.value,
538            }
539        };
540        for at in chain {
541            self.memo.insert(at, found);
542        }
543        found
544    }
545
546    /// What reaches the end of a block, which is what an edge out of it carries.
547    fn end(&mut self, of: Block) -> Value {
548        if of == self.block {
549            self.value
550        } else if of == self.copy {
551            self.copied
552        } else {
553            self.start(of)
554        }
555    }
556}
557
558/// Puts the parameters one value needs where its two definitions meet, and points every read of it
559/// at the definition that reaches the read.
560fn merge(func: &mut Func, cfg: &Cfg, joins: &HashSet<Block>, reaching: &mut Reaching<'_>) {
561    let (block, copy, value) = (reaching.block, reaching.copy, reaching.value);
562    // Where the value is still wanted at the start of a block. A read is where it starts, and it
563    // goes up through predecessors until it meets one of the two definitions.
564    let mut readers = Vec::new();
565    for other in func.blocks() {
566        if other == block || other == copy {
567            continue;
568        }
569        let mut reads = false;
570        for inst in func.insts(other) {
571            uses::operands(func, inst, |used| reads |= used == value);
572        }
573        if reads {
574            readers.push(other);
575        }
576    }
577    let mut live: HashSet<Block> = readers.iter().copied().collect();
578    let mut work = readers.clone();
579    while let Some(at) = work.pop() {
580        for &pred in cfg.predecessors(at) {
581            if pred != block && pred != copy && live.insert(pred) {
582                work.push(pred);
583            }
584        }
585    }
586    let mut places: Vec<Block> = joins
587        .iter()
588        .copied()
589        .filter(|&join| join != block && join != copy && live.contains(&join))
590        .collect();
591    places.sort_by_key(|join| join.index());
592    let ty = func[value].ty;
593    let decls: Vec<u32> = func.value_decls(value).collect();
594    for &place in &places {
595        let param = func.append_param(place, ty);
596        for &decl in &decls {
597            func.declare_value(param, decl);
598        }
599        reaching.params.insert(place, param);
600    }
601    for &reader in &readers {
602        let now = reaching.start(reader);
603        if now == value {
604            continue;
605        }
606        let swap = |had: Value| if had == value { now } else { had };
607        for inst in func.insts(reader).collect::<Vec<Inst>>() {
608            func.rewrite(func[inst].args, swap);
609            for at in func.target_list(inst).iter() {
610                func.rewrite(func[at].args, swap);
611            }
612        }
613    }
614    // The edges into a merge carry what reached the end of the block they leave. This comes after
615    // the reads are rewritten so that what it appends is not rewritten a second time.
616    for other in func.blocks().collect::<Vec<Block>>() {
617        let Some(term) = func.terminator(other) else { continue };
618        for at in func.target_list(term).iter() {
619            let call = func[at];
620            if !reaching.params.contains_key(&call.block) {
621                continue;
622            }
623            let carry = reaching.end(other);
624            let args = func.append_arg(call.args, carry);
625            func.set_block_call(at, BlockCall { args, ..call });
626        }
627    }
628    // A debugger asking for the variable at a start somewhere below is asking for whichever
629    // definition reached there, so a start moves to it the same way a read does.
630    let starts: Vec<(Start, Value)> = func
631        .value_starts(value)
632        .filter_map(|start| {
633            let at = func.start_place(start).map_or(start.block, |(at, _)| at);
634            if at == block || at == copy {
635                return None;
636            }
637            let now = reaching.start(at);
638            (now != value).then_some((start, now))
639        })
640        .collect();
641    let mut targets: Vec<Value> = starts.iter().map(|&(_, now)| now).collect();
642    targets.dedup();
643    for target in targets {
644        let which: Vec<Start> =
645            starts.iter().filter(|&&(_, now)| now == target).map(|&(start, _)| start).collect();
646        if !which.is_empty() {
647            func.move_starts(value, target, &which);
648        }
649    }
650}
651
652/// What this block's parameters hold along one edge into it.
653fn bind(func: &Func, block: Block, at: Idx<BlockCall>) -> Bindings {
654    let args = func[at].args;
655    let params = func[block].params.iter().copied();
656    params.zip(func[args].iter().copied()).collect()
657}
658
659/// The arguments the redirected edge carries, or `None` when one of them is only computed here.
660///
661/// A parameter of the block is replaced by whatever the edge being redirected was passing for it. A
662/// value from anywhere else is passed on as it stands, because a value used in this block and
663/// defined outside it dominates the predecessor, which is the argument the module doc makes. A value
664/// defined by an instruction in this block is the case that needs section 23.1's copy, and it is the
665/// answer this returns `None` for.
666///
667/// [`leaky`] does not cover this one. An argument on the arm is read by the block's own terminator,
668/// so the value never leaves the block by that route and the block is not leaky on account of it.
669/// The two checks are about the two ways a value gets out, and both are needed.
670fn carried(func: &Func, block: Block, call: BlockCall, subst: &Bindings) -> Option<Vec<Value>> {
671    let mut out = Vec::with_capacity(func[call.args].len());
672    for &arg in &func[call.args] {
673        if let Some(&bound) = subst.get(&arg) {
674            out.push(bound);
675            continue;
676        }
677        if let Def::Result { inst, .. } = func[arg].def {
678            if func.block_of(inst) == Some(block) {
679                return None;
680            }
681        }
682        out.push(arg);
683    }
684    Some(out)
685}
686
687/// Whether a path may walk past everything this block does on the way to its terminator.
688///
689/// The predicate is [`Opcode::has_effects`], which is what [`crate::dce`] deletes an instruction
690/// under, and the terminator is exempt because the thread is what replaces it. `is_terminator` on
691/// the function rather than on the opcode, for the reason dead code elimination gives: `asm goto`
692/// branches and its opcode does not say so.
693///
694/// A load answers that it has effects, so a block with one in it is not threaded past. That is
695/// conservative rather than necessary, since skipping a load skips a value nothing on the threaded
696/// path reads, and it is most of what [`WOULD_COPY_EFFECT`] turns out to be counting.
697fn skippable(func: &Func, block: Block) -> bool {
698    func.insts(block).all(|inst| func.is_terminator(inst) || !func[inst].opcode.has_effects())
699}
700
701/// Every block that defines a value read from somewhere other than itself.
702///
703/// The other half of what section 23.1's copy is for, and the half an argument list does not show.
704/// A block the candidate dominates reads what the candidate defined with nothing carrying it across,
705/// because dominance is the only permission a use needs in this IR. Point an edge past the candidate
706/// and that dominance is gone, so the read below is of a value nothing on the new path computed.
707///
708/// One walk for the whole function rather than one per candidate block. A thread with no copy only
709/// makes it stale in the safe direction, since it never adds a read of a value defined in the block
710/// it went past: [`carried`] refuses the edge when an arm carries one, and everything else it passes
711/// on was defined further up. A copy does add reads, of the parameters its merges put further down,
712/// so the pass walks again after each one.
713fn leaky(func: &Func) -> HashSet<Block> {
714    let mut out = HashSet::new();
715    for block in func.blocks().collect::<Vec<Block>>() {
716        for inst in func.insts(block).collect::<Vec<Inst>>() {
717            uses::operands(func, inst, |value| {
718                if let Some(home) = defined_in(func, value) {
719                    if home != block {
720                        out.insert(home);
721                    }
722                }
723            });
724        }
725    }
726    out
727}
728
729/// The block a value comes from, whether it is a parameter of one or a result computed in one.
730fn defined_in(func: &Func, value: Value) -> Option<Block> {
731    match func[value].def {
732        Def::Result { inst, .. } => func.block_of(inst),
733        Def::Param { block, .. } => Some(block),
734    }
735}
736
737/// Whether the loop structure survives pointing this edge at that block.
738///
739/// Section 23.5, and every answer of `false` is a refusal rather than a cost. Entering a loop
740/// anywhere but at its header makes the loop irreducible, moving a latch's edge is how the single
741/// latch property stops holding, and a block the forest has already given up on is one there is no
742/// useful answer about.
743fn allowed(loops: &Loops, from: Block, into: Block) -> bool {
744    if loops.is_irreducible(from) || loops.is_irreducible(into) {
745        return false;
746    }
747    if loops.all().any(|id| loops.latches(id).contains(&from)) {
748        return false;
749    }
750    let mut id = loops.innermost(into);
751    while let Some(loop_id) = id {
752        // Only a loop the predecessor is outside of, because an edge that stays within a loop is
753        // not a way into it.
754        if !loops.contains(loop_id, from) && loops.header(loop_id) != into {
755            return false;
756        }
757        id = loops.parent(loop_id);
758    }
759    true
760}
761
762#[cfg(test)]
763mod tests {
764    use rucc_base::Interner;
765    use rucc_ir::{
766        Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Restrict, Signature, Type, Value,
767    };
768
769    use std::collections::HashMap;
770
771    use rucc_ir::{Module, verify_func};
772    use rucc_target::{TargetInfo, Triple};
773
774    use super::{COPY, FREE, Thread};
775    use crate::stats::Kind;
776    use crate::{Fuel, Pass, Stats};
777
778    /// Runs the instance that copies nothing, with as much fuel as it wants.
779    fn thread(func: &mut Func) -> Stats {
780        FREE.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
781    }
782
783    /// Runs the instance that copies, and checks what it left is still a function.
784    fn copying(func: &mut Func) -> Stats {
785        copying_with(&COPY, func)
786    }
787
788    fn copying_with(pass: &Thread, func: &mut Func) -> Stats {
789        let stats =
790            pass.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
791        let mut names = Interner::new();
792        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
793        let module = Module::new(names.intern("t.c"), &target);
794        if let Err(errors) = verify_func(&module, func, &names) {
795            panic!("{errors:#?}");
796        }
797        stats
798    }
799
800    /// The blocks the function still has, by number.
801    fn blocks(func: &Func) -> Vec<usize> {
802        func.blocks().map(Block::index).collect()
803    }
804
805    /// Where a block's terminator goes, as block numbers.
806    fn goes_to(func: &Func, block: usize) -> Vec<usize> {
807        let block = Block::from_usize(block);
808        let term = func.terminator(block).expect("every block here has one");
809        func.successors(term).map(|call| call.block.index()).collect()
810    }
811
812    /// What a block's terminator carries on its first edge.
813    fn carries(func: &Func, block: usize) -> Vec<Value> {
814        let block = Block::from_usize(block);
815        let term = func.terminator(block).expect("every block here has one");
816        let call = func.successors(term).next().expect("a terminator here has an edge");
817        func[call.args].to_vec()
818    }
819
820    /// Section 23.3's example: two arms set one value to two constants and a join tests it.
821    ///
822    /// Block 0 is the entry, blocks 1 and 2 are the arms carrying `left` and `right`, block 3 is
823    /// the join and takes the value as a parameter, and blocks 4 and 5 are the two ways the test
824    /// can come out. The value the arms carry comes back, so a test can say which one was
825    /// substituted into what.
826    fn diamond(left: i128, right: i128) -> (Func, [Value; 2]) {
827        let mut names = Interner::new();
828        let mut func = Func::new(names.intern("f"), Signature::new());
829        let entry = func.create_block();
830        let arms = [func.create_block(), func.create_block()];
831        let join = func.create_block();
832        let param = func.append_param(join, Type::int(32));
833        let yes = func.create_block();
834        let no = func.create_block();
835
836        let mut build = Builder::new(&mut func, entry);
837        let cond = build.iconst(Type::int(1), 1);
838        build.br_if(cond, arms[0], &[], arms[1], &[]);
839        let mut sent = Vec::new();
840        for (arm, value) in arms.iter().zip([left, right]) {
841            let mut build = Builder::new(&mut func, *arm);
842            let it = build.iconst(Type::int(32), value);
843            sent.push(it);
844            build.jump(join, &[it]);
845        }
846        let mut build = Builder::new(&mut func, join);
847        let one = build.iconst(Type::int(32), 1);
848        let test = build.icmp(IntPred::Eq, param, one);
849        build.br_if(test, yes, &[], no, &[]);
850        for block in [yes, no] {
851            let mut build = Builder::new(&mut func, block);
852            build.ret(&[]);
853        }
854        (func, [sent[0], sent[1]])
855    }
856
857    #[test]
858    fn both_edges_of_a_join_that_decides_its_test_are_threaded() {
859        let (mut func, _) = diamond(1, 2);
860        let stats = thread(&mut func);
861        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 2);
862        // The arm carrying 1 goes to the true side and the arm carrying 2 to the false side, so
863        // the block that tested it has nothing left arriving at it.
864        assert_eq!(goes_to(&func, 1), vec![4]);
865        assert_eq!(goes_to(&func, 2), vec![5]);
866        // And nothing arrives at the block that tested it, so it goes with the same sweep
867        // `simplify-cfg` uses. The verifier holds a pass to that rather than letting the next one
868        // tidy up after it.
869        assert_eq!(blocks(&func), vec![0, 1, 2, 4, 5]);
870        assert_eq!(stats.count(Kind::Optimized, crate::simplify_cfg::REMOVED), 1);
871    }
872
873    #[test]
874    fn an_edge_that_does_not_decide_the_test_is_left_alone() {
875        let mut names = Interner::new();
876        let signature = Signature::new().with_params(&[Type::int(32)]);
877        let mut func = Func::new(names.intern("f"), signature);
878        let entry = func.create_block();
879        // A parameter of the function rather than a constant, so binding it to the block's
880        // parameter says nothing about the test.
881        let outside = func.append_param(entry, Type::int(32));
882        let join = func.create_block();
883        let param = func.append_param(join, Type::int(32));
884        let yes = func.create_block();
885        let no = func.create_block();
886
887        let mut build = Builder::new(&mut func, entry);
888        build.jump(join, &[outside]);
889        let mut build = Builder::new(&mut func, join);
890        let one = build.iconst(Type::int(32), 1);
891        let test = build.icmp(IntPred::Eq, param, one);
892        build.br_if(test, yes, &[], no, &[]);
893        for block in [yes, no] {
894            let mut build = Builder::new(&mut func, block);
895            build.ret(&[]);
896        }
897
898        let stats = thread(&mut func);
899        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
900        assert_eq!(goes_to(&func, 0), vec![1]);
901    }
902
903    #[test]
904    fn a_branch_decided_whichever_way_control_arrived_is_left_to_simplify_cfg() {
905        let mut names = Interner::new();
906        let mut func = Func::new(names.intern("f"), Signature::new());
907        let entry = func.create_block();
908        let arms = [func.create_block(), func.create_block()];
909        let join = func.create_block();
910        func.append_param(join, Type::int(32));
911        let yes = func.create_block();
912        let no = func.create_block();
913
914        let mut build = Builder::new(&mut func, entry);
915        let cond = build.iconst(Type::int(1), 1);
916        build.br_if(cond, arms[0], &[], arms[1], &[]);
917        for (arm, value) in arms.iter().zip([1, 2]) {
918            let mut build = Builder::new(&mut func, *arm);
919            let it = build.iconst(Type::int(32), value);
920            build.jump(join, &[it]);
921        }
922        let mut build = Builder::new(&mut func, join);
923        // The test reads nothing the edges carry, so it comes out the same way whichever edge
924        // control arrived on and it is `simplify-cfg`'s to fold once rather than this pass's to
925        // point every edge at separately.
926        let known = build.iconst(Type::int(1), 1);
927        build.br_if(known, yes, &[], no, &[]);
928        for block in [yes, no] {
929            let mut build = Builder::new(&mut func, block);
930            build.ret(&[]);
931        }
932
933        let stats = thread(&mut func);
934        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
935        assert_eq!(goes_to(&func, 1), vec![3]);
936        assert_eq!(goes_to(&func, 2), vec![3]);
937    }
938
939    #[test]
940    fn a_block_with_something_that_happens_in_it_needs_the_copy() {
941        let mut names = Interner::new();
942        let mut func = Func::new(names.intern("f"), Signature::new());
943        let entry = func.create_block();
944        let arms = [func.create_block(), func.create_block()];
945        let join = func.create_block();
946        let param = func.append_param(join, Type::int(32));
947        let yes = func.create_block();
948        let no = func.create_block();
949
950        let mut build = Builder::new(&mut func, entry);
951        let cond = build.iconst(Type::int(1), 1);
952        build.br_if(cond, arms[0], &[], arms[1], &[]);
953        for (arm, value) in arms.iter().zip([1, 2]) {
954            let mut build = Builder::new(&mut func, *arm);
955            let it = build.iconst(Type::int(32), value);
956            build.jump(join, &[it]);
957        }
958        let mut build = Builder::new(&mut func, join);
959        // A store above the test. It has to happen on every path that reached the block, so no
960        // path may walk past it, and threading either edge would be a path that did.
961        let what = build.iconst(Type::int(32), 7);
962        let address = build.iconst(Type::int(64), 16);
963        let address = build.unary(rucc_ir::Opcode::IntToPtr, address, Type::PTR);
964        let info = MemInfo {
965            size: 4,
966            align: 4,
967            order: MemOrder::NotAtomic,
968            tbaa: None,
969            owns: 0,
970            restrict: Restrict::NONE,
971        };
972        build.store(what, address, info, Flags::NONE);
973        let one = build.iconst(Type::int(32), 1);
974        let test = build.icmp(IntPred::Eq, param, one);
975        build.br_if(test, yes, &[], no, &[]);
976        for block in [yes, no] {
977            let mut build = Builder::new(&mut func, block);
978            build.ret(&[]);
979        }
980
981        let stats = thread(&mut func);
982        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
983        assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_EFFECT), 2);
984    }
985
986    /// The shape a clamp compiles to, which is where the corpus caught this being wrong.
987    ///
988    /// `raw < 15 ? 15 : raw` puts the value under test in a block parameter and then hands the same
989    /// parameter to the arm that did not change it. The arm reads it with nothing carrying it there,
990    /// because the join dominates the arm, and an edge threaded past the join is a path on which the
991    /// read has no value behind it. It compiled to a program that printed the wrong number.
992    fn clamp() -> Func {
993        clamp_with(|_, _| {})
994    }
995
996    /// The clamp with more in the join ahead of its test, put there by `extra` from the parameter.
997    fn clamp_with(extra: impl FnOnce(&mut Builder<'_>, Value)) -> Func {
998        let mut names = Interner::new();
999        let signature = Signature::new().with_returns(&[Type::int(32)]);
1000        let mut func = Func::new(names.intern("f"), signature);
1001        let entry = func.create_block();
1002        let arms = [func.create_block(), func.create_block()];
1003        let join = func.create_block();
1004        let param = func.append_param(join, Type::int(32));
1005        let yes = func.create_block();
1006        let no = func.create_block();
1007
1008        let mut build = Builder::new(&mut func, entry);
1009        let cond = build.iconst(Type::int(1), 1);
1010        build.br_if(cond, arms[0], &[], arms[1], &[]);
1011        for (arm, value) in arms.iter().zip([1, 2]) {
1012            let mut build = Builder::new(&mut func, *arm);
1013            let it = build.iconst(Type::int(32), value);
1014            build.jump(join, &[it]);
1015        }
1016        let mut build = Builder::new(&mut func, join);
1017        extra(&mut build, param);
1018        let one = build.iconst(Type::int(32), 1);
1019        let test = build.icmp(IntPred::Eq, param, one);
1020        build.br_if(test, yes, &[], no, &[]);
1021        let mut build = Builder::new(&mut func, yes);
1022        let floor = build.iconst(Type::int(32), 15);
1023        build.ret(&[floor]);
1024        // The read from below. Nothing on the edge carries the parameter here, and nothing has to,
1025        // since every path to this block goes through the block that defines it.
1026        let mut build = Builder::new(&mut func, no);
1027        build.ret(&[param]);
1028        func
1029    }
1030
1031    #[test]
1032    fn a_value_the_block_defines_and_something_below_it_reads_needs_the_copy() {
1033        let mut func = clamp();
1034        let stats = thread(&mut func);
1035        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
1036        assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_READ_BELOW), 2);
1037        assert_eq!(goes_to(&func, 1), vec![3]);
1038        assert_eq!(goes_to(&func, 2), vec![3]);
1039    }
1040
1041    /// What the copy is for: both edges of the clamp are threaded, and the read below gets the
1042    /// value that reached it along whichever one it came in by.
1043    #[test]
1044    fn a_copy_threads_the_clamp_and_the_read_below_gets_a_merge() {
1045        let mut func = clamp();
1046        let stats = copying(&mut func);
1047        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2, "{stats:?}");
1048        assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_READ_BELOW), 0);
1049        // The join is gone, since both edges into it went to copies, and each arm goes to a copy
1050        // that goes straight to the arm the value it carries decides.
1051        assert!(!blocks(&func).contains(&3), "{:?}", blocks(&func));
1052        let first = goes_to(&func, 1)[0];
1053        let second = goes_to(&func, 2)[0];
1054        assert_eq!(goes_to(&func, first), vec![4]);
1055        assert_eq!(goes_to(&func, second), vec![5]);
1056        // What the false arm returns is what the arm that took it carried, which is 2. The merge
1057        // gave the false arm a parameter while the join still had an edge to it, and once the
1058        // join was gone the parameter had one edge left and was swept down to the constant.
1059        let returned = Block::from_usize(5);
1060        let term = func.terminator(returned).expect("a return");
1061        let read = func[func[term].args][0];
1062        assert_eq!(super::defined_in(&func, read), Some(Block::from_usize(2)));
1063    }
1064
1065    #[test]
1066    fn a_copy_is_not_made_of_a_block_with_something_that_happens_in_it() {
1067        let mut func = clamp_with(|build, _| {
1068            let what = build.iconst(Type::int(32), 7);
1069            let address = build.iconst(Type::int(64), 16);
1070            let address = build.unary(rucc_ir::Opcode::IntToPtr, address, Type::PTR);
1071            let info = MemInfo {
1072                size: 4,
1073                align: 4,
1074                order: MemOrder::NotAtomic,
1075                tbaa: None,
1076                owns: 0,
1077                restrict: Restrict::NONE,
1078            };
1079            build.store(what, address, info, Flags::NONE);
1080        });
1081        let stats = copying(&mut func);
1082        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
1083        assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_EFFECT), 2);
1084    }
1085
1086    /// A block of more than fifteen instructions is not copied, and the same block with its sum a
1087    /// little shorter is.
1088    #[test]
1089    fn a_block_larger_than_the_budget_is_not_copied() {
1090        for (adds, copied) in [(13, 2), (14, 0)] {
1091            let mut func = clamp_with(|build, param| {
1092                let mut sum = param;
1093                for _ in 0..adds {
1094                    sum = build.binary(rucc_ir::Opcode::Add, sum, param, Flags::NONE);
1095                }
1096            });
1097            // The one, the test and the adds, which is fifteen and then sixteen.
1098            let stats = copying(&mut func);
1099            assert_eq!(stats.count(Kind::Optimized, super::COPIED), copied, "{adds}: {stats:?}");
1100            if copied == 0 {
1101                assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 2);
1102            }
1103        }
1104    }
1105
1106    #[test]
1107    fn a_path_of_copies_may_not_grow_past_its_limit() {
1108        let func = clamp();
1109        let an = crate::machine::fixtures::analyses();
1110        let join = Block::from_usize(3);
1111        let from = Block::from_usize(1);
1112        let into = Block::from_usize(5);
1113        let loops = an.loops(&func);
1114        let mut paths = HashMap::new();
1115        assert_eq!(COPY.path(&func, loops, join, from, into, &paths, 0), Ok(2));
1116        paths.insert(from, 99);
1117        assert_eq!(
1118            COPY.path(&func, loops, join, from, into, &paths, 0),
1119            Err(Some(super::TOO_LONG))
1120        );
1121        paths.insert(from, 98);
1122        assert_eq!(COPY.path(&func, loops, join, from, into, &paths, 0), Ok(100));
1123        assert_eq!(
1124            COPY.path(&func, loops, join, from, into, &paths, 64),
1125            Err(Some(super::TOO_MANY))
1126        );
1127        assert_eq!(FREE.path(&func, loops, join, from, into, &paths, 0), Err(None));
1128    }
1129
1130    /// Sixty four copies and no more, however many edges are left that want one.
1131    #[test]
1132    fn one_run_makes_at_most_sixty_four_copies() {
1133        let mut names = Interner::new();
1134        let signature =
1135            Signature::new().with_params(&[Type::int(32)]).with_returns(&[Type::int(32)]);
1136        let mut func = Func::new(names.intern("f"), signature);
1137        let entry = func.create_block();
1138        let pick = func.append_param(entry, Type::int(32));
1139        let arms: Vec<Block> = (0..70).map(|_| func.create_block()).collect();
1140        let join = func.create_block();
1141        let param = func.append_param(join, Type::int(32));
1142        let yes = func.create_block();
1143        let no = func.create_block();
1144        let cases: Vec<(i128, Block)> = (0..).zip(arms.iter().copied()).collect();
1145        let (&default, _) = arms.split_last().expect("arms");
1146        Builder::new(&mut func, entry).switch(pick, default, &cases[..69]);
1147        for (&arm, value) in arms.iter().zip(0..) {
1148            let mut build = Builder::new(&mut func, arm);
1149            let it = build.iconst(Type::int(32), value);
1150            build.jump(join, &[it]);
1151        }
1152        let mut build = Builder::new(&mut func, join);
1153        let one = build.iconst(Type::int(32), 1);
1154        let test = build.icmp(IntPred::Eq, param, one);
1155        // A sum the false arm reads, so that every edge still wants a copy after the first ones:
1156        // the merge they leave behind is carried a value the join works out.
1157        let sum = build.binary(rucc_ir::Opcode::Add, param, one, Flags::NONE);
1158        build.br_if(test, yes, &[], no, &[]);
1159        let mut build = Builder::new(&mut func, yes);
1160        let zero = build.iconst(Type::int(32), 0);
1161        build.ret(&[zero]);
1162        let mut build = Builder::new(&mut func, no);
1163        build.ret(&[sum]);
1164
1165        let stats = copying(&mut func);
1166        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 64, "{stats:?}");
1167        // The edge carrying 1 decides the true arm, which reads nothing of the join's. Once the
1168        // first copy put a merge on the false arm, nothing below read the join's values at all,
1169        // so that edge was threaded with no copy. The other five were left where they were.
1170        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1, "{stats:?}");
1171        assert_eq!(stats.count(Kind::Missed, super::TOO_MANY), 5, "{stats:?}");
1172    }
1173
1174    #[test]
1175    fn a_thread_back_to_a_loop_header_counts_each_instruction_twice() {
1176        let func = loop_with_a_parameter(2);
1177        let an = crate::machine::fixtures::analyses();
1178        let loops = an.loops(&func);
1179        let header = Block::from_usize(1);
1180        let body = Block::from_usize(2);
1181        assert!(super::back_edge(loops, body, header));
1182        assert!(!super::back_edge(loops, header, Block::from_usize(3)));
1183    }
1184
1185    #[test]
1186    fn an_arm_carrying_a_value_the_block_worked_out_is_threaded_through_a_copy() {
1187        let mut names = Interner::new();
1188        let signature = Signature::new().with_returns(&[Type::int(32)]);
1189        let mut func = Func::new(names.intern("f"), signature);
1190        let entry = func.create_block();
1191        let arms = [func.create_block(), func.create_block()];
1192        let join = func.create_block();
1193        let param = func.append_param(join, Type::int(32));
1194        let yes = func.create_block();
1195        let got = func.append_param(yes, Type::int(32));
1196        let no = func.create_block();
1197
1198        let mut build = Builder::new(&mut func, entry);
1199        let cond = build.iconst(Type::int(1), 1);
1200        build.br_if(cond, arms[0], &[], arms[1], &[]);
1201        for (arm, value) in arms.iter().zip([1, 2]) {
1202            let mut build = Builder::new(&mut func, *arm);
1203            let it = build.iconst(Type::int(32), value);
1204            build.jump(join, &[it]);
1205        }
1206        let mut build = Builder::new(&mut func, join);
1207        let one = build.iconst(Type::int(32), 1);
1208        let test = build.icmp(IntPred::Eq, param, one);
1209        let sum = build.binary(rucc_ir::Opcode::Add, param, one, Flags::NONE);
1210        build.br_if(test, yes, &[sum], no, &[]);
1211        let mut build = Builder::new(&mut func, yes);
1212        build.ret(&[got]);
1213        let mut build = Builder::new(&mut func, no);
1214        let zero = build.iconst(Type::int(32), 0);
1215        build.ret(&[zero]);
1216
1217        let stats = copying(&mut func);
1218        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1219        assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
1220        let copy = goes_to(&func, 1)[0];
1221        assert_eq!(goes_to(&func, copy), vec![4]);
1222        assert_eq!(carries(&func, copy).len(), 1);
1223    }
1224
1225    #[test]
1226    fn an_arm_carrying_a_value_the_block_worked_out_needs_the_copy() {
1227        let mut names = Interner::new();
1228        let mut func = Func::new(names.intern("f"), Signature::new());
1229        let entry = func.create_block();
1230        let arms = [func.create_block(), func.create_block()];
1231        let join = func.create_block();
1232        let param = func.append_param(join, Type::int(32));
1233        let yes = func.create_block();
1234        func.append_param(yes, Type::int(32));
1235        let no = func.create_block();
1236
1237        let mut build = Builder::new(&mut func, entry);
1238        let cond = build.iconst(Type::int(1), 1);
1239        build.br_if(cond, arms[0], &[], arms[1], &[]);
1240        for (arm, value) in arms.iter().zip([1, 2]) {
1241            let mut build = Builder::new(&mut func, *arm);
1242            let it = build.iconst(Type::int(32), value);
1243            build.jump(join, &[it]);
1244        }
1245        let mut build = Builder::new(&mut func, join);
1246        let one = build.iconst(Type::int(32), 1);
1247        let test = build.icmp(IntPred::Eq, param, one);
1248        // The true arm carries a sum this block worked out, which is exactly the value section
1249        // 23.1's copy of the block exists to make available on the threaded path.
1250        let sum = build.binary(rucc_ir::Opcode::Add, param, one, Flags::NONE);
1251        build.br_if(test, yes, &[sum], no, &[]);
1252        for block in [yes, no] {
1253            let mut build = Builder::new(&mut func, block);
1254            build.ret(&[]);
1255        }
1256
1257        let stats = thread(&mut func);
1258        // The edge carrying 2 takes the false arm, which carries nothing, so it threads. The one
1259        // carrying 1 takes the arm with the sum on it and is the one that would need the copy.
1260        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1261        assert_eq!(stats.count(Kind::Missed, super::WOULD_COPY_CARRIED), 1);
1262        assert_eq!(goes_to(&func, 2), vec![5]);
1263        assert_eq!(goes_to(&func, 1), vec![3]);
1264    }
1265
1266    #[test]
1267    fn the_block_parameter_is_substituted_into_what_the_arm_carries() {
1268        let mut names = Interner::new();
1269        let mut func = Func::new(names.intern("f"), Signature::new());
1270        let entry = func.create_block();
1271        let arms = [func.create_block(), func.create_block()];
1272        let join = func.create_block();
1273        let param = func.append_param(join, Type::int(32));
1274        let yes = func.create_block();
1275        func.append_param(yes, Type::int(32));
1276        let no = func.create_block();
1277
1278        let mut build = Builder::new(&mut func, entry);
1279        let cond = build.iconst(Type::int(1), 1);
1280        build.br_if(cond, arms[0], &[], arms[1], &[]);
1281        let mut sent = Vec::new();
1282        for (arm, value) in arms.iter().zip([1, 2]) {
1283            let mut build = Builder::new(&mut func, *arm);
1284            let it = build.iconst(Type::int(32), value);
1285            sent.push(it);
1286            build.jump(join, &[it]);
1287        }
1288        let mut build = Builder::new(&mut func, join);
1289        let one = build.iconst(Type::int(32), 1);
1290        let test = build.icmp(IntPred::Eq, param, one);
1291        // The arm passes the block's own parameter on, which along each edge is the constant that
1292        // edge was carrying.
1293        build.br_if(test, yes, &[param], no, &[]);
1294        for block in [yes, no] {
1295            let mut build = Builder::new(&mut func, block);
1296            build.ret(&[]);
1297        }
1298
1299        let stats = thread(&mut func);
1300        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 2);
1301        assert_eq!(goes_to(&func, 1), vec![4]);
1302        assert_eq!(carries(&func, 1), vec![sent[0]]);
1303    }
1304
1305    #[test]
1306    fn a_switch_the_edge_decides_is_threaded() {
1307        let mut names = Interner::new();
1308        let mut func = Func::new(names.intern("f"), Signature::new());
1309        let entry = func.create_block();
1310        let arms = [func.create_block(), func.create_block()];
1311        let join = func.create_block();
1312        let param = func.append_param(join, Type::int(32));
1313        let cases = [func.create_block(), func.create_block(), func.create_block()];
1314
1315        let mut build = Builder::new(&mut func, entry);
1316        let cond = build.iconst(Type::int(1), 1);
1317        build.br_if(cond, arms[0], &[], arms[1], &[]);
1318        for (arm, value) in arms.iter().zip([0, 1]) {
1319            let mut build = Builder::new(&mut func, *arm);
1320            let it = build.iconst(Type::int(32), value);
1321            build.jump(join, &[it]);
1322        }
1323        let mut build = Builder::new(&mut func, join);
1324        build.switch(param, cases[0], &[(0, cases[1]), (1, cases[2])]);
1325        for block in cases {
1326            let mut build = Builder::new(&mut func, block);
1327            build.ret(&[]);
1328        }
1329
1330        let stats = thread(&mut func);
1331        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 2);
1332        assert_eq!(goes_to(&func, 1), vec![cases[1].index()]);
1333        assert_eq!(goes_to(&func, 2), vec![cases[2].index()]);
1334    }
1335
1336    /// A loop whose header takes a parameter, entered from outside with a constant.
1337    ///
1338    /// Block 0 is the entry and jumps into the header carrying 1, block 1 is the header and tests
1339    /// its parameter, block 2 is the body and jumps back carrying the function's own parameter,
1340    /// block 3 is the way out and is where the test's false arm goes, and block 4 is somewhere
1341    /// outside the loop. Which block the true arm goes to is the caller's to choose, which is what
1342    /// makes one of these a thread into the middle of the loop and the other a thread onto a block
1343    /// the loop has nothing to do with.
1344    ///
1345    /// This is the only shape in which a thread can make a loop irreducible when nothing is copied.
1346    /// The block being threaded past has to be the header itself, because otherwise the arm being
1347    /// threaded onto was already a way into the loop from outside it and the loop was already
1348    /// irreducible before this pass looked at it.
1349    fn loop_with_a_parameter(arm: usize) -> Func {
1350        let mut names = Interner::new();
1351        let signature = Signature::new().with_params(&[Type::int(32)]);
1352        let mut func = Func::new(names.intern("f"), signature);
1353        let entry = func.create_block();
1354        let outside = func.append_param(entry, Type::int(32));
1355        let header = func.create_block();
1356        let param = func.append_param(header, Type::int(32));
1357        let body = func.create_block();
1358        let out = func.create_block();
1359        let elsewhere = func.create_block();
1360        let taken = [entry, header, body, out, elsewhere][arm];
1361
1362        let mut build = Builder::new(&mut func, entry);
1363        let one = build.iconst(Type::int(32), 1);
1364        build.jump(header, &[one]);
1365        let mut build = Builder::new(&mut func, header);
1366        let lit = build.iconst(Type::int(32), 1);
1367        let test = build.icmp(IntPred::Eq, param, lit);
1368        build.br_if(test, taken, &[], out, &[]);
1369        let mut build = Builder::new(&mut func, body);
1370        // Carrying the function's own parameter, so the edge back decides nothing and each of
1371        // these tests is about the one edge that comes from outside.
1372        build.jump(header, &[outside]);
1373        for block in [out, elsewhere] {
1374            let mut build = Builder::new(&mut func, block);
1375            build.ret(&[]);
1376        }
1377        func
1378    }
1379
1380    #[test]
1381    fn threading_into_a_loop_anywhere_but_its_header_is_refused() {
1382        // The true arm is the body, so pointing the edge from outside at it would give the loop a
1383        // second way in and make it irreducible.
1384        let mut func = loop_with_a_parameter(2);
1385        let stats = thread(&mut func);
1386        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 0);
1387        assert_eq!(stats.count(Kind::Missed, super::WOULD_BREAK_A_LOOP), 1);
1388        assert_eq!(goes_to(&func, 0), vec![1]);
1389    }
1390
1391    #[test]
1392    fn threading_onto_a_block_outside_the_loop_is_allowed() {
1393        // The true arm is in no loop at all, so the edge from outside can be pointed straight at
1394        // it and the loop keeps the one way in it had.
1395        let mut func = loop_with_a_parameter(4);
1396        let stats = thread(&mut func);
1397        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1398        assert_eq!(goes_to(&func, 0), vec![4]);
1399    }
1400
1401    #[test]
1402    fn threading_onto_the_header_of_a_loop_is_allowed() {
1403        let mut names = Interner::new();
1404        let mut func = Func::new(names.intern("f"), Signature::new());
1405        let entry = func.create_block();
1406        let join = func.create_block();
1407        let param = func.append_param(join, Type::int(32));
1408        let header = func.create_block();
1409        let out = func.create_block();
1410
1411        let mut build = Builder::new(&mut func, entry);
1412        let one = build.iconst(Type::int(32), 1);
1413        build.jump(join, &[one]);
1414        let mut build = Builder::new(&mut func, join);
1415        let lit = build.iconst(Type::int(32), 1);
1416        let test = build.icmp(IntPred::Eq, param, lit);
1417        build.br_if(test, header, &[], out, &[]);
1418        let mut build = Builder::new(&mut func, header);
1419        // A loop of one block, so the header is its own latch and the block being threaded onto
1420        // is the header itself, which is the way in the loop already has.
1421        let again = build.iconst(Type::int(1), 1);
1422        build.br_if(again, header, &[], out, &[]);
1423        let mut build = Builder::new(&mut func, out);
1424        build.ret(&[]);
1425
1426        let stats = thread(&mut func);
1427        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1428        assert_eq!(goes_to(&func, 0), vec![2]);
1429    }
1430
1431    #[test]
1432    fn fuel_stops_the_threading_where_it_stands() {
1433        let (mut func, _) = diamond(1, 2);
1434        let mut fuel = Fuel::of(1);
1435        let stats = FREE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
1436        assert_eq!(stats.count(Kind::Optimized, super::THREADED), 1);
1437        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1438        assert_eq!(goes_to(&func, 2), vec![3], "the second edge is where it was");
1439    }
1440}