Skip to main content

rucc_opt/
phiopt.rs

1//! If-conversion, the part of it that turns a diamond into a select.
2//!
3//! Design: `spec/optimizer/22-phiopt-and-if-conversion.md`. A block ends in a two way branch, each
4//! arm works out a value and does nothing else, and the two arms meet again at a block that takes
5//! that value as a parameter. The branch is not deciding what the program does, it is deciding
6//! which of two numbers to keep, and `select` says that directly. Section 22.2 asks for the shape
7//! matcher and five transformations built on it, and the shape matcher plus the first, the third
8//! and half of the fourth of them is what is here.
9//!
10//! This is the highest variance transformation in the compiler and the document says so in its
11//! third paragraph. Removing a mispredicted branch is worth about twenty cycles. Removing a
12//! perfectly predicted one costs whatever the arm that is no longer skipped costs, and no static
13//! analysis tells the two apart reliably. So the cost rule below is written to be argued with
14//! rather than to be right, and section 42's measurement of the pass on and off at `-O2` is the
15//! only honest evaluation there is.
16//!
17//! # The shape
18//!
19//! A head block ending in `br_if`, and a join block both arms reach. Each side of the branch is
20//! either a block of its own that does nothing but work out values and jump to the join, or the
21//! join itself. That gives three shapes and the pass takes all three: the diamond where both sides
22//! have a block, and the two triangles where one side goes straight to the join because the arm
23//! was empty and `simplify-cfg` already took it out.
24//!
25//! What replaces it is one block. Everything the arms worked out moves into the head, a `select`
26//! is built for each of the join's parameters the two sides disagree about, and the head jumps to
27//! the join carrying them. The arms are then unreachable and go, and the join is left for
28//! `simplify-cfg` to merge upward when nothing else arrives at it.
29//!
30//! # The operation both arms did
31//!
32//! Section 22.2's third transformation, `factor_out_conditional_operation`. When the two sides
33//! worked out their answers the same way from different operands, `cond ? f(a) : f(b)`, the select
34//! goes under the operation rather than over it and the answer is `f(cond ? a : b)`. One operation
35//! where there were two, and the same one select either way.
36//!
37//! It is structural and not a rewrite rule for the reason section 22.2 gives about all five: the
38//! two `f`s are in different blocks and no pattern spans blocks. By the time they are in one block
39//! the arms have already been hoisted and the select already written, and undoing that is a larger
40//! rewrite than never writing it.
41//!
42//! The two operations have to match in everything but one operand. The opcode and the operand count
43//! obviously. The flags, because those are what the optimizer is licensed to assume and one copy
44//! written under the union of two sets of assumptions would be claiming on one path something only
45//! the other path established. Whatever else the instruction carries, which for a comparison is the
46//! predicate, since two predicates are two different questions. And exactly one operand position
47//! apart, because two positions apart needs two selects and one operation, which is what one select
48//! and two operations already cost.
49//!
50//! Agreeing in every position is allowed and is the case where no select is written at all. Both
51//! arms working out the same thing from the same operands is what a common subexpression that
52//! nothing has numbered looks like from here, and one copy of it serves both sides.
53//!
54//! The operation has to be worked out in the arm and read only by the arm's jump to the join. The
55//! first because an operation to stop writing is one this has to be able to find. The second
56//! because the one copy that replaces the two is written after the arms have gone, and a second
57//! reader inside the arm would have been left pointing at an instruction that is no longer in any
58//! block.
59//!
60//! Only the value the join takes is asked about, so a chain both arms share is factored one deep.
61//! `total += (long long)(i * 2)` against `total += (long long)(i + 1)` has three operations in each
62//! arm, the outermost pair factors, the sign extensions under them are the same operation on
63//! different operands and would factor too, and they are not looked at because nothing hands them
64//! to the join. Doing it to a depth would mean factoring what the select then reads, which is the
65//! same function called on what it just produced, and it is left for when something asks for it.
66//!
67//! A constant operand is not refused and the reason is that it was measured and it goes both ways.
68//! An operation with a constant in it takes that constant as an immediate, so factoring turns two
69//! free immediates into a select between two values that have to be in registers, and on
70//! `product + 2` against `product + 1` outside a loop that costs five bytes. On `total += 1`
71//! against `total += 1000` inside one it saves fourteen, because the constants were being
72//! rematerialized every iteration anyway. Over the corpus, refusing every constant operand trades
73//! thirty two bytes of win for twenty two bytes of loss, which is ten bytes across 1453 programs
74//! and is not worth a rule.
75//!
76//! # The store both arms made
77//!
78//! Section 22.2's fourth transformation, conditional store replacement, in the half of it that
79//! needs no proof. When both arms store to the same place, `if (c) *p = a; else *p = b;` becomes
80//! `*p = c ? a : b`, and the branch goes with the rest of them.
81//!
82//! Half, and which half is the whole point. Section 22.6 calls the other half the worst bug in the
83//! document, because a store made on a path that was not going to make one writes memory the
84//! program was not going to write. The load modify store form GCC uses, reading the location and
85//! writing back what it read on the path that had no store, is not a no-op: it is a write, so it
86//! races with another thread writing the same bytes, and it faults if the page is read only. What
87//! would license it is knowing the location is written whatever happens, which is the predicate
88//! section 22.6 asks for and which nothing here can answer yet.
89//!
90//! When both arms store to the same address, that predicate is discharged by the shape itself and
91//! nothing has to be proved. One store before and one store after, to the same address, of a value
92//! the program was going to write there on one path or the other. Nothing new is written, nothing
93//! is written twice, and the order of that store against everything else in the function is where
94//! it was. So this is the case that goes in, and the one armed case is refused by name rather than
95//! by falling through the effects check, so that `-fopt-info-all` says which of the two it was.
96//!
97//! What has to match beyond the address is the access itself: the flags, and the alignment, size,
98//! aliasing node and `restrict` scope that a store carries alongside them, because the one store
99//! written below carries one of each and two that disagree have no single answer to carry. The
100//! address has to be the same value rather than a provably equal one, which is the strong form of
101//! the question and is the only form available without an alias analysis. It also settles where the
102//! address comes from: neither arm dominates the other, so a value both of them name is worked out
103//! at or above the head, and the one store is written where it is available.
104//!
105//! The same value rather than the same address is also where most of what this does not catch
106//! goes, so the two refusals are counted separately and say which. `if (x > 128) q[i] = 128; else
107//! q[i] = x;` works `q + i` out twice, once in each arm, and two instructions that compute the same
108//! address are two values, so this walks away from a diamond whose two stores go to the same place
109//! by any reading a person would give it. What fixes that is document 16's value numbering turning
110//! the two into one, not anything about memory, and hoisting the address by hand into `int *p =
111//! &q[i];` is enough to get the fold today.
112//!
113//! `volatile` and atomic are refused. `volatile` because section 22.6 says never, and the reason is
114//! not that the flags fail to match: how many accesses there are and what order they come in are
115//! both observable, and a value that arrives through a select is a different program from one that
116//! arrives through a branch. Atomic for the ordering rather than the access, since a store with an
117//! order on it is a fence as much as a write.
118//!
119//! # What a select is built for
120//!
121//! Two sides disagree about a parameter when they hand the join different values, and also when
122//! they hand it different values that are the same number. The second half is there because the
123//! corpus has eight diamonds whose two arms both work out the same constant, in separate
124//! instructions that nothing has hash consed into one, and the tier six rule `select(c, x, x) -> x`
125//! does not reach them for exactly the same reason: two operands that are not one value do not
126//! match a pattern that writes one name twice. What would reach them is document 12.1's hash
127//! consing or document 16's value numbering, and until one of those exists the cheap question is
128//! worth asking here, where the alternative is a `select` this pass wrote itself between two sevens.
129//!
130//! # Why moving an arm's work into the head is safe
131//!
132//! Because the arm has exactly one predecessor, which is the head. That is checked, and it is the
133//! whole of the argument in both directions.
134//!
135//! Downward: an instruction in the arm reads values that dominate the arm, and the head dominates
136//! the arm too, so every one of them is available where the instruction is going. Upward: nothing
137//! outside the arm can read what the arm defines except by the arm's own jump, since the arm
138//! dominates only itself, and that jump's arguments are exactly what the selects are built out of.
139//! An arm with two predecessors would break both halves at once, which is why the check is on the
140//! predecessor count and not on the shape of the graph around it.
141//!
142//! The loop rules that `spec/optimizer/23-jump-threading.md` needs are not needed here, and the
143//! reason is worth writing down rather than leaving as an absence. No edge is added, so no loop
144//! gains a second way in and no loop can become irreducible. An arm cannot be a loop header, since
145//! a header has a back edge and this arm has one predecessor and it is not itself. An arm can be a
146//! latch, and then the head becomes the latch instead, which keeps the single latch property
147//! document 07.3 wants rather than spoiling it. The one shape that would matter is a join that
148//! only its own arms reach, which is a region unreachable from the entry, and the pass asks
149//! whether the head is reachable before it looks at anything.
150//!
151//! # What it refuses, and every one of them is section 22.6
152//!
153//! An arm that does something. The predicate is [`rucc_ir::Opcode::has_effects`], which is what
154//! dead code elimination deletes an instruction under, so an arm this pass will hoist is an arm
155//! whose instructions could have been deleted outright had nothing read them. A call, a `volatile`
156//! access and a load are all effects by that answer, which closes the second and sixth failures in
157//! section 22.6 with one question. The one exception is the pair of stores above, which is the one
158//! effect this pass moves and is allowed to because moving it does not change what happens.
159//!
160//! A store the other side does not match. That is the first failure in section 22.6 and it gets a
161//! reason of its own rather than the general one, because it is a different answer rather than a
162//! stricter one: the transformation exists, it is section 22.2's fourth, and what is missing is the
163//! proof that the location is written whatever happens.
164//!
165//! An arm that divides. Division is not an effect, because nothing observes it and dead code
166//! elimination is right to delete one, but it traps, and a trap on a path that did not have one is
167//! section 22.6's third failure. The exception is a divisor that is a constant which is neither
168//! zero nor minus one, which cannot trap and is most of the divisions real code contains.
169//!
170//! A value the two sides disagree about whose type has no `select`. The IR names a `select` at
171//! eight, sixteen, thirty two and sixty four bit integers and at nothing else, so producing one of
172//! any other type would build a term the back end has no rule for. That is an invisible gap rather
173//! than a wrong answer, and the producer is the side that has to avoid it.
174//!
175//! A branch that is already decided. Section 22.6 does not list this one and the corpus found it,
176//! on a program whose source says `if (1)`. `simplify-cfg` runs after this pass and turns a decided
177//! branch into a jump, and then the arm that cannot run is deleted whole and its work with it.
178//! Converting first replaces a branch that costs nothing at run time with a select that costs
179//! something, and it keeps alive the work in the arm that never ran, because the fold that would
180//! undo it is `select(1, a, b) -> a` and that rule does not exist yet. The case cost twenty eight
181//! bytes of `.text` and a multiply that could not happen.
182//!
183//! The question is put to `simplify_cfg::taken` rather than answered again here, for the
184//! reason that function's own documentation gives: two answers about when a branch is decided
185//! would be two compilers. It matters in this case rather than being tidiness. The condition on
186//! `if (1)` is not a constant, it is `icmp ne 1, 0`, and `fold` leaves that standing on purpose,
187//! because nothing lowers an `i1` by itself and folding one would turn working code into code that
188//! does not build, which is issue 352. `taken` reads the answer off without leaving anything
189//! standing, since the branch that was the comparison's only reader goes at the same time.
190//!
191//! # The cost rule
192//!
193//! Section 22.2 states it and this implements it without softening it.
194//!
195//! Both arms empty of instructions: convert, always. The select replaces a branch with one
196//! operation that reads two values which already exist, and there is no machine where that is
197//! worse. Nothing about predictability enters, because there is nothing being speculated.
198//!
199//! What is factored does not count as work. Both arms did the operation, one of them was always
200//! going to do it, and afterwards one copy of it runs whichever way the branch would have gone, so
201//! nothing is being speculated. A diamond whose arms factor away entirely converts on the same
202//! terms as a diamond with empty arms, and one that factors down to two instructions is judged on
203//! the two rather than on what it started as.
204//!
205//! Arms with work left in them: up to [`heuristics::PHIOPT_ARM_INSTRUCTIONS`] instructions each,
206//! and only when the branch probability is within
207//! [`heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT`] of even by document 11's estimate. A branch
208//! the estimate calls one sided keeps its branch, because if the estimate is right the branch is
209//! free and the arm is not.
210//!
211//! The estimate is usually a guess and the guess is often wrong, which section 22.6 lists as the
212//! failure with no defence. Note where that leaves an unpredicted branch: document 11 answers even
213//! and says it is guessing, even is inside the margin, so a branch nothing is known about is
214//! treated as unpredictable and converted. That is the aggressive reading and it is deliberate,
215//! since the alternative is a pass that fires on almost nothing and measures nothing.
216//!
217//! It is also, today, the only reading, and that is worth saying rather than leaving to be
218//! discovered. Every static predictor in document 11 that gives a one sided answer keys on
219//! something one arm of the branch does and the other does not: one arm never comes back, one arm
220//! calls something cold, one arm leaves the loop, one arm returns a negative number. A diamond has
221//! neither of those, because both of its arms fall through to the same block, so the predictors
222//! that could refuse a conversion here are exactly the ones a diamond cannot trip. What is left is
223//! the branch condition itself, which is `__builtin_expect` at ninety percent and the pointer
224//! heuristic at seventy, and only the first of those is outside the margin. `__builtin_expect` is
225//! dropped in the front end today, so until it is wired the probability half of the rule refuses
226//! nothing at all. The check is here rather than deferred because leaving it out would mean the
227//! measurement never showed that, and because the day the hint is wired is the day it starts
228//! mattering.
229//!
230//! # Which level, and how many times
231//!
232//! Every level that optimizes, which is section 22.2's `-O1` and above.
233//!
234//! Once. Section 22.7 asks for two instances at `-O2`, one before the loop pipeline and one after,
235//! because the loop passes make diamonds. There is no loop pipeline yet, so the second instance
236//! would be a second walk over every function to find the shapes the first one already took, and
237//! it belongs in the change that adds the passes it exists to clean up after.
238//!
239//! Section 22.2 also wants a peephole run after this one, so that the rule set can answer what the
240//! `select` becomes: `select(c, a, a)` is `a`, `select(c, 1, 0)` is `zext(c)`, and the min, max and
241//! abs recognitions are all rules rather than code here. Those rules are tier six of
242//! `spec/optimizer/13-rewrite-rules.md` and none of them are written, so the run that would fire
243//! them is not in the pipeline yet either. It goes in with them.
244
245use rucc_cost::heuristics;
246use rucc_ir::{Block, Builder, Extra, Flags, Func, Inst, InstData, MemOrder, Opcode, Type, Value};
247
248use crate::cfg::Cfg;
249use crate::fold::constant;
250use crate::profile::Probability;
251use crate::simplify_cfg::{self, Bindings};
252use crate::{Analyses, Fuel, Pass, Preserved, Stats};
253
254/// Recorded once for each diamond that became a select.
255const CONVERTED: &str =
256    "branch whose two arms only work out a value replaced by the value and no branch";
257
258/// Recorded once for each operation both arms did that ended up being done once.
259const FACTORED: &str = "operation both arms did to different operands done once below the branch";
260
261/// Recorded once for each pair of stores to one place that became one store below the branch.
262const STORE_REPLACED: &str = "store both arms made to the same place made once below the branch";
263
264/// Recorded for a diamond one of whose arms does something that has to happen.
265const ARM_HAS_EFFECTS: &str =
266    "branch kept, an arm does something that only happens on the path it is on";
267
268/// Recorded for a diamond where only one of the two paths stores at all.
269const STORE_ON_ONE_PATH: &str =
270    "branch kept, a store only one path makes would have to be made on the other path too";
271
272/// Recorded for a diamond where both paths store but not the same store to the same place.
273const STORES_DO_NOT_MATCH: &str =
274    "branch kept, both paths store but not to one address the two of them name the same way";
275
276/// Recorded for a diamond one of whose arms divides by something that could be zero.
277const ARM_MAY_TRAP: &str = "branch kept, an arm divides and doing it on both paths could trap";
278
279/// Recorded for a diamond whose two arms disagree about a value nothing can choose between.
280const NO_SELECT_AT_THAT_WIDTH: &str =
281    "branch kept, the value the arms disagree about is not a width a select is lowered at";
282
283/// Recorded for a diamond whose arms are more work than the branch is worth.
284const ARMS_TOO_LONG: &str = "branch kept, its arms are more work than doing both of them is worth";
285
286/// Recorded for a diamond whose branch the estimate says the machine will get right.
287const BRANCH_IS_PREDICTED: &str =
288    "branch kept, it goes one way often enough that the machine will predict it";
289
290/// Recorded for a diamond that would have been converted if there had been fuel for it.
291const CONDITION_IS_DECIDED: &str =
292    "branch kept, its condition is already known and the arm that cannot run is better deleted";
293const NO_FUEL: &str = "branch kept, the pass ran out of fuel";
294
295/// The pass.
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub struct PhiOpt;
298
299impl Pass for PhiOpt {
300    fn name(&self) -> &'static str {
301        "phiopt"
302    }
303
304    fn describe(&self) -> &'static str {
305        "a branch whose two arms only work out a value becomes a select, and the branch goes"
306    }
307
308    fn preserves(&self) -> Preserved {
309        // Nothing. Blocks stop existing and an edge stops existing with them, so every analysis
310        // built on the graph was built on a different graph.
311        Preserved::NONE
312    }
313
314    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
315        let mut stats = Stats::new();
316        if func.entry().is_none() {
317            return stats;
318        }
319        for head in func.blocks().collect::<Vec<Block>>() {
320            let cfg = an.cfg(func);
321            if !cfg.reaches(head) {
322                continue;
323            }
324            let Some(shape) = diamond(func, cfg, head) else { continue };
325            let store = storing(func, &shape);
326            if let Some(reason) = refused(func, &shape, store.as_ref()) {
327                stats.missed(reason);
328                continue;
329            }
330            let plan = factoring(func, &shape);
331            // What is factored is not speculated. Both arms did the operation, one of them was
332            // always going to do it, and after this one copy of it runs whichever way the branch
333            // would have gone. So it comes off the count the cost rule is about, and a diamond
334            // whose arms factor away entirely converts on the same terms as a diamond with empty
335            // arms: always, because there is nothing being done that was not being done before.
336            // The store both arms made comes off the count for the same reason a factored operation
337            // does. One of the two was always going to run, and afterwards one copy of it runs
338            // whichever way the branch would have gone, so nothing about memory is being speculated.
339            let replaced = plan.iter().flatten().count() + usize::from(store.is_some());
340            let saved = u32::try_from(replaced).unwrap_or(u32::MAX);
341            let work = shape
342                .arms
343                .map(|arm| arm.map_or(0, |block| length(func, block)).saturating_sub(saved));
344            if work.iter().any(|&count| count > 0) {
345                if work.iter().any(|&count| count > heuristics::PHIOPT_ARM_INSTRUCTIONS) {
346                    stats.missed(ARMS_TOO_LONG);
347                    continue;
348                }
349                // The first edge out of the head, which is the arm taken when the condition holds,
350                // because `Cfg::successors` is in the order the terminator names its targets. Which
351                // of the two is asked about does not matter, since the question is whether the
352                // number is near even and the other edge is its complement.
353                if !unpredictable(an.frequencies(func).taken(head, 0)) {
354                    stats.missed(BRANCH_IS_PREDICTED);
355                    continue;
356                }
357            }
358            if !fuel.take() {
359                // Where the pass stops rather than where it starts skipping, for the reason jump
360                // threading gives: a budget that has reached zero will not have anything in it at
361                // the next block either, and the refusals above are the counts worth being true.
362                stats.missed(NO_FUEL);
363                break;
364            }
365            convert(func, &shape, &plan, store.as_ref());
366            // The graph was about the function as it was a moment ago, and the manager clears the
367            // cache after the pass returns, which is too late for the next block.
368            an.clear();
369            for _ in plan.iter().flatten() {
370                stats.optimized(FACTORED);
371            }
372            if store.is_some() {
373                stats.optimized(STORE_REPLACED);
374            }
375            stats.optimized(CONVERTED);
376        }
377        stats
378    }
379}
380
381/// A branch whose two arms meet again, and what each of them hands the block they meet at.
382pub(crate) struct Diamond {
383    /// The block the branch is in.
384    pub(crate) head: Block,
385    /// The bit the branch is on, which is the bit the selects are on.
386    pub(crate) cond: Value,
387    /// The block both arms reach.
388    pub(crate) join: Block,
389    /// The block on each side, when that side is a block of its own rather than the join.
390    ///
391    /// Index zero is the side taken when the condition holds, which is the side `select` calls
392    /// `then`, and the order is the order the terminator names its targets in.
393    pub(crate) arms: [Option<Block>; 2],
394    /// What each side hands the join, in the order the join takes its parameters.
395    pub(crate) args: [Vec<Value>; 2],
396}
397
398/// The diamond this block is the head of, if it is the head of one.
399pub(crate) fn diamond(func: &Func, cfg: &Cfg, head: Block) -> Option<Diamond> {
400    let entry = cfg.entry()?;
401    let term = func.terminator(head)?;
402    if func[term].opcode != Opcode::BrIf {
403        return None;
404    }
405    let cond = *func[func[term].args].first()?;
406    let mut targets = func.successors(term);
407    let sides = [targets.next()?, targets.next()?];
408    // Both arms at the same block is a branch that goes to one place carrying two argument lists.
409    // It is convertible and it is rare enough not to be worth a second shape, and `simplify-cfg`
410    // takes the case where the two lists agree.
411    if sides[0].block == sides[1].block {
412        return None;
413    }
414    let through = [
415        passes_through(func, cfg, head, sides[0].block),
416        passes_through(func, cfg, head, sides[1].block),
417    ];
418    // The diamond, then the two triangles. A side that is not the join has to be a block that
419    // reaches it, which is what makes the arm below a side that has one.
420    let join = match through {
421        [Some(left), Some(right)] if left == right => left,
422        [Some(left), _] if left == sides[1].block => left,
423        [_, Some(right)] if right == sides[0].block => right,
424        _ => return None,
425    };
426    // A join that is the head is a loop with nothing outside it, and one that is the entry is a
427    // block control arrives at rather than one it reaches.
428    if join == head || join == entry {
429        return None;
430    }
431    let arms = [
432        (sides[0].block != join).then_some(sides[0].block),
433        (sides[1].block != join).then_some(sides[1].block),
434    ];
435    let mut args = [Vec::new(), Vec::new()];
436    for (index, side) in sides.iter().enumerate() {
437        let carried = match arms[index] {
438            // The arm's own jump is what tells the join what this side worked out.
439            Some(arm) => func.successors(func.terminator(arm)?).next()?.args,
440            None => side.args,
441        };
442        args[index] = func[carried].to_vec();
443    }
444    Some(Diamond { head, cond, join, arms, args })
445}
446
447/// Where this side of the branch ends up, when it is a block whose only job is to get there.
448///
449/// Everything this asks is needed. Parameters, because a block that takes them is being told
450/// something on the edge and there would be nothing to tell it once the edge is gone. One
451/// predecessor and it being the head, because that is the whole argument for moving the block's
452/// work upward and it is also what makes removing the block afterwards legal. A jump, because an
453/// arm that branches is a second decision and this pass is about one.
454fn passes_through(func: &Func, cfg: &Cfg, head: Block, block: Block) -> Option<Block> {
455    if !func[block].params.is_empty() {
456        return None;
457    }
458    match cfg.predecessors(block) {
459        [only] if *only == head => {}
460        _ => return None,
461    }
462    let term = func.terminator(block)?;
463    if func[term].opcode != Opcode::Jump {
464        return None;
465    }
466    Some(func.successors(term).next()?.block)
467}
468
469/// Why this diamond is left alone, or `None` when nothing is in the way.
470///
471/// The store plan is passed in because the two stores it names are the one pair of instructions
472/// with effects this pass is allowed to move, and everything else with an effect still refuses.
473fn refused(func: &Func, shape: &Diamond, store: Option<&Stored>) -> Option<&'static str> {
474    // A branch nobody has to take is not a branch worth removing. `simplify-cfg` runs after this
475    // pass and turns a decided branch into a jump, and then the arm that cannot run is deleted
476    // whole. Converting first replaces a branch that costs nothing with a select that costs
477    // something, and the fold that would undo it is a rule the set does not have yet, so the work
478    // in the arm that never ran survives into the machine code. The corpus found this on `if (1)`.
479    //
480    // The question is put to `simplify-cfg` rather than answered again here, for the reason its
481    // own documentation gives: two answers about when a branch is decided would be two compilers.
482    // It matters in this case, because the condition on `if (1)` is not a constant, it is a
483    // comparison of two constants, which `fold` deliberately leaves standing.
484    let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
485    if simplify_cfg::taken(func, term, &Bindings::new()).is_some() {
486        return Some(CONDITION_IS_DECIDED);
487    }
488    let moving = store.map(|one| one.insts);
489    for &arm in shape.arms.iter().flatten() {
490        for inst in func.insts(arm) {
491            if func.is_terminator(inst) || moving.is_some_and(|two| two.contains(&inst)) {
492                continue;
493            }
494            if func[inst].opcode == Opcode::Store {
495                // Named separately from the effects below because it is a different answer rather
496                // than a stricter one. A store the other side does not make is section 22.2's
497                // fourth transformation without its proof, and section 22.6 calls it the worst bug
498                // in the document: making it on both paths writes memory the program was not going
499                // to write, which is not a no-op if another thread is writing the same bytes and is
500                // not a no-op if the page is read only. What would license it is knowing the
501                // location is written whatever happens, and nothing here knows that yet.
502                return Some(mismatch(func, shape));
503            }
504            if func[inst].opcode.has_effects() {
505                return Some(ARM_HAS_EFFECTS);
506            }
507            if !speculatable(func, inst) {
508                return Some(ARM_MAY_TRAP);
509            }
510        }
511    }
512    let params = func[shape.join].params.iter();
513    for ((&param, &then), &other) in params.zip(&shape.args[0]).zip(&shape.args[1]) {
514        // The two sides agreeing about a parameter is the common case in a triangle, where one
515        // side passes on what it was already holding, and it needs no select at all.
516        if agree(func, then, other) {
517            continue;
518        }
519        if !selectable(func[param].ty) {
520            return Some(NO_SELECT_AT_THAT_WIDTH);
521        }
522    }
523    None
524}
525
526/// Whether the two sides hand the join the same thing, so that no `select` is needed for it.
527///
528/// The same value is the easy answer and it is the one a triangle gives, where one side passes on
529/// what it was already holding. The same constant is the answer the corpus asked for. `x ? 7 : 7`
530/// arrives here as two `iconst.i32 7` instructions, one in each arm, which are two values because
531/// nothing has hash consed them into one. Asking only about the value builds a `select` between two
532/// sevens, which costs a compare, a byte and a conditional move to work out that seven is seven.
533/// The module comment says what the general answer would be and why it is not available yet.
534fn agree(func: &Func, then: Value, other: Value) -> bool {
535    if then == other {
536        return true;
537    }
538    let (Some((left, lty)), Some((right, rty))) = (constant(func, then), constant(func, other))
539    else {
540        return false;
541    };
542    lty == rty && left == right
543}
544
545/// Whether doing this on a path that was not going to do it is harmless.
546///
547/// Only division asks anything here, because the caller has already refused everything with an
548/// effect and what is left is arithmetic. Zero is the divisor everybody knows about. Minus one is
549/// the other one: the smallest signed number divided by it is not representable and x86 raises the
550/// same exception it raises for zero.
551pub(crate) fn speculatable(func: &Func, inst: Inst) -> bool {
552    let opcode = func[inst].opcode;
553    if !matches!(opcode, Opcode::SDiv | Opcode::UDiv | Opcode::SRem | Opcode::URem) {
554        return true;
555    }
556    let Some(&divisor) = func[func[inst].args].get(1) else { return false };
557    let Some((imm, ty)) = constant(func, divisor) else { return false };
558    if imm.unsigned() == 0 {
559        return false;
560    }
561    imm.signed(ty) != -1
562}
563
564/// A store both arms make to the same place, which becomes one store below the branch.
565///
566/// Section 22.2's fourth transformation, in the half of it that needs no proof. `if (c) *p = a;
567/// else *p = b;` is `*p = c ? a : b`, and the number of stores is one before and one after, to the
568/// same address, of a value the program was going to write there on one path or the other.
569struct Stored {
570    /// The store each side wrote, which goes when the one copy below replaces both.
571    insts: [Inst; 2],
572    /// What each side wrote, taken in the order the branch names its targets.
573    values: [Value; 2],
574    /// The address, which is one value both sides named.
575    addr: Value,
576    /// The store to write once, whose value operand is replaced by the select above it.
577    data: InstData,
578}
579
580/// Which of the two store refusals this diamond is, once it is known to be one of them.
581///
582/// The two are worth separating because they say different things about what would fix them. One
583/// path storing is section 22.6's predicate, which is a proof nothing here can do. Both paths
584/// storing and not matching is usually two arms that worked the same address out separately, which
585/// is `a[i] = ...` on both sides, and what fixes that is document 16's value numbering making the
586/// two into one value rather than anything about memory.
587fn mismatch(func: &Func, shape: &Diamond) -> &'static str {
588    let [Some(then), Some(other)] = shape.arms else { return STORE_ON_ONE_PATH };
589    match (stored_in(func, then), stored_in(func, other)) {
590        (Some(_), Some(_)) => STORES_DO_NOT_MATCH,
591        _ => STORE_ON_ONE_PATH,
592    }
593}
594
595/// The store this diamond can move below the branch, if it has one.
596///
597/// Both sides have to have a block, which is what makes this the safe half of the transformation.
598/// A triangle has one side that is the join, and a store in the join already runs whichever way the
599/// branch went, so there is nothing here to move and the shape that reaches this with one arm is
600/// the one where a store happens on one path only. That one is refused above.
601fn storing(func: &Func, shape: &Diamond) -> Option<Stored> {
602    let [Some(then), Some(other)] = shape.arms else { return None };
603    let insts = [stored_in(func, then)?, stored_in(func, other)?];
604    let data = [func[insts[0]], func[insts[1]]];
605    // The flags are what the optimizer is licensed to assume about the access, so one store written
606    // under the union of two sets of assumptions would be claiming on one path something only the
607    // other path established. `volatile` is refused outright rather than by disagreeing, because
608    // section 22.6 says never and because the reason is not the flag matching: both how many
609    // accesses there are and what order they come in are observable, and a value that arrives
610    // through a select is a different program from one that arrives through a branch.
611    if data[0].flags != data[1].flags || data[0].flags.contains(Flags::VOLATILE) {
612        return None;
613    }
614    let (Extra::Mem(one), Extra::Mem(two)) = (data[0].extra, data[1].extra) else { return None };
615    // The alignment, the size, the aliasing node and the `restrict` scope, all of which the one
616    // store carries forward, so two that disagree about any of them have no single answer to carry.
617    if func[one] != func[two] || func[one].order != MemOrder::NotAtomic {
618        return None;
619    }
620    // A store names what it writes and then where, which is the order the builder takes them in.
621    let &[then, addr] = func[data[0].args].first_chunk::<2>()?;
622    let &[other, addr_two] = func[data[1].args].first_chunk::<2>()?;
623    // The same value for the address, which is stronger than the same address and is what can be
624    // checked without an alias analysis. It also settles where that value comes from: neither arm
625    // dominates the other, so a value both of them name is one worked out at or above the head, and
626    // the one store is written in the head where it is available.
627    if addr != addr_two || func[then].ty != func[other].ty {
628        return None;
629    }
630    if !agree(func, then, other) && !selectable(func[then].ty) {
631        return None;
632    }
633    Some(Stored { insts, values: [then, other], addr, data: data[0] })
634}
635
636/// The one store this arm makes, if it makes exactly one and does nothing else that has to happen.
637///
638/// Exactly one, because two stores below one select is two selects and a shape nothing has asked
639/// for. Nothing else with an effect, because everything else with an effect is still refused and
640/// this is the check that says so: an arm that stores and also calls something has a call that only
641/// happens on the path it is on, and no amount of agreement about the store changes that.
642fn stored_in(func: &Func, arm: Block) -> Option<Inst> {
643    let mut store = None;
644    for inst in func.insts(arm) {
645        if func.is_terminator(inst) || !func[inst].opcode.has_effects() {
646            continue;
647        }
648        if func[inst].opcode != Opcode::Store || store.is_some() {
649            return None;
650        }
651        store = Some(inst);
652    }
653    store
654}
655
656/// One join argument both arms worked out the same way, and the one operand they disagreed about.
657///
658/// Section 22.2's third transformation. `cond ? f(a) : f(b)` is `f(cond ? a : b)`, which is one
659/// operation where there were two and one select either way, and it is structural rather than a
660/// rewrite rule because the two `f`s are in different blocks and no pattern spans blocks.
661struct Factored {
662    /// The instruction each side wrote, which goes when the one copy below replaces both.
663    insts: [Inst; 2],
664    /// What each side handed that instruction, taken from the side taken when the condition holds.
665    operands: Vec<Value>,
666    /// The one position the two sides put different values in, and what each of them put there.
667    ///
668    /// `None` when they agree in every position, which is both arms computing the same thing from
669    /// the same operands. Then one copy serves both and there is no select at all.
670    differ: Option<(usize, [Value; 2])>,
671    /// The instruction to write once, whose operand list is replaced by the one above.
672    data: InstData,
673    /// What it produces.
674    ty: Type,
675}
676
677/// What can be factored out of each of the join's parameters, in the order the join takes them.
678///
679/// A triangle factors nothing. One of its sides is the join itself, so there is no block on that
680/// side holding an operation to pair the other one with, and what that side hands the join is a
681/// value worked out before the branch.
682fn factoring(func: &Func, shape: &Diamond) -> Vec<Option<Factored>> {
683    let count = shape.args[0].len();
684    let [Some(then), Some(other)] = shape.arms else {
685        return (0..count).map(|_| None).collect();
686    };
687    (0..count).map(|index| factored(func, shape, [then, other], index)).collect()
688}
689
690/// Whether this join argument is the same operation on both sides, and what to write instead.
691fn factored(func: &Func, shape: &Diamond, arms: [Block; 2], index: usize) -> Option<Factored> {
692    let sides = [shape.args[0][index], shape.args[1][index]];
693    // Two sides that agree need no operation written at all, and the caller passes the value on.
694    if agree(func, sides[0], sides[1]) {
695        return None;
696    }
697    let insts = [written_in(func, arms[0], sides[0])?, written_in(func, arms[1], sides[1])?];
698    let data = [func[insts[0]], func[insts[1]]];
699    // Everything about the two has to match except the operands. The flags are what the optimizer
700    // is licensed to assume, so writing one copy under the union of two sets of assumptions would
701    // be claiming on one path something only the other path established. The extra is whatever the
702    // instruction carries that is not an operand, which for a comparison is the predicate, and two
703    // predicates that differ are two different questions.
704    if data[0].opcode != data[1].opcode || data[0].flags != data[1].flags {
705        return None;
706    }
707    if data[0].extra != data[1].extra || func[sides[0]].ty != func[sides[1]].ty {
708        return None;
709    }
710    let operands = [func[data[0].args].to_vec(), func[data[1].args].to_vec()];
711    if operands[0].len() != operands[1].len() {
712        return None;
713    }
714    let mut apart =
715        operands[0].iter().zip(&operands[1]).enumerate().filter(|(_, (one, two))| one != two);
716    let differ = match (apart.next(), apart.next()) {
717        // Two positions apart would need two selects, and two selects and one operation is what
718        // one select and two operations already cost. There is nothing to win, so it is left.
719        (_, Some(_)) => return None,
720        (Some((at, (&one, &two))), None) => {
721            if func[one].ty != func[two].ty || !selectable(func[one].ty) {
722                return None;
723            }
724            Some((at, [one, two]))
725        }
726        (None, None) => None,
727    };
728    let ty = func[sides[0]].ty;
729    Some(Factored { insts, operands: operands[0].clone(), differ, data: data[0], ty })
730}
731
732/// The instruction in this arm that works out this value, if the arm is where it comes from and the
733/// only thing that reads it is the jump to the join.
734///
735/// Both halves are needed. The arm has to be where it is worked out, because an operation to factor
736/// out is one this pass is about to stop writing and it can only stop writing what it can find.
737/// Nothing else can read it, because the one copy that replaces the two is written after the arms
738/// have gone and a second reader in the arm would have been left pointing at an instruction that is
739/// no longer in any block.
740fn written_in(func: &Func, arm: Block, value: Value) -> Option<Inst> {
741    let inst = func
742        .insts(arm)
743        .find(|&inst| func[inst].results == 1 && func[inst].first_result == Some(value))?;
744    let mut seen = 0;
745    for inst in func.insts(arm) {
746        seen += func[func[inst].args].iter().filter(|&&arg| arg == value).count();
747        for call in func.successors(inst) {
748            seen += func[call.args].iter().filter(|&&arg| arg == value).count();
749        }
750    }
751    (seen == 1).then_some(inst)
752}
753
754/// Whether a value of this type is one a `select` can choose.
755///
756/// The four widths `crates/rucc-ir/src/term.rs` names a `select` at. A wider integer, a float, a
757/// pointer, a bit or a vector has no head, so a `select` of one would be a term the rule set has
758/// no lowering for and the failure would be at instruction selection rather than here.
759fn selectable(ty: Type) -> bool {
760    ty.is_scalar() && ty.is_int() && matches!(ty.bits(), 8 | 16 | 32 | 64)
761}
762
763/// How much work an arm does, not counting the jump that is about to go.
764pub(crate) fn length(func: &Func, block: Block) -> u32 {
765    let count = func.insts(block).filter(|&inst| !func.is_terminator(inst)).count();
766    u32::try_from(count).unwrap_or(u32::MAX)
767}
768
769/// Whether the estimate leaves enough doubt about this branch to be worth removing it.
770pub(crate) fn unpredictable(taken: Probability) -> bool {
771    let margin = heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT * (Probability::SCALE / 100);
772    taken.parts() >= margin && taken.parts() <= Probability::SCALE - margin
773}
774
775/// Moves the arms into the head, builds the selects and takes the branch out.
776///
777/// The order matters and is the reason this is one function. The branch goes first, so that what
778/// the arms were doing can be appended to the head without anything having to be threaded around a
779/// terminator. The selects are built after that work has moved, since they read what it produced.
780/// The jump goes last because it is the terminator.
781fn convert(func: &mut Func, shape: &Diamond, plan: &[Option<Factored>], store: Option<&Stored>) {
782    let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
783    let span = func.span(term);
784    func.remove_inst(term);
785    let mut dropped: Vec<Inst> = plan.iter().flatten().flat_map(|one| one.insts).collect();
786    dropped.extend(store.iter().flat_map(|one| one.insts));
787    for &arm in shape.arms.iter().flatten() {
788        for inst in func.insts(arm).collect::<Vec<Inst>>() {
789            if func.is_terminator(inst) {
790                continue;
791            }
792            func.remove_inst(inst);
793            // A factored operation is not moved, it is replaced. One copy of it is written below,
794            // after the selects it reads, and these two are what that copy is instead of.
795            if !dropped.contains(&inst) {
796                func.append_inst(shape.head, inst);
797            }
798        }
799    }
800    let mut build = Builder::new(func, shape.head).at(span);
801    let mut args = Vec::with_capacity(shape.args[0].len());
802    for (index, (&then, &other)) in shape.args[0].iter().zip(&shape.args[1]).enumerate() {
803        if let Some(one) = &plan[index] {
804            let mut operands = one.operands.clone();
805            if let Some((at, sides)) = one.differ {
806                operands[at] = build.select(shape.cond, sides[0], sides[1]);
807            }
808            let list = build.func().push_values(&operands);
809            args.push(build.value(InstData { args: list, ..one.data }, one.ty));
810            continue;
811        }
812        // The condition holds on the first side, which is the side `select` takes when the bit is
813        // one, so the order the branch named its targets in is the order the arguments go in.
814        let same = agree(build.func(), then, other);
815        args.push(if same { then } else { build.select(shape.cond, then, other) });
816    }
817    // After everything the arms were doing has moved, because the value being stored is often one
818    // of the things they were working out, and before the jump because the jump is the terminator.
819    if let Some(one) = store {
820        let [then, other] = one.values;
821        let same = agree(build.func(), then, other);
822        let what = if same { then } else { build.select(shape.cond, then, other) };
823        let list = build.func().push_values(&[what, one.addr]);
824        build.inst(InstData { args: list, ..one.data }, &[]);
825    }
826    build.jump(shape.join, &args);
827    // Nothing arrives at the arms now, and section 6.5 makes taking an unreachable block out the
828    // standing obligation of whichever pass stranded it rather than something the next pass tidies
829    // up. The verifier holds every pass to that.
830    for &arm in shape.arms.iter().flatten() {
831        func.remove_block(arm);
832    }
833}
834
835#[cfg(test)]
836mod tests {
837    use rucc_base::Interner;
838    use rucc_ir::{
839        Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict, Signature, Type,
840        Value,
841    };
842
843    use super::PhiOpt;
844    use crate::profile::{Probability, Quality};
845    use crate::stats::Kind;
846    use crate::{Analyses, Fuel, Pass, Stats};
847
848    /// Runs the pass with as much fuel as it wants.
849    fn phiopt(func: &mut Func) -> Stats {
850        PhiOpt.run(func, &mut Analyses::new(), &mut Fuel::unlimited())
851    }
852
853    /// The blocks the function still has, by number.
854    fn blocks(func: &Func) -> Vec<usize> {
855        func.blocks().map(Block::index).collect()
856    }
857
858    /// Where a block's terminator goes, as block numbers.
859    fn goes_to(func: &Func, block: usize) -> Vec<usize> {
860        let block = Block::from_usize(block);
861        let term = func.terminator(block).expect("every block here has one");
862        func.successors(term).map(|call| call.block.index()).collect()
863    }
864
865    /// The opcodes a block holds, in order.
866    fn opcodes(func: &Func, block: usize) -> Vec<Opcode> {
867        let block = Block::from_usize(block);
868        func.insts(block).map(|inst| func[inst].opcode).collect()
869    }
870
871    /// What a block's terminator carries on its first edge.
872    fn carries(func: &Func, block: usize) -> Vec<Value> {
873        let block = Block::from_usize(block);
874        let term = func.terminator(block).expect("every block here has one");
875        let call = func.successors(term).next().expect("a terminator here has an edge");
876        func[call.args].to_vec()
877    }
878
879    /// Four aligned bytes, ordinary, with nothing known about aliasing.
880    fn plain() -> MemInfo {
881        MemInfo {
882            size: 4,
883            align: 4,
884            order: MemOrder::NotAtomic,
885            tbaa: None,
886            restrict: Restrict::NONE,
887        }
888    }
889
890    /// A store, which is the instruction used here whenever something has to happen.
891    fn store_something(build: &mut Builder<'_>) {
892        let what = build.iconst(Type::int(32), 7);
893        let address = build.iconst(Type::int(64), 16);
894        let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
895        build.store(what, address, plain(), Flags::NONE);
896    }
897
898    /// `if (x < 0) *p = a; else *p = b;`, with both stores told the same thing about the access.
899    ///
900    /// The address is a function parameter, so it is one value both arms name, and the two values
901    /// written are the other two parameters. Block 0 is the head, blocks 1 and 2 are the arms and
902    /// block 3 is the join, which takes nothing and returns.
903    fn both_arms_store(info: MemInfo, flags: [Flags; 2], addresses: bool) -> Func {
904        let mut names = Interner::new();
905        let ints = [Type::PTR, Type::int(32), Type::int(32), Type::PTR];
906        let signature = Signature::new().with_params(&ints);
907        let mut func = Func::new(names.intern("f"), signature);
908        let head = func.create_block();
909        let address = func.append_param(head, Type::PTR);
910        let written =
911            [func.append_param(head, Type::int(32)), func.append_param(head, Type::int(32))];
912        let elsewhere = func.append_param(head, Type::PTR);
913        let arms = [func.create_block(), func.create_block()];
914        let join = func.create_block();
915
916        let mut build = Builder::new(&mut func, head);
917        let zero = build.iconst(Type::int(32), 0);
918        let test = build.icmp(IntPred::Slt, written[0], zero);
919        build.br_if(test, arms[0], &[], arms[1], &[]);
920        for (index, arm) in arms.iter().enumerate() {
921            let mut build = Builder::new(&mut func, *arm);
922            let where_to = if addresses && index == 1 { elsewhere } else { address };
923            build.store(written[index], where_to, info, flags[index]);
924            build.jump(join, &[]);
925        }
926        let mut build = Builder::new(&mut func, join);
927        build.ret(&[]);
928        func
929    }
930
931    /// `x < y ? a : b`, as a diamond whose two arms are empty.
932    ///
933    /// Block 0 is the head and takes the two values it compares as function parameters, blocks 1
934    /// and 2 are the arms and carry one of two constants, and block 3 is the join and returns what
935    /// it was given.
936    fn empty_arms() -> Func {
937        let mut names = Interner::new();
938        let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
939        let mut func = Func::new(names.intern("f"), signature);
940        let head = func.create_block();
941        let left = func.append_param(head, Type::int(32));
942        let right = func.append_param(head, Type::int(32));
943        let arms = [func.create_block(), func.create_block()];
944        let join = func.create_block();
945        let param = func.append_param(join, Type::int(32));
946
947        let mut build = Builder::new(&mut func, head);
948        let test = build.icmp(IntPred::Slt, left, right);
949        build.br_if(test, arms[0], &[], arms[1], &[]);
950        for (arm, value) in arms.iter().zip([1, 2]) {
951            let mut build = Builder::new(&mut func, *arm);
952            let it = build.iconst(Type::int(32), value);
953            build.jump(join, &[it]);
954        }
955        let mut build = Builder::new(&mut func, join);
956        build.ret(&[param]);
957        func
958    }
959
960    #[test]
961    fn a_branch_that_is_already_decided_is_left_for_simplify_cfg() {
962        // What `if (1)` looks like by the time it gets here. Converting would build a select on a
963        // constant and keep the arm that cannot run, and the pass that would fold it does not
964        // exist, so the answer is to leave the branch alone and let the arm be deleted whole.
965        let mut names = Interner::new();
966        let mut func = Func::new(names.intern("f"), Signature::new());
967        let head = func.create_block();
968        let arms = [func.create_block(), func.create_block()];
969        let join = func.create_block();
970        let param = func.append_param(join, Type::int(32));
971
972        let mut build = Builder::new(&mut func, head);
973        // What `if (1)` reaches this pass as. Not a constant, a comparison of two constants, since
974        // `fold` will not turn an `icmp` into an `i1` that nothing lowers.
975        let one = build.iconst(Type::int(32), 1);
976        let zero = build.iconst(Type::int(32), 0);
977        let test = build.icmp(IntPred::Ne, one, zero);
978        build.br_if(test, arms[0], &[], arms[1], &[]);
979        for (arm, value) in arms.iter().zip([1, 2]) {
980            let mut build = Builder::new(&mut func, *arm);
981            let it = build.iconst(Type::int(32), value);
982            build.jump(join, &[it]);
983        }
984        let mut build = Builder::new(&mut func, join);
985        build.ret(&[param]);
986
987        let stats = phiopt(&mut func);
988        assert_eq!(stats.count(Kind::Missed, super::CONDITION_IS_DECIDED), 1);
989        assert_eq!(blocks(&func), vec![0, 1, 2, 3]);
990    }
991
992    #[test]
993    fn a_diamond_whose_arms_are_empty_becomes_a_select() {
994        let mut func = empty_arms();
995        let stats = phiopt(&mut func);
996        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
997        // The two constants moved up with the arms, and the select is what the branch was.
998        assert_eq!(
999            opcodes(&func, 0),
1000            vec![Opcode::ICmp, Opcode::IConst, Opcode::IConst, Opcode::Select, Opcode::Jump]
1001        );
1002        assert_eq!(goes_to(&func, 0), vec![3]);
1003        assert_eq!(blocks(&func), vec![0, 3]);
1004    }
1005
1006    #[test]
1007    fn the_side_the_condition_holds_on_is_the_side_the_select_takes_first() {
1008        let mut func = empty_arms();
1009        phiopt(&mut func);
1010        let select = func
1011            .insts(Block::from_usize(0))
1012            .find(|&inst| func[inst].opcode == Opcode::Select)
1013            .expect("the select the pass just built");
1014        let args = func[func[select].args].to_vec();
1015        let one = crate::fold::constant(&func, args[1]).expect("the true arm carried a constant");
1016        let two = crate::fold::constant(&func, args[2]).expect("the false arm carried a constant");
1017        assert_eq!(one.0.unsigned(), 1, "the arm the branch named first");
1018        assert_eq!(two.0.unsigned(), 2, "the arm the branch named second");
1019    }
1020
1021    /// A triangle: one side goes straight to the join carrying what it already had.
1022    #[test]
1023    fn a_triangle_whose_empty_side_goes_straight_to_the_join_is_converted() {
1024        let mut names = Interner::new();
1025        let signature = Signature::new().with_params(&[Type::int(32)]);
1026        let mut func = Func::new(names.intern("f"), signature);
1027        let head = func.create_block();
1028        let outside = func.append_param(head, Type::int(32));
1029        let arm = func.create_block();
1030        let join = func.create_block();
1031        let param = func.append_param(join, Type::int(32));
1032
1033        let mut build = Builder::new(&mut func, head);
1034        let zero = build.iconst(Type::int(32), 0);
1035        let test = build.icmp(IntPred::Slt, outside, zero);
1036        build.br_if(test, arm, &[], join, &[outside]);
1037        let mut build = Builder::new(&mut func, arm);
1038        let it = build.iconst(Type::int(32), 0);
1039        build.jump(join, &[it]);
1040        let mut build = Builder::new(&mut func, join);
1041        build.ret(&[param]);
1042
1043        let stats = phiopt(&mut func);
1044        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1045        assert_eq!(blocks(&func), vec![0, 2]);
1046        assert_eq!(goes_to(&func, 0), vec![2]);
1047        assert_eq!(opcodes(&func, 0).last(), Some(&Opcode::Jump));
1048    }
1049
1050    #[test]
1051    fn a_parameter_both_sides_agree_about_needs_no_select() {
1052        let mut names = Interner::new();
1053        let signature = Signature::new().with_params(&[Type::int(32)]);
1054        let mut func = Func::new(names.intern("f"), signature);
1055        let head = func.create_block();
1056        let outside = func.append_param(head, Type::int(32));
1057        let arms = [func.create_block(), func.create_block()];
1058        let join = func.create_block();
1059        let param = func.append_param(join, Type::int(32));
1060
1061        let mut build = Builder::new(&mut func, head);
1062        let zero = build.iconst(Type::int(32), 0);
1063        let test = build.icmp(IntPred::Slt, outside, zero);
1064        build.br_if(test, arms[0], &[], arms[1], &[]);
1065        for arm in arms {
1066            let mut build = Builder::new(&mut func, arm);
1067            build.jump(join, &[outside]);
1068        }
1069        let mut build = Builder::new(&mut func, join);
1070        build.ret(&[param]);
1071
1072        let stats = phiopt(&mut func);
1073        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1074        assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried the same value");
1075        assert_eq!(carries(&func, 0), vec![outside]);
1076    }
1077
1078    #[test]
1079    fn two_sides_carrying_the_same_number_need_no_select_either() {
1080        // `x ? 7 : 7`, which the corpus has eight of. The two sevens are two values, because
1081        // nothing has hash consed them into one, so asking only whether the values are equal
1082        // builds a select between two sevens and pays a compare and a conditional move for it.
1083        let mut names = Interner::new();
1084        let signature = Signature::new().with_params(&[Type::int(32)]);
1085        let mut func = Func::new(names.intern("f"), signature);
1086        let head = func.create_block();
1087        let outside = func.append_param(head, Type::int(32));
1088        let arms = [func.create_block(), func.create_block()];
1089        let join = func.create_block();
1090        let param = func.append_param(join, Type::int(32));
1091
1092        let mut build = Builder::new(&mut func, head);
1093        let zero = build.iconst(Type::int(32), 0);
1094        let test = build.icmp(IntPred::Slt, outside, zero);
1095        build.br_if(test, arms[0], &[], arms[1], &[]);
1096        for arm in arms {
1097            let mut build = Builder::new(&mut func, arm);
1098            let seven = build.iconst(Type::int(32), 7);
1099            build.jump(join, &[seven]);
1100        }
1101        let mut build = Builder::new(&mut func, join);
1102        build.ret(&[param]);
1103
1104        let stats = phiopt(&mut func);
1105        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1106        assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried a seven");
1107    }
1108
1109    #[test]
1110    fn two_sides_carrying_different_numbers_still_get_a_select() {
1111        let mut func = empty_arms();
1112        let stats = phiopt(&mut func);
1113        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1114        assert!(opcodes(&func, 0).contains(&Opcode::Select), "one and two are not the same number");
1115    }
1116
1117    #[test]
1118    fn a_store_both_arms_make_to_one_place_is_made_once_below_the_branch() {
1119        let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1120        let stats = phiopt(&mut func);
1121        assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1122        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1123        // One store, below the select that chooses what it writes, and no branch above either.
1124        assert_eq!(
1125            opcodes(&func, 0),
1126            vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Store, Opcode::Jump]
1127        );
1128        assert_eq!(blocks(&func), vec![0, 3]);
1129        assert_eq!(goes_to(&func, 0), vec![3]);
1130    }
1131
1132    #[test]
1133    fn the_one_store_writes_what_the_side_the_condition_holds_on_was_writing() {
1134        let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1135        phiopt(&mut func);
1136        let head = Block::from_usize(0);
1137        let select = func
1138            .insts(head)
1139            .find(|&inst| func[inst].opcode == Opcode::Select)
1140            .expect("the select the pass just built");
1141        let store = func
1142            .insts(head)
1143            .find(|&inst| func[inst].opcode == Opcode::Store)
1144            .expect("the one store that is left");
1145        let chosen = func[func[select].args].to_vec();
1146        let written = func[func[store].args].to_vec();
1147        // The head's parameters in order: the address, then what each side writes.
1148        let params = func[head].params.to_vec();
1149        assert_eq!(chosen[1], params[1], "the arm the branch named first");
1150        assert_eq!(chosen[2], params[2], "the arm the branch named second");
1151        assert_eq!(written[0], func[select].first_result.expect("a select produces one value"));
1152        assert_eq!(written[1], params[0], "the address both arms named");
1153    }
1154
1155    /// Both arms writing the same value needs no select, only the one store.
1156    #[test]
1157    fn two_arms_that_write_the_same_thing_get_a_store_and_no_select() {
1158        let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1159        // Point the second arm's store at the first arm's value, which is what the front end
1160        // produces when both branches of a conditional assign the same thing.
1161        let head = Block::from_usize(0);
1162        let params = func[head].params.to_vec();
1163        let store = func
1164            .insts(Block::from_usize(2))
1165            .find(|&inst| func[inst].opcode == Opcode::Store)
1166            .expect("the second arm's store");
1167        let args = func.push_values(&[params[1], params[0]]);
1168        func[store].args = args;
1169
1170        let stats = phiopt(&mut func);
1171        assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1172        assert_eq!(
1173            opcodes(&func, 0),
1174            vec![Opcode::IConst, Opcode::ICmp, Opcode::Store, Opcode::Jump]
1175        );
1176    }
1177
1178    /// Two stores to two different places is two writes, and doing both is writing one of them twice.
1179    #[test]
1180    fn two_arms_that_store_to_different_addresses_keep_their_branch() {
1181        let mut func = both_arms_store(plain(), [Flags::NONE; 2], true);
1182        let stats = phiopt(&mut func);
1183        assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1184        assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1185        assert_eq!(goes_to(&func, 0), vec![1, 2]);
1186    }
1187
1188    #[test]
1189    fn a_volatile_store_keeps_its_branch_even_when_both_arms_make_it() {
1190        let mut func = both_arms_store(plain(), [Flags::VOLATILE; 2], false);
1191        let stats = phiopt(&mut func);
1192        assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1193        assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1194        assert_eq!(goes_to(&func, 0), vec![1, 2]);
1195    }
1196
1197    #[test]
1198    fn an_atomic_store_keeps_its_branch_even_when_both_arms_make_it() {
1199        let mut func = both_arms_store(
1200            MemInfo { order: MemOrder::SeqCst, ..plain() },
1201            [Flags::NONE; 2],
1202            false,
1203        );
1204        let stats = phiopt(&mut func);
1205        assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1206        assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1207    }
1208
1209    /// Two stores told different things about the access have no one answer to carry downward.
1210    #[test]
1211    fn two_stores_that_disagree_about_the_access_keep_their_branch() {
1212        let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1213        let store = func
1214            .insts(Block::from_usize(2))
1215            .find(|&inst| func[inst].opcode == Opcode::Store)
1216            .expect("the second arm's store");
1217        let mem = func.add_mem(MemInfo { align: 1, ..plain() });
1218        func[store].extra = rucc_ir::Extra::Mem(mem);
1219
1220        let stats = phiopt(&mut func);
1221        assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1222        assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1223    }
1224
1225    /// The store comes off the work count, because one of the two arms was always going to make it.
1226    ///
1227    /// Each arm here holds the store and as much other work as the rule allows, so counting the
1228    /// store as work would put both arms one over the limit and the branch would stay.
1229    #[test]
1230    fn a_store_each_way_does_not_count_against_how_long_the_arms_may_be() {
1231        let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1232        let params = func[Block::from_usize(0)].params.to_vec();
1233        for arm in [1, 2] {
1234            let block = Block::from_usize(arm);
1235            let term = func.terminator(block).expect("an arm ends in its jump");
1236            func.remove_inst(term);
1237            let mut build = Builder::new(&mut func, block);
1238            let mut value = params[1];
1239            for _ in 0..rucc_cost::heuristics::PHIOPT_ARM_INSTRUCTIONS {
1240                value = build.binary(Opcode::Add, value, params[2], Flags::NONE);
1241            }
1242            func.append_inst(block, term);
1243        }
1244
1245        let stats = phiopt(&mut func);
1246        assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
1247        assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1248    }
1249
1250    /// A store one side makes and the other does not, which is the transformation with no proof.
1251    #[test]
1252    fn an_arm_that_stores_where_the_other_does_not_keeps_its_branch() {
1253        let mut names = Interner::new();
1254        let signature = Signature::new().with_params(&[Type::int(32)]);
1255        let mut func = Func::new(names.intern("f"), signature);
1256        let head = func.create_block();
1257        let outside = func.append_param(head, Type::int(32));
1258        let arms = [func.create_block(), func.create_block()];
1259        let join = func.create_block();
1260        let param = func.append_param(join, Type::int(32));
1261
1262        let mut build = Builder::new(&mut func, head);
1263        let zero = build.iconst(Type::int(32), 0);
1264        let test = build.icmp(IntPred::Slt, outside, zero);
1265        build.br_if(test, arms[0], &[], arms[1], &[]);
1266        let mut build = Builder::new(&mut func, arms[0]);
1267        store_something(&mut build);
1268        let it = build.iconst(Type::int(32), 1);
1269        build.jump(join, &[it]);
1270        let mut build = Builder::new(&mut func, arms[1]);
1271        let it = build.iconst(Type::int(32), 2);
1272        build.jump(join, &[it]);
1273        let mut build = Builder::new(&mut func, join);
1274        build.ret(&[param]);
1275
1276        let stats = phiopt(&mut func);
1277        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1278        assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
1279        assert_eq!(goes_to(&func, 0), vec![1, 2]);
1280    }
1281
1282    /// A load, which is the effect that is not a store and gets the general answer.
1283    #[test]
1284    fn an_arm_that_does_something_else_keeps_its_branch() {
1285        let mut names = Interner::new();
1286        let signature = Signature::new().with_params(&[Type::int(32)]);
1287        let mut func = Func::new(names.intern("f"), signature);
1288        let head = func.create_block();
1289        let outside = func.append_param(head, Type::int(32));
1290        let arms = [func.create_block(), func.create_block()];
1291        let join = func.create_block();
1292        let param = func.append_param(join, Type::int(32));
1293
1294        let mut build = Builder::new(&mut func, head);
1295        let zero = build.iconst(Type::int(32), 0);
1296        let test = build.icmp(IntPred::Slt, outside, zero);
1297        build.br_if(test, arms[0], &[], arms[1], &[]);
1298        let mut build = Builder::new(&mut func, arms[0]);
1299        let address = build.iconst(Type::int(64), 16);
1300        let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
1301        let it = build.load(Type::int(32), address, plain(), Flags::NONE);
1302        build.jump(join, &[it]);
1303        let mut build = Builder::new(&mut func, arms[1]);
1304        let it = build.iconst(Type::int(32), 2);
1305        build.jump(join, &[it]);
1306        let mut build = Builder::new(&mut func, join);
1307        build.ret(&[param]);
1308
1309        let stats = phiopt(&mut func);
1310        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1311        assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
1312        assert_eq!(goes_to(&func, 0), vec![1, 2]);
1313    }
1314
1315    /// A division whose divisor is not known cannot be moved onto the path that skipped it.
1316    #[test]
1317    fn an_arm_that_divides_by_something_unknown_keeps_its_branch() {
1318        let mut names = Interner::new();
1319        let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
1320        let mut func = Func::new(names.intern("f"), signature);
1321        let head = func.create_block();
1322        let left = func.append_param(head, Type::int(32));
1323        let right = func.append_param(head, Type::int(32));
1324        let arms = [func.create_block(), func.create_block()];
1325        let join = func.create_block();
1326        let param = func.append_param(join, Type::int(32));
1327
1328        let mut build = Builder::new(&mut func, head);
1329        let zero = build.iconst(Type::int(32), 0);
1330        let test = build.icmp(IntPred::Ne, right, zero);
1331        build.br_if(test, arms[0], &[], arms[1], &[]);
1332        let mut build = Builder::new(&mut func, arms[0]);
1333        let it = build.binary(Opcode::SDiv, left, right, Flags::NONE);
1334        build.jump(join, &[it]);
1335        let mut build = Builder::new(&mut func, arms[1]);
1336        let it = build.iconst(Type::int(32), 0);
1337        build.jump(join, &[it]);
1338        let mut build = Builder::new(&mut func, join);
1339        build.ret(&[param]);
1340
1341        let stats = phiopt(&mut func);
1342        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1343        assert_eq!(stats.count(Kind::Missed, super::ARM_MAY_TRAP), 1);
1344        assert_eq!(goes_to(&func, 0), vec![1, 2]);
1345    }
1346
1347    #[test]
1348    fn a_division_by_a_constant_that_is_not_zero_or_minus_one_is_moved() {
1349        let mut names = Interner::new();
1350        let signature = Signature::new().with_params(&[Type::int(32)]);
1351        let mut func = Func::new(names.intern("f"), signature);
1352        let head = func.create_block();
1353        let outside = func.append_param(head, Type::int(32));
1354        let arms = [func.create_block(), func.create_block()];
1355        let join = func.create_block();
1356        let param = func.append_param(join, Type::int(32));
1357
1358        let mut build = Builder::new(&mut func, head);
1359        let zero = build.iconst(Type::int(32), 0);
1360        let test = build.icmp(IntPred::Slt, outside, zero);
1361        build.br_if(test, arms[0], &[], arms[1], &[]);
1362        let mut build = Builder::new(&mut func, arms[0]);
1363        let three = build.iconst(Type::int(32), 3);
1364        let it = build.binary(Opcode::SDiv, outside, three, Flags::NONE);
1365        build.jump(join, &[it]);
1366        let mut build = Builder::new(&mut func, arms[1]);
1367        let it = build.iconst(Type::int(32), 0);
1368        build.jump(join, &[it]);
1369        let mut build = Builder::new(&mut func, join);
1370        build.ret(&[param]);
1371
1372        let stats = phiopt(&mut func);
1373        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1374        assert!(opcodes(&func, 0).contains(&Opcode::SDiv));
1375    }
1376
1377    /// Nothing chooses between two pointers, so the shape is matched and then left alone.
1378    #[test]
1379    fn a_value_no_select_is_lowered_for_keeps_its_branch() {
1380        let mut names = Interner::new();
1381        let signature = Signature::new().with_params(&[Type::int(32)]);
1382        let mut func = Func::new(names.intern("f"), signature);
1383        let head = func.create_block();
1384        let outside = func.append_param(head, Type::int(32));
1385        let arms = [func.create_block(), func.create_block()];
1386        let join = func.create_block();
1387        func.append_param(join, Type::PTR);
1388
1389        let mut build = Builder::new(&mut func, head);
1390        let zero = build.iconst(Type::int(32), 0);
1391        let test = build.icmp(IntPred::Slt, outside, zero);
1392        build.br_if(test, arms[0], &[], arms[1], &[]);
1393        for (arm, value) in arms.iter().zip([16, 32]) {
1394            let mut build = Builder::new(&mut func, *arm);
1395            let it = build.iconst(Type::int(64), value);
1396            let it = build.unary(Opcode::IntToPtr, it, Type::PTR);
1397            build.jump(join, &[it]);
1398        }
1399        let mut build = Builder::new(&mut func, join);
1400        build.ret(&[]);
1401
1402        let stats = phiopt(&mut func);
1403        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1404        assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
1405    }
1406
1407    #[test]
1408    fn arms_with_more_work_in_them_than_the_budget_keep_their_branch() {
1409        let mut names = Interner::new();
1410        let signature = Signature::new().with_params(&[Type::int(32)]);
1411        let mut func = Func::new(names.intern("f"), signature);
1412        let head = func.create_block();
1413        let outside = func.append_param(head, Type::int(32));
1414        let arms = [func.create_block(), func.create_block()];
1415        let join = func.create_block();
1416        let param = func.append_param(join, Type::int(32));
1417
1418        let mut build = Builder::new(&mut func, head);
1419        let zero = build.iconst(Type::int(32), 0);
1420        let test = build.icmp(IntPred::Slt, outside, zero);
1421        build.br_if(test, arms[0], &[], arms[1], &[]);
1422        let mut build = Builder::new(&mut func, arms[0]);
1423        // Four instructions, which is past the budget however cheap each of them is.
1424        let mut it = outside;
1425        for _ in 0..4 {
1426            it = build.binary(Opcode::Add, it, outside, Flags::NONE);
1427        }
1428        build.jump(join, &[it]);
1429        let mut build = Builder::new(&mut func, arms[1]);
1430        let it = build.iconst(Type::int(32), 0);
1431        build.jump(join, &[it]);
1432        let mut build = Builder::new(&mut func, join);
1433        build.ret(&[param]);
1434
1435        let stats = phiopt(&mut func);
1436        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1437        assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
1438    }
1439
1440    /// The margin, at the two ends of it and just outside.
1441    ///
1442    /// A pass level test of the refusal it guards is not written, and the module doc says why: a
1443    /// diamond is the one shape none of document 11's one sided predictors can key on, so every
1444    /// branch this pass matches comes back even until `__builtin_expect` is wired through the
1445    /// front end. The arithmetic is what there is to check today.
1446    #[test]
1447    fn the_margin_is_a_quarter_in_from_each_end() {
1448        let guessed = |percent: u32| Probability::percent(percent, Quality::Guessed);
1449        assert!(super::unpredictable(Probability::even()));
1450        assert!(super::unpredictable(guessed(25)));
1451        assert!(super::unpredictable(guessed(75)));
1452        assert!(!super::unpredictable(guessed(24)));
1453        assert!(!super::unpredictable(guessed(76)));
1454        assert!(!super::unpredictable(Probability::always()));
1455        assert!(!super::unpredictable(Probability::never()));
1456    }
1457
1458    #[test]
1459    fn an_arm_that_two_edges_reach_is_not_an_arm() {
1460        let mut names = Interner::new();
1461        let signature = Signature::new().with_params(&[Type::int(32)]);
1462        let mut func = Func::new(names.intern("f"), signature);
1463        let head = func.create_block();
1464        let outside = func.append_param(head, Type::int(32));
1465        let above = func.create_block();
1466        let arms = [func.create_block(), func.create_block()];
1467        let join = func.create_block();
1468        let param = func.append_param(join, Type::int(32));
1469
1470        // The entry reaches the first arm as well as the head does, so moving the arm's work into
1471        // the head would leave the entry's path without it.
1472        let mut build = Builder::new(&mut func, head);
1473        let zero = build.iconst(Type::int(32), 0);
1474        let first = build.icmp(IntPred::Slt, outside, zero);
1475        build.br_if(first, above, &[], arms[0], &[]);
1476        let mut build = Builder::new(&mut func, above);
1477        let one = build.iconst(Type::int(32), 1);
1478        let second = build.icmp(IntPred::Slt, outside, one);
1479        build.br_if(second, arms[0], &[], arms[1], &[]);
1480        for (arm, value) in arms.iter().zip([1, 2]) {
1481            let mut build = Builder::new(&mut func, *arm);
1482            let it = build.iconst(Type::int(32), value);
1483            build.jump(join, &[it]);
1484        }
1485        let mut build = Builder::new(&mut func, join);
1486        build.ret(&[param]);
1487
1488        let stats = phiopt(&mut func);
1489        // Neither branch is a diamond. The head's first side goes to a block that is not the join
1490        // and is not an arm either, since two edges reach it, and the second branch's first side
1491        // is the same block for the same reason.
1492        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1493        assert_eq!(goes_to(&func, 0), vec![1, 2]);
1494        assert_eq!(goes_to(&func, 1), vec![2, 3]);
1495    }
1496
1497    #[test]
1498    fn fuel_stops_the_conversion_where_it_stands() {
1499        let mut func = empty_arms();
1500        let mut fuel = Fuel::of(0);
1501        let stats = PhiOpt.run(&mut func, &mut Analyses::new(), &mut fuel);
1502        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1503        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1504        assert_eq!(goes_to(&func, 0), vec![1, 2]);
1505    }
1506
1507    /// `x < y ? f(a, k) : f(b, k)`, as a diamond whose two arms do the same thing to different
1508    /// operands.
1509    ///
1510    /// Block 0 is the head, taking the two values it compares and the two operands and working out
1511    /// the operand both arms share. Blocks 1 and 2 are the arms, each applying every one of
1512    /// `steps` to its own operand and that shared value, and block 3 is the join, taking one
1513    /// parameter for each of them.
1514    fn same_operation(steps: &[Opcode]) -> Func {
1515        let mut names = Interner::new();
1516        let int = Type::int(32);
1517        let signature = Signature::new().with_params(&[int, int, int, int]);
1518        let mut func = Func::new(names.intern("f"), signature);
1519        let head = func.create_block();
1520        let left = func.append_param(head, int);
1521        let right = func.append_param(head, int);
1522        let operands = [func.append_param(head, int), func.append_param(head, int)];
1523        let arms = [func.create_block(), func.create_block()];
1524        let join = func.create_block();
1525        let params: Vec<Value> = steps.iter().map(|_| func.append_param(join, int)).collect();
1526
1527        let mut build = Builder::new(&mut func, head);
1528        // In the head rather than in each arm, so that the two sides share this operand as one
1529        // value. Two arms that each work out their own three are two operations apart, not one.
1530        let shared = build.iconst(int, 3);
1531        let test = build.icmp(IntPred::Slt, left, right);
1532        build.br_if(test, arms[0], &[], arms[1], &[]);
1533        for (&arm, operand) in arms.iter().zip(operands) {
1534            let mut build = Builder::new(&mut func, arm);
1535            let carried: Vec<Value> = steps
1536                .iter()
1537                .map(|&opcode| build.binary(opcode, operand, shared, Flags::default()))
1538                .collect();
1539            build.jump(join, &carried);
1540        }
1541        let mut build = Builder::new(&mut func, join);
1542        build.ret(&params);
1543        func
1544    }
1545
1546    /// The transformation. Two adds become one add of a select, rather than one select of two adds.
1547    #[test]
1548    fn an_operation_both_arms_did_is_done_once_below_the_branch() {
1549        let mut func = same_operation(&[Opcode::Add]);
1550        let stats = phiopt(&mut func);
1551        assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
1552        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1553        assert_eq!(
1554            opcodes(&func, 0),
1555            vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Add, Opcode::Jump],
1556            "the select chooses the operand and the add happens once"
1557        );
1558        assert_eq!(blocks(&func), vec![0, 3]);
1559    }
1560
1561    /// The select goes under the operation, so what it chooses between is the operands and not the
1562    /// answers. Getting that the wrong way round would be a select of two adds that happens to have
1563    /// the right opcodes in it.
1564    #[test]
1565    fn the_select_chooses_the_operands_and_not_the_answers() {
1566        let mut func = same_operation(&[Opcode::Add]);
1567        phiopt(&mut func);
1568        let head = Block::from_usize(0);
1569        let select = func
1570            .insts(head)
1571            .find(|&inst| func[inst].opcode == Opcode::Select)
1572            .expect("the select the pass just built");
1573        let add = func
1574            .insts(head)
1575            .find(|&inst| func[inst].opcode == Opcode::Add)
1576            .expect("the add the pass just wrote");
1577        let chosen = func[func[select].args].to_vec();
1578        let params = func[head].params.to_vec();
1579        assert_eq!(&chosen[1..], &params[2..], "the two operands the arms differed in");
1580        let added = func[func[add].args].to_vec();
1581        assert_eq!(added[0], func[select].first_result.expect("a select has a result"));
1582        assert_eq!(carries(&func, 0), vec![func[add].first_result.expect("an add has a result")]);
1583    }
1584
1585    /// Nothing is speculated by an operation both arms were doing, so the length rule is about what
1586    /// is left after the factoring rather than about what the arms arrived holding. Three
1587    /// instructions an arm is over the limit, and three instructions that all factor is none.
1588    #[test]
1589    fn arms_that_factor_away_entirely_are_not_too_long() {
1590        let steps = [Opcode::Add, Opcode::Sub, Opcode::Mul];
1591        let mut func = same_operation(&steps);
1592        let stats = phiopt(&mut func);
1593        assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
1594        assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 3);
1595        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1596        let written = opcodes(&func, 0);
1597        assert_eq!(written.iter().filter(|&&op| op == Opcode::Select).count(), 3);
1598        for step in steps {
1599            assert_eq!(written.iter().filter(|&&op| op == step).count(), 1, "{step:?} once");
1600        }
1601    }
1602
1603    /// Both arms doing the same thing to the same operands is a common subexpression nothing has
1604    /// numbered, and one copy of it serves both sides with no select at all.
1605    #[test]
1606    fn arms_that_agree_in_every_operand_need_no_select() {
1607        let mut names = Interner::new();
1608        let int = Type::int(32);
1609        let signature = Signature::new().with_params(&[int, int, int]);
1610        let mut func = Func::new(names.intern("f"), signature);
1611        let head = func.create_block();
1612        let left = func.append_param(head, int);
1613        let right = func.append_param(head, int);
1614        let operand = func.append_param(head, int);
1615        let arms = [func.create_block(), func.create_block()];
1616        let join = func.create_block();
1617        let param = func.append_param(join, int);
1618
1619        let mut build = Builder::new(&mut func, head);
1620        let shared = build.iconst(int, 3);
1621        let test = build.icmp(IntPred::Slt, left, right);
1622        build.br_if(test, arms[0], &[], arms[1], &[]);
1623        for &arm in &arms {
1624            let mut build = Builder::new(&mut func, arm);
1625            let it = build.binary(Opcode::Add, operand, shared, Flags::default());
1626            build.jump(join, &[it]);
1627        }
1628        let mut build = Builder::new(&mut func, join);
1629        build.ret(&[param]);
1630
1631        let stats = phiopt(&mut func);
1632        assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
1633        assert_eq!(
1634            opcodes(&func, 0),
1635            vec![Opcode::IConst, Opcode::ICmp, Opcode::Add, Opcode::Jump],
1636            "one add and nothing to choose between"
1637        );
1638    }
1639
1640    /// Two different operations are two operations, and the pass falls back to hoisting both and
1641    /// selecting between what they produced.
1642    #[test]
1643    fn arms_that_do_different_things_are_not_factored() {
1644        let mut names = Interner::new();
1645        let int = Type::int(32);
1646        let signature = Signature::new().with_params(&[int, int, int, int]);
1647        let mut func = Func::new(names.intern("f"), signature);
1648        let head = func.create_block();
1649        let left = func.append_param(head, int);
1650        let right = func.append_param(head, int);
1651        let operands = [func.append_param(head, int), func.append_param(head, int)];
1652        let arms = [func.create_block(), func.create_block()];
1653        let join = func.create_block();
1654        let param = func.append_param(join, int);
1655
1656        let mut build = Builder::new(&mut func, head);
1657        let shared = build.iconst(int, 3);
1658        let test = build.icmp(IntPred::Slt, left, right);
1659        build.br_if(test, arms[0], &[], arms[1], &[]);
1660        for ((&arm, operand), opcode) in arms.iter().zip(operands).zip([Opcode::Add, Opcode::Sub]) {
1661            let mut build = Builder::new(&mut func, arm);
1662            let it = build.binary(opcode, operand, shared, Flags::default());
1663            build.jump(join, &[it]);
1664        }
1665        let mut build = Builder::new(&mut func, join);
1666        build.ret(&[param]);
1667
1668        let stats = phiopt(&mut func);
1669        assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1670        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1671        assert_eq!(
1672            opcodes(&func, 0),
1673            vec![
1674                Opcode::IConst,
1675                Opcode::ICmp,
1676                Opcode::Add,
1677                Opcode::Sub,
1678                Opcode::Select,
1679                Opcode::Jump
1680            ],
1681            "both operations hoisted and a select between their answers"
1682        );
1683    }
1684
1685    /// Two operand positions apart needs two selects and one operation, which is what one select
1686    /// and two operations already cost, so there is nothing to win and it is left alone.
1687    #[test]
1688    fn arms_that_differ_in_two_operands_are_not_factored() {
1689        let mut names = Interner::new();
1690        let int = Type::int(32);
1691        let signature = Signature::new().with_params(&[int, int, int, int, int, int]);
1692        let mut func = Func::new(names.intern("f"), signature);
1693        let head = func.create_block();
1694        let left = func.append_param(head, int);
1695        let right = func.append_param(head, int);
1696        let first = [func.append_param(head, int), func.append_param(head, int)];
1697        let second = [func.append_param(head, int), func.append_param(head, int)];
1698        let arms = [func.create_block(), func.create_block()];
1699        let join = func.create_block();
1700        let param = func.append_param(join, int);
1701
1702        let mut build = Builder::new(&mut func, head);
1703        let test = build.icmp(IntPred::Slt, left, right);
1704        build.br_if(test, arms[0], &[], arms[1], &[]);
1705        for ((&arm, one), two) in arms.iter().zip(first).zip(second) {
1706            let mut build = Builder::new(&mut func, arm);
1707            let it = build.binary(Opcode::Add, one, two, Flags::default());
1708            build.jump(join, &[it]);
1709        }
1710        let mut build = Builder::new(&mut func, join);
1711        build.ret(&[param]);
1712
1713        let stats = phiopt(&mut func);
1714        assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1715        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1716        assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
1717    }
1718
1719    /// The one copy is written after the arms have gone, so an operation something else in the arm
1720    /// reads cannot be one of the two it replaces. Here each arm hands its answer to the join
1721    /// twice, which is two readers and not one.
1722    #[test]
1723    fn an_operation_read_more_than_once_is_not_factored() {
1724        let mut names = Interner::new();
1725        let int = Type::int(32);
1726        let signature = Signature::new().with_params(&[int, int, int, int]);
1727        let mut func = Func::new(names.intern("f"), signature);
1728        let head = func.create_block();
1729        let left = func.append_param(head, int);
1730        let right = func.append_param(head, int);
1731        let operands = [func.append_param(head, int), func.append_param(head, int)];
1732        let arms = [func.create_block(), func.create_block()];
1733        let join = func.create_block();
1734        let params = [func.append_param(join, int), func.append_param(join, int)];
1735
1736        let mut build = Builder::new(&mut func, head);
1737        let shared = build.iconst(int, 3);
1738        let test = build.icmp(IntPred::Slt, left, right);
1739        build.br_if(test, arms[0], &[], arms[1], &[]);
1740        for (&arm, operand) in arms.iter().zip(operands) {
1741            let mut build = Builder::new(&mut func, arm);
1742            let it = build.binary(Opcode::Add, operand, shared, Flags::default());
1743            build.jump(join, &[it, it]);
1744        }
1745        let mut build = Builder::new(&mut func, join);
1746        build.ret(&params);
1747
1748        let stats = phiopt(&mut func);
1749        assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1750        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1751        assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
1752    }
1753
1754    /// A triangle has a block on one side only, so there is no second operation to pair the first
1755    /// one with and nothing to factor.
1756    #[test]
1757    fn a_triangle_factors_nothing() {
1758        let mut names = Interner::new();
1759        let int = Type::int(32);
1760        let signature = Signature::new().with_params(&[int, int, int]);
1761        let mut func = Func::new(names.intern("f"), signature);
1762        let head = func.create_block();
1763        let left = func.append_param(head, int);
1764        let right = func.append_param(head, int);
1765        let operand = func.append_param(head, int);
1766        let arm = func.create_block();
1767        let join = func.create_block();
1768        let param = func.append_param(join, int);
1769
1770        let mut build = Builder::new(&mut func, head);
1771        let shared = build.iconst(int, 3);
1772        let test = build.icmp(IntPred::Slt, left, right);
1773        build.br_if(test, arm, &[], join, &[operand]);
1774        let mut build = Builder::new(&mut func, arm);
1775        let it = build.binary(Opcode::Add, operand, shared, Flags::default());
1776        build.jump(join, &[it]);
1777        let mut build = Builder::new(&mut func, join);
1778        build.ret(&[param]);
1779
1780        let stats = phiopt(&mut func);
1781        assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1782        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1783    }
1784}