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 of them is
8//! 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//! Two sides disagree about a parameter when they hand the join different values, and also when
31//! they hand it different values that are the same number. The second half is there because the
32//! corpus has eight diamonds whose two arms both work out the same constant, in separate
33//! instructions that nothing has hash consed into one, and the tier six rule `select(c, x, x) -> x`
34//! does not reach them for exactly the same reason: two operands that are not one value do not
35//! match a pattern that writes one name twice. What would reach them is document 12.1's hash
36//! consing or document 16's value numbering, and until one of those exists the cheap question is
37//! worth asking here, where the alternative is a `select` this pass wrote itself between two sevens.
38//!
39//! # Why moving an arm's work into the head is safe
40//!
41//! Because the arm has exactly one predecessor, which is the head. That is checked, and it is the
42//! whole of the argument in both directions.
43//!
44//! Downward: an instruction in the arm reads values that dominate the arm, and the head dominates
45//! the arm too, so every one of them is available where the instruction is going. Upward: nothing
46//! outside the arm can read what the arm defines except by the arm's own jump, since the arm
47//! dominates only itself, and that jump's arguments are exactly what the selects are built out of.
48//! An arm with two predecessors would break both halves at once, which is why the check is on the
49//! predecessor count and not on the shape of the graph around it.
50//!
51//! The loop rules that `spec/optimizer/23-jump-threading.md` needs are not needed here, and the
52//! reason is worth writing down rather than leaving as an absence. No edge is added, so no loop
53//! gains a second way in and no loop can become irreducible. An arm cannot be a loop header, since
54//! a header has a back edge and this arm has one predecessor and it is not itself. An arm can be a
55//! latch, and then the head becomes the latch instead, which keeps the single latch property
56//! document 07.3 wants rather than spoiling it. The one shape that would matter is a join that
57//! only its own arms reach, which is a region unreachable from the entry, and the pass asks
58//! whether the head is reachable before it looks at anything.
59//!
60//! # What it refuses, and every one of them is section 22.6
61//!
62//! An arm that does something. The predicate is [`rucc_ir::Opcode::has_effects`], which is what
63//! dead code elimination deletes an instruction under, so an arm this pass will hoist is an arm
64//! whose instructions could have been deleted outright had nothing read them. A store, a call, a
65//! `volatile` access and a load are all effects by that answer, which closes the first, second and
66//! sixth failures in section 22.6 with one question. The store case is the one worth naming: the
67//! whole of conditional store replacement is section 22.2's fourth transformation and it is not
68//! here, so a diamond that stores is a diamond this pass walks away from.
69//!
70//! An arm that divides. Division is not an effect, because nothing observes it and dead code
71//! elimination is right to delete one, but it traps, and a trap on a path that did not have one is
72//! section 22.6's third failure. The exception is a divisor that is a constant which is neither
73//! zero nor minus one, which cannot trap and is most of the divisions real code contains.
74//!
75//! A value the two sides disagree about whose type has no `select`. The IR names a `select` at
76//! eight, sixteen, thirty two and sixty four bit integers and at nothing else, so producing one of
77//! any other type would build a term the back end has no rule for. That is an invisible gap rather
78//! than a wrong answer, and the producer is the side that has to avoid it.
79//!
80//! A branch that is already decided. Section 22.6 does not list this one and the corpus found it,
81//! on a program whose source says `if (1)`. `simplify-cfg` runs after this pass and turns a decided
82//! branch into a jump, and then the arm that cannot run is deleted whole and its work with it.
83//! Converting first replaces a branch that costs nothing at run time with a select that costs
84//! something, and it keeps alive the work in the arm that never ran, because the fold that would
85//! undo it is `select(1, a, b) -> a` and that rule does not exist yet. The case cost twenty eight
86//! bytes of `.text` and a multiply that could not happen.
87//!
88//! The question is put to `simplify_cfg::taken` rather than answered again here, for the
89//! reason that function's own documentation gives: two answers about when a branch is decided
90//! would be two compilers. It matters in this case rather than being tidiness. The condition on
91//! `if (1)` is not a constant, it is `icmp ne 1, 0`, and `fold` leaves that standing on purpose,
92//! because nothing lowers an `i1` by itself and folding one would turn working code into code that
93//! does not build, which is issue 352. `taken` reads the answer off without leaving anything
94//! standing, since the branch that was the comparison's only reader goes at the same time.
95//!
96//! # The cost rule
97//!
98//! Section 22.2 states it and this implements it without softening it.
99//!
100//! Both arms empty of instructions: convert, always. The select replaces a branch with one
101//! operation that reads two values which already exist, and there is no machine where that is
102//! worse. Nothing about predictability enters, because there is nothing being speculated.
103//!
104//! Arms with work in them: up to [`heuristics::PHIOPT_ARM_INSTRUCTIONS`] instructions each, and
105//! only when the branch probability is within
106//! [`heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT`] of even by document 11's estimate. A branch
107//! the estimate calls one sided keeps its branch, because if the estimate is right the branch is
108//! free and the arm is not.
109//!
110//! The estimate is usually a guess and the guess is often wrong, which section 22.6 lists as the
111//! failure with no defence. Note where that leaves an unpredicted branch: document 11 answers even
112//! and says it is guessing, even is inside the margin, so a branch nothing is known about is
113//! treated as unpredictable and converted. That is the aggressive reading and it is deliberate,
114//! since the alternative is a pass that fires on almost nothing and measures nothing.
115//!
116//! It is also, today, the only reading, and that is worth saying rather than leaving to be
117//! discovered. Every static predictor in document 11 that gives a one sided answer keys on
118//! something one arm of the branch does and the other does not: one arm never comes back, one arm
119//! calls something cold, one arm leaves the loop, one arm returns a negative number. A diamond has
120//! neither of those, because both of its arms fall through to the same block, so the predictors
121//! that could refuse a conversion here are exactly the ones a diamond cannot trip. What is left is
122//! the branch condition itself, which is `__builtin_expect` at ninety percent and the pointer
123//! heuristic at seventy, and only the first of those is outside the margin. `__builtin_expect` is
124//! dropped in the front end today, so until it is wired the probability half of the rule refuses
125//! nothing at all. The check is here rather than deferred because leaving it out would mean the
126//! measurement never showed that, and because the day the hint is wired is the day it starts
127//! mattering.
128//!
129//! # Which level, and how many times
130//!
131//! Every level that optimizes, which is section 22.2's `-O1` and above.
132//!
133//! Once. Section 22.7 asks for two instances at `-O2`, one before the loop pipeline and one after,
134//! because the loop passes make diamonds. There is no loop pipeline yet, so the second instance
135//! would be a second walk over every function to find the shapes the first one already took, and
136//! it belongs in the change that adds the passes it exists to clean up after.
137//!
138//! Section 22.2 also wants a peephole run after this one, so that the rule set can answer what the
139//! `select` becomes: `select(c, a, a)` is `a`, `select(c, 1, 0)` is `zext(c)`, and the min, max and
140//! abs recognitions are all rules rather than code here. Those rules are tier six of
141//! `spec/optimizer/13-rewrite-rules.md` and none of them are written, so the run that would fire
142//! them is not in the pipeline yet either. It goes in with them.
143
144use rucc_cost::heuristics;
145use rucc_ir::{Block, Builder, Func, Inst, Opcode, Type, Value};
146
147use crate::cfg::Cfg;
148use crate::fold::constant;
149use crate::profile::Probability;
150use crate::simplify_cfg::{self, Bindings};
151use crate::{Analyses, Fuel, Pass, Preserved, Stats};
152
153/// Recorded once for each diamond that became a select.
154const CONVERTED: &str =
155    "branch whose two arms only work out a value replaced by the value and no branch";
156
157/// Recorded for a diamond one of whose arms does something that has to happen.
158const ARM_HAS_EFFECTS: &str =
159    "branch kept, an arm does something that only happens on the path it is on";
160
161/// Recorded for a diamond one of whose arms divides by something that could be zero.
162const ARM_MAY_TRAP: &str = "branch kept, an arm divides and doing it on both paths could trap";
163
164/// Recorded for a diamond whose two arms disagree about a value nothing can choose between.
165const NO_SELECT_AT_THAT_WIDTH: &str =
166    "branch kept, the value the arms disagree about is not a width a select is lowered at";
167
168/// Recorded for a diamond whose arms are more work than the branch is worth.
169const ARMS_TOO_LONG: &str = "branch kept, its arms are more work than doing both of them is worth";
170
171/// Recorded for a diamond whose branch the estimate says the machine will get right.
172const BRANCH_IS_PREDICTED: &str =
173    "branch kept, it goes one way often enough that the machine will predict it";
174
175/// Recorded for a diamond that would have been converted if there had been fuel for it.
176const CONDITION_IS_DECIDED: &str =
177    "branch kept, its condition is already known and the arm that cannot run is better deleted";
178const NO_FUEL: &str = "branch kept, the pass ran out of fuel";
179
180/// The pass.
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub struct PhiOpt;
183
184impl Pass for PhiOpt {
185    fn name(&self) -> &'static str {
186        "phiopt"
187    }
188
189    fn describe(&self) -> &'static str {
190        "a branch whose two arms only work out a value becomes a select, and the branch goes"
191    }
192
193    fn preserves(&self) -> Preserved {
194        // Nothing. Blocks stop existing and an edge stops existing with them, so every analysis
195        // built on the graph was built on a different graph.
196        Preserved::NONE
197    }
198
199    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
200        let mut stats = Stats::new();
201        if func.entry().is_none() {
202            return stats;
203        }
204        for head in func.blocks().collect::<Vec<Block>>() {
205            let cfg = an.cfg(func);
206            if !cfg.reaches(head) {
207                continue;
208            }
209            let Some(shape) = diamond(func, cfg, head) else { continue };
210            if let Some(reason) = refused(func, &shape) {
211                stats.missed(reason);
212                continue;
213            }
214            let work = shape.arms.map(|arm| arm.map_or(0, |block| length(func, block)));
215            if work.iter().any(|&count| count > 0) {
216                if work.iter().any(|&count| count > heuristics::PHIOPT_ARM_INSTRUCTIONS) {
217                    stats.missed(ARMS_TOO_LONG);
218                    continue;
219                }
220                // The first edge out of the head, which is the arm taken when the condition holds,
221                // because `Cfg::successors` is in the order the terminator names its targets. Which
222                // of the two is asked about does not matter, since the question is whether the
223                // number is near even and the other edge is its complement.
224                if !unpredictable(an.frequencies(func).taken(head, 0)) {
225                    stats.missed(BRANCH_IS_PREDICTED);
226                    continue;
227                }
228            }
229            if !fuel.take() {
230                // Where the pass stops rather than where it starts skipping, for the reason jump
231                // threading gives: a budget that has reached zero will not have anything in it at
232                // the next block either, and the refusals above are the counts worth being true.
233                stats.missed(NO_FUEL);
234                break;
235            }
236            convert(func, &shape);
237            // The graph was about the function as it was a moment ago, and the manager clears the
238            // cache after the pass returns, which is too late for the next block.
239            an.clear();
240            stats.optimized(CONVERTED);
241        }
242        stats
243    }
244}
245
246/// A branch whose two arms meet again, and what each of them hands the block they meet at.
247struct Diamond {
248    /// The block the branch is in.
249    head: Block,
250    /// The bit the branch is on, which is the bit the selects are on.
251    cond: Value,
252    /// The block both arms reach.
253    join: Block,
254    /// The block on each side, when that side is a block of its own rather than the join.
255    ///
256    /// Index zero is the side taken when the condition holds, which is the side `select` calls
257    /// `then`, and the order is the order the terminator names its targets in.
258    arms: [Option<Block>; 2],
259    /// What each side hands the join, in the order the join takes its parameters.
260    args: [Vec<Value>; 2],
261}
262
263/// The diamond this block is the head of, if it is the head of one.
264fn diamond(func: &Func, cfg: &Cfg, head: Block) -> Option<Diamond> {
265    let entry = cfg.entry()?;
266    let term = func.terminator(head)?;
267    if func[term].opcode != Opcode::BrIf {
268        return None;
269    }
270    let cond = *func[func[term].args].first()?;
271    let mut targets = func.successors(term);
272    let sides = [targets.next()?, targets.next()?];
273    // Both arms at the same block is a branch that goes to one place carrying two argument lists.
274    // It is convertible and it is rare enough not to be worth a second shape, and `simplify-cfg`
275    // takes the case where the two lists agree.
276    if sides[0].block == sides[1].block {
277        return None;
278    }
279    let through = [
280        passes_through(func, cfg, head, sides[0].block),
281        passes_through(func, cfg, head, sides[1].block),
282    ];
283    // The diamond, then the two triangles. A side that is not the join has to be a block that
284    // reaches it, which is what makes the arm below a side that has one.
285    let join = match through {
286        [Some(left), Some(right)] if left == right => left,
287        [Some(left), _] if left == sides[1].block => left,
288        [_, Some(right)] if right == sides[0].block => right,
289        _ => return None,
290    };
291    // A join that is the head is a loop with nothing outside it, and one that is the entry is a
292    // block control arrives at rather than one it reaches.
293    if join == head || join == entry {
294        return None;
295    }
296    let arms = [
297        (sides[0].block != join).then_some(sides[0].block),
298        (sides[1].block != join).then_some(sides[1].block),
299    ];
300    let mut args = [Vec::new(), Vec::new()];
301    for (index, side) in sides.iter().enumerate() {
302        let carried = match arms[index] {
303            // The arm's own jump is what tells the join what this side worked out.
304            Some(arm) => func.successors(func.terminator(arm)?).next()?.args,
305            None => side.args,
306        };
307        args[index] = func[carried].to_vec();
308    }
309    Some(Diamond { head, cond, join, arms, args })
310}
311
312/// Where this side of the branch ends up, when it is a block whose only job is to get there.
313///
314/// Everything this asks is needed. Parameters, because a block that takes them is being told
315/// something on the edge and there would be nothing to tell it once the edge is gone. One
316/// predecessor and it being the head, because that is the whole argument for moving the block's
317/// work upward and it is also what makes removing the block afterwards legal. A jump, because an
318/// arm that branches is a second decision and this pass is about one.
319fn passes_through(func: &Func, cfg: &Cfg, head: Block, block: Block) -> Option<Block> {
320    if !func[block].params.is_empty() {
321        return None;
322    }
323    match cfg.predecessors(block) {
324        [only] if *only == head => {}
325        _ => return None,
326    }
327    let term = func.terminator(block)?;
328    if func[term].opcode != Opcode::Jump {
329        return None;
330    }
331    Some(func.successors(term).next()?.block)
332}
333
334/// Why this diamond is left alone, or `None` when nothing is in the way.
335fn refused(func: &Func, shape: &Diamond) -> Option<&'static str> {
336    // A branch nobody has to take is not a branch worth removing. `simplify-cfg` runs after this
337    // pass and turns a decided branch into a jump, and then the arm that cannot run is deleted
338    // whole. Converting first replaces a branch that costs nothing with a select that costs
339    // something, and the fold that would undo it is a rule the set does not have yet, so the work
340    // in the arm that never ran survives into the machine code. The corpus found this on `if (1)`.
341    //
342    // The question is put to `simplify-cfg` rather than answered again here, for the reason its
343    // own documentation gives: two answers about when a branch is decided would be two compilers.
344    // It matters in this case, because the condition on `if (1)` is not a constant, it is a
345    // comparison of two constants, which `fold` deliberately leaves standing.
346    let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
347    if simplify_cfg::taken(func, term, &Bindings::new()).is_some() {
348        return Some(CONDITION_IS_DECIDED);
349    }
350    for &arm in shape.arms.iter().flatten() {
351        for inst in func.insts(arm) {
352            if func.is_terminator(inst) {
353                continue;
354            }
355            if func[inst].opcode.has_effects() {
356                return Some(ARM_HAS_EFFECTS);
357            }
358            if !speculatable(func, inst) {
359                return Some(ARM_MAY_TRAP);
360            }
361        }
362    }
363    let params = func[shape.join].params.iter();
364    for ((&param, &then), &other) in params.zip(&shape.args[0]).zip(&shape.args[1]) {
365        // The two sides agreeing about a parameter is the common case in a triangle, where one
366        // side passes on what it was already holding, and it needs no select at all.
367        if agree(func, then, other) {
368            continue;
369        }
370        if !selectable(func[param].ty) {
371            return Some(NO_SELECT_AT_THAT_WIDTH);
372        }
373    }
374    None
375}
376
377/// Whether the two sides hand the join the same thing, so that no `select` is needed for it.
378///
379/// The same value is the easy answer and it is the one a triangle gives, where one side passes on
380/// what it was already holding. The same constant is the answer the corpus asked for. `x ? 7 : 7`
381/// arrives here as two `iconst.i32 7` instructions, one in each arm, which are two values because
382/// nothing has hash consed them into one. Asking only about the value builds a `select` between two
383/// sevens, which costs a compare, a byte and a conditional move to work out that seven is seven.
384/// The module comment says what the general answer would be and why it is not available yet.
385fn agree(func: &Func, then: Value, other: Value) -> bool {
386    if then == other {
387        return true;
388    }
389    let (Some((left, lty)), Some((right, rty))) = (constant(func, then), constant(func, other))
390    else {
391        return false;
392    };
393    lty == rty && left == right
394}
395
396/// Whether doing this on a path that was not going to do it is harmless.
397///
398/// Only division asks anything here, because the caller has already refused everything with an
399/// effect and what is left is arithmetic. Zero is the divisor everybody knows about. Minus one is
400/// the other one: the smallest signed number divided by it is not representable and x86 raises the
401/// same exception it raises for zero.
402fn speculatable(func: &Func, inst: Inst) -> bool {
403    let opcode = func[inst].opcode;
404    if !matches!(opcode, Opcode::SDiv | Opcode::UDiv | Opcode::SRem | Opcode::URem) {
405        return true;
406    }
407    let Some(&divisor) = func[func[inst].args].get(1) else { return false };
408    let Some((imm, ty)) = constant(func, divisor) else { return false };
409    if imm.unsigned() == 0 {
410        return false;
411    }
412    imm.signed(ty) != -1
413}
414
415/// Whether a value of this type is one a `select` can choose.
416///
417/// The four widths `crates/rucc-ir/src/term.rs` names a `select` at. A wider integer, a float, a
418/// pointer, a bit or a vector has no head, so a `select` of one would be a term the rule set has
419/// no lowering for and the failure would be at instruction selection rather than here.
420fn selectable(ty: Type) -> bool {
421    ty.is_scalar() && ty.is_int() && matches!(ty.bits(), 8 | 16 | 32 | 64)
422}
423
424/// How much work an arm does, not counting the jump that is about to go.
425fn length(func: &Func, block: Block) -> u32 {
426    let count = func.insts(block).filter(|&inst| !func.is_terminator(inst)).count();
427    u32::try_from(count).unwrap_or(u32::MAX)
428}
429
430/// Whether the estimate leaves enough doubt about this branch to be worth removing it.
431fn unpredictable(taken: Probability) -> bool {
432    let margin = heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT * (Probability::SCALE / 100);
433    taken.parts() >= margin && taken.parts() <= Probability::SCALE - margin
434}
435
436/// Moves the arms into the head, builds the selects and takes the branch out.
437///
438/// The order matters and is the reason this is one function. The branch goes first, so that what
439/// the arms were doing can be appended to the head without anything having to be threaded around a
440/// terminator. The selects are built after that work has moved, since they read what it produced.
441/// The jump goes last because it is the terminator.
442fn convert(func: &mut Func, shape: &Diamond) {
443    let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
444    let span = func.span(term);
445    func.remove_inst(term);
446    for &arm in shape.arms.iter().flatten() {
447        for inst in func.insts(arm).collect::<Vec<Inst>>() {
448            if func.is_terminator(inst) {
449                continue;
450            }
451            func.remove_inst(inst);
452            func.append_inst(shape.head, inst);
453        }
454    }
455    let mut build = Builder::new(func, shape.head).at(span);
456    let mut args = Vec::with_capacity(shape.args[0].len());
457    for (&then, &other) in shape.args[0].iter().zip(&shape.args[1]) {
458        // The condition holds on the first side, which is the side `select` takes when the bit is
459        // one, so the order the branch named its targets in is the order the arguments go in.
460        let same = agree(build.func(), then, other);
461        args.push(if same { then } else { build.select(shape.cond, then, other) });
462    }
463    build.jump(shape.join, &args);
464    // Nothing arrives at the arms now, and section 6.5 makes taking an unreachable block out the
465    // standing obligation of whichever pass stranded it rather than something the next pass tidies
466    // up. The verifier holds every pass to that.
467    for &arm in shape.arms.iter().flatten() {
468        func.remove_block(arm);
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use rucc_base::Interner;
475    use rucc_ir::{
476        Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict, Signature, Type,
477        Value,
478    };
479
480    use super::PhiOpt;
481    use crate::profile::{Probability, Quality};
482    use crate::stats::Kind;
483    use crate::{Analyses, Fuel, Pass, Stats};
484
485    /// Runs the pass with as much fuel as it wants.
486    fn phiopt(func: &mut Func) -> Stats {
487        PhiOpt.run(func, &mut Analyses::new(), &mut Fuel::unlimited())
488    }
489
490    /// The blocks the function still has, by number.
491    fn blocks(func: &Func) -> Vec<usize> {
492        func.blocks().map(Block::index).collect()
493    }
494
495    /// Where a block's terminator goes, as block numbers.
496    fn goes_to(func: &Func, block: usize) -> Vec<usize> {
497        let block = Block::from_usize(block);
498        let term = func.terminator(block).expect("every block here has one");
499        func.successors(term).map(|call| call.block.index()).collect()
500    }
501
502    /// The opcodes a block holds, in order.
503    fn opcodes(func: &Func, block: usize) -> Vec<Opcode> {
504        let block = Block::from_usize(block);
505        func.insts(block).map(|inst| func[inst].opcode).collect()
506    }
507
508    /// What a block's terminator carries on its first edge.
509    fn carries(func: &Func, block: usize) -> Vec<Value> {
510        let block = Block::from_usize(block);
511        let term = func.terminator(block).expect("every block here has one");
512        let call = func.successors(term).next().expect("a terminator here has an edge");
513        func[call.args].to_vec()
514    }
515
516    /// A store, which is the instruction used here whenever something has to happen.
517    fn store_something(build: &mut Builder<'_>) {
518        let what = build.iconst(Type::int(32), 7);
519        let address = build.iconst(Type::int(64), 16);
520        let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
521        let info = MemInfo {
522            size: 4,
523            align: 4,
524            order: MemOrder::NotAtomic,
525            tbaa: None,
526            restrict: Restrict::NONE,
527        };
528        build.store(what, address, info, Flags::NONE);
529    }
530
531    /// `x < y ? a : b`, as a diamond whose two arms are empty.
532    ///
533    /// Block 0 is the head and takes the two values it compares as function parameters, blocks 1
534    /// and 2 are the arms and carry one of two constants, and block 3 is the join and returns what
535    /// it was given.
536    fn empty_arms() -> Func {
537        let mut names = Interner::new();
538        let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
539        let mut func = Func::new(names.intern("f"), signature);
540        let head = func.create_block();
541        let left = func.append_param(head, Type::int(32));
542        let right = func.append_param(head, Type::int(32));
543        let arms = [func.create_block(), func.create_block()];
544        let join = func.create_block();
545        let param = func.append_param(join, Type::int(32));
546
547        let mut build = Builder::new(&mut func, head);
548        let test = build.icmp(IntPred::Slt, left, right);
549        build.br_if(test, arms[0], &[], arms[1], &[]);
550        for (arm, value) in arms.iter().zip([1, 2]) {
551            let mut build = Builder::new(&mut func, *arm);
552            let it = build.iconst(Type::int(32), value);
553            build.jump(join, &[it]);
554        }
555        let mut build = Builder::new(&mut func, join);
556        build.ret(&[param]);
557        func
558    }
559
560    #[test]
561    fn a_branch_that_is_already_decided_is_left_for_simplify_cfg() {
562        // What `if (1)` looks like by the time it gets here. Converting would build a select on a
563        // constant and keep the arm that cannot run, and the pass that would fold it does not
564        // exist, so the answer is to leave the branch alone and let the arm be deleted whole.
565        let mut names = Interner::new();
566        let mut func = Func::new(names.intern("f"), Signature::new());
567        let head = func.create_block();
568        let arms = [func.create_block(), func.create_block()];
569        let join = func.create_block();
570        let param = func.append_param(join, Type::int(32));
571
572        let mut build = Builder::new(&mut func, head);
573        // What `if (1)` reaches this pass as. Not a constant, a comparison of two constants, since
574        // `fold` will not turn an `icmp` into an `i1` that nothing lowers.
575        let one = build.iconst(Type::int(32), 1);
576        let zero = build.iconst(Type::int(32), 0);
577        let test = build.icmp(IntPred::Ne, one, zero);
578        build.br_if(test, arms[0], &[], arms[1], &[]);
579        for (arm, value) in arms.iter().zip([1, 2]) {
580            let mut build = Builder::new(&mut func, *arm);
581            let it = build.iconst(Type::int(32), value);
582            build.jump(join, &[it]);
583        }
584        let mut build = Builder::new(&mut func, join);
585        build.ret(&[param]);
586
587        let stats = phiopt(&mut func);
588        assert_eq!(stats.count(Kind::Missed, super::CONDITION_IS_DECIDED), 1);
589        assert_eq!(blocks(&func), vec![0, 1, 2, 3]);
590    }
591
592    #[test]
593    fn a_diamond_whose_arms_are_empty_becomes_a_select() {
594        let mut func = empty_arms();
595        let stats = phiopt(&mut func);
596        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
597        // The two constants moved up with the arms, and the select is what the branch was.
598        assert_eq!(
599            opcodes(&func, 0),
600            vec![Opcode::ICmp, Opcode::IConst, Opcode::IConst, Opcode::Select, Opcode::Jump]
601        );
602        assert_eq!(goes_to(&func, 0), vec![3]);
603        assert_eq!(blocks(&func), vec![0, 3]);
604    }
605
606    #[test]
607    fn the_side_the_condition_holds_on_is_the_side_the_select_takes_first() {
608        let mut func = empty_arms();
609        phiopt(&mut func);
610        let select = func
611            .insts(Block::from_usize(0))
612            .find(|&inst| func[inst].opcode == Opcode::Select)
613            .expect("the select the pass just built");
614        let args = func[func[select].args].to_vec();
615        let one = crate::fold::constant(&func, args[1]).expect("the true arm carried a constant");
616        let two = crate::fold::constant(&func, args[2]).expect("the false arm carried a constant");
617        assert_eq!(one.0.unsigned(), 1, "the arm the branch named first");
618        assert_eq!(two.0.unsigned(), 2, "the arm the branch named second");
619    }
620
621    /// A triangle: one side goes straight to the join carrying what it already had.
622    #[test]
623    fn a_triangle_whose_empty_side_goes_straight_to_the_join_is_converted() {
624        let mut names = Interner::new();
625        let signature = Signature::new().with_params(&[Type::int(32)]);
626        let mut func = Func::new(names.intern("f"), signature);
627        let head = func.create_block();
628        let outside = func.append_param(head, Type::int(32));
629        let arm = func.create_block();
630        let join = func.create_block();
631        let param = func.append_param(join, Type::int(32));
632
633        let mut build = Builder::new(&mut func, head);
634        let zero = build.iconst(Type::int(32), 0);
635        let test = build.icmp(IntPred::Slt, outside, zero);
636        build.br_if(test, arm, &[], join, &[outside]);
637        let mut build = Builder::new(&mut func, arm);
638        let it = build.iconst(Type::int(32), 0);
639        build.jump(join, &[it]);
640        let mut build = Builder::new(&mut func, join);
641        build.ret(&[param]);
642
643        let stats = phiopt(&mut func);
644        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
645        assert_eq!(blocks(&func), vec![0, 2]);
646        assert_eq!(goes_to(&func, 0), vec![2]);
647        assert_eq!(opcodes(&func, 0).last(), Some(&Opcode::Jump));
648    }
649
650    #[test]
651    fn a_parameter_both_sides_agree_about_needs_no_select() {
652        let mut names = Interner::new();
653        let signature = Signature::new().with_params(&[Type::int(32)]);
654        let mut func = Func::new(names.intern("f"), signature);
655        let head = func.create_block();
656        let outside = func.append_param(head, Type::int(32));
657        let arms = [func.create_block(), func.create_block()];
658        let join = func.create_block();
659        let param = func.append_param(join, Type::int(32));
660
661        let mut build = Builder::new(&mut func, head);
662        let zero = build.iconst(Type::int(32), 0);
663        let test = build.icmp(IntPred::Slt, outside, zero);
664        build.br_if(test, arms[0], &[], arms[1], &[]);
665        for arm in arms {
666            let mut build = Builder::new(&mut func, arm);
667            build.jump(join, &[outside]);
668        }
669        let mut build = Builder::new(&mut func, join);
670        build.ret(&[param]);
671
672        let stats = phiopt(&mut func);
673        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
674        assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried the same value");
675        assert_eq!(carries(&func, 0), vec![outside]);
676    }
677
678    #[test]
679    fn two_sides_carrying_the_same_number_need_no_select_either() {
680        // `x ? 7 : 7`, which the corpus has eight of. The two sevens are two values, because
681        // nothing has hash consed them into one, so asking only whether the values are equal
682        // builds a select between two sevens and pays a compare and a conditional move for it.
683        let mut names = Interner::new();
684        let signature = Signature::new().with_params(&[Type::int(32)]);
685        let mut func = Func::new(names.intern("f"), signature);
686        let head = func.create_block();
687        let outside = func.append_param(head, Type::int(32));
688        let arms = [func.create_block(), func.create_block()];
689        let join = func.create_block();
690        let param = func.append_param(join, Type::int(32));
691
692        let mut build = Builder::new(&mut func, head);
693        let zero = build.iconst(Type::int(32), 0);
694        let test = build.icmp(IntPred::Slt, outside, zero);
695        build.br_if(test, arms[0], &[], arms[1], &[]);
696        for arm in arms {
697            let mut build = Builder::new(&mut func, arm);
698            let seven = build.iconst(Type::int(32), 7);
699            build.jump(join, &[seven]);
700        }
701        let mut build = Builder::new(&mut func, join);
702        build.ret(&[param]);
703
704        let stats = phiopt(&mut func);
705        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
706        assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried a seven");
707    }
708
709    #[test]
710    fn two_sides_carrying_different_numbers_still_get_a_select() {
711        let mut func = empty_arms();
712        let stats = phiopt(&mut func);
713        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
714        assert!(opcodes(&func, 0).contains(&Opcode::Select), "one and two are not the same number");
715    }
716
717    #[test]
718    fn an_arm_that_does_something_keeps_its_branch() {
719        let mut names = Interner::new();
720        let signature = Signature::new().with_params(&[Type::int(32)]);
721        let mut func = Func::new(names.intern("f"), signature);
722        let head = func.create_block();
723        let outside = func.append_param(head, Type::int(32));
724        let arms = [func.create_block(), func.create_block()];
725        let join = func.create_block();
726        let param = func.append_param(join, Type::int(32));
727
728        let mut build = Builder::new(&mut func, head);
729        let zero = build.iconst(Type::int(32), 0);
730        let test = build.icmp(IntPred::Slt, outside, zero);
731        build.br_if(test, arms[0], &[], arms[1], &[]);
732        let mut build = Builder::new(&mut func, arms[0]);
733        store_something(&mut build);
734        let it = build.iconst(Type::int(32), 1);
735        build.jump(join, &[it]);
736        let mut build = Builder::new(&mut func, arms[1]);
737        let it = build.iconst(Type::int(32), 2);
738        build.jump(join, &[it]);
739        let mut build = Builder::new(&mut func, join);
740        build.ret(&[param]);
741
742        let stats = phiopt(&mut func);
743        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
744        assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
745        assert_eq!(goes_to(&func, 0), vec![1, 2]);
746    }
747
748    /// A division whose divisor is not known cannot be moved onto the path that skipped it.
749    #[test]
750    fn an_arm_that_divides_by_something_unknown_keeps_its_branch() {
751        let mut names = Interner::new();
752        let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
753        let mut func = Func::new(names.intern("f"), signature);
754        let head = func.create_block();
755        let left = func.append_param(head, Type::int(32));
756        let right = func.append_param(head, Type::int(32));
757        let arms = [func.create_block(), func.create_block()];
758        let join = func.create_block();
759        let param = func.append_param(join, Type::int(32));
760
761        let mut build = Builder::new(&mut func, head);
762        let zero = build.iconst(Type::int(32), 0);
763        let test = build.icmp(IntPred::Ne, right, zero);
764        build.br_if(test, arms[0], &[], arms[1], &[]);
765        let mut build = Builder::new(&mut func, arms[0]);
766        let it = build.binary(Opcode::SDiv, left, right, Flags::NONE);
767        build.jump(join, &[it]);
768        let mut build = Builder::new(&mut func, arms[1]);
769        let it = build.iconst(Type::int(32), 0);
770        build.jump(join, &[it]);
771        let mut build = Builder::new(&mut func, join);
772        build.ret(&[param]);
773
774        let stats = phiopt(&mut func);
775        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
776        assert_eq!(stats.count(Kind::Missed, super::ARM_MAY_TRAP), 1);
777        assert_eq!(goes_to(&func, 0), vec![1, 2]);
778    }
779
780    #[test]
781    fn a_division_by_a_constant_that_is_not_zero_or_minus_one_is_moved() {
782        let mut names = Interner::new();
783        let signature = Signature::new().with_params(&[Type::int(32)]);
784        let mut func = Func::new(names.intern("f"), signature);
785        let head = func.create_block();
786        let outside = func.append_param(head, Type::int(32));
787        let arms = [func.create_block(), func.create_block()];
788        let join = func.create_block();
789        let param = func.append_param(join, Type::int(32));
790
791        let mut build = Builder::new(&mut func, head);
792        let zero = build.iconst(Type::int(32), 0);
793        let test = build.icmp(IntPred::Slt, outside, zero);
794        build.br_if(test, arms[0], &[], arms[1], &[]);
795        let mut build = Builder::new(&mut func, arms[0]);
796        let three = build.iconst(Type::int(32), 3);
797        let it = build.binary(Opcode::SDiv, outside, three, Flags::NONE);
798        build.jump(join, &[it]);
799        let mut build = Builder::new(&mut func, arms[1]);
800        let it = build.iconst(Type::int(32), 0);
801        build.jump(join, &[it]);
802        let mut build = Builder::new(&mut func, join);
803        build.ret(&[param]);
804
805        let stats = phiopt(&mut func);
806        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
807        assert!(opcodes(&func, 0).contains(&Opcode::SDiv));
808    }
809
810    /// Nothing chooses between two pointers, so the shape is matched and then left alone.
811    #[test]
812    fn a_value_no_select_is_lowered_for_keeps_its_branch() {
813        let mut names = Interner::new();
814        let signature = Signature::new().with_params(&[Type::int(32)]);
815        let mut func = Func::new(names.intern("f"), signature);
816        let head = func.create_block();
817        let outside = func.append_param(head, Type::int(32));
818        let arms = [func.create_block(), func.create_block()];
819        let join = func.create_block();
820        func.append_param(join, Type::PTR);
821
822        let mut build = Builder::new(&mut func, head);
823        let zero = build.iconst(Type::int(32), 0);
824        let test = build.icmp(IntPred::Slt, outside, zero);
825        build.br_if(test, arms[0], &[], arms[1], &[]);
826        for (arm, value) in arms.iter().zip([16, 32]) {
827            let mut build = Builder::new(&mut func, *arm);
828            let it = build.iconst(Type::int(64), value);
829            let it = build.unary(Opcode::IntToPtr, it, Type::PTR);
830            build.jump(join, &[it]);
831        }
832        let mut build = Builder::new(&mut func, join);
833        build.ret(&[]);
834
835        let stats = phiopt(&mut func);
836        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
837        assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
838    }
839
840    #[test]
841    fn arms_with_more_work_in_them_than_the_budget_keep_their_branch() {
842        let mut names = Interner::new();
843        let signature = Signature::new().with_params(&[Type::int(32)]);
844        let mut func = Func::new(names.intern("f"), signature);
845        let head = func.create_block();
846        let outside = func.append_param(head, Type::int(32));
847        let arms = [func.create_block(), func.create_block()];
848        let join = func.create_block();
849        let param = func.append_param(join, Type::int(32));
850
851        let mut build = Builder::new(&mut func, head);
852        let zero = build.iconst(Type::int(32), 0);
853        let test = build.icmp(IntPred::Slt, outside, zero);
854        build.br_if(test, arms[0], &[], arms[1], &[]);
855        let mut build = Builder::new(&mut func, arms[0]);
856        // Four instructions, which is past the budget however cheap each of them is.
857        let mut it = outside;
858        for _ in 0..4 {
859            it = build.binary(Opcode::Add, it, outside, Flags::NONE);
860        }
861        build.jump(join, &[it]);
862        let mut build = Builder::new(&mut func, arms[1]);
863        let it = build.iconst(Type::int(32), 0);
864        build.jump(join, &[it]);
865        let mut build = Builder::new(&mut func, join);
866        build.ret(&[param]);
867
868        let stats = phiopt(&mut func);
869        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
870        assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
871    }
872
873    /// The margin, at the two ends of it and just outside.
874    ///
875    /// A pass level test of the refusal it guards is not written, and the module doc says why: a
876    /// diamond is the one shape none of document 11's one sided predictors can key on, so every
877    /// branch this pass matches comes back even until `__builtin_expect` is wired through the
878    /// front end. The arithmetic is what there is to check today.
879    #[test]
880    fn the_margin_is_a_quarter_in_from_each_end() {
881        let guessed = |percent: u32| Probability::percent(percent, Quality::Guessed);
882        assert!(super::unpredictable(Probability::even()));
883        assert!(super::unpredictable(guessed(25)));
884        assert!(super::unpredictable(guessed(75)));
885        assert!(!super::unpredictable(guessed(24)));
886        assert!(!super::unpredictable(guessed(76)));
887        assert!(!super::unpredictable(Probability::always()));
888        assert!(!super::unpredictable(Probability::never()));
889    }
890
891    #[test]
892    fn an_arm_that_two_edges_reach_is_not_an_arm() {
893        let mut names = Interner::new();
894        let signature = Signature::new().with_params(&[Type::int(32)]);
895        let mut func = Func::new(names.intern("f"), signature);
896        let head = func.create_block();
897        let outside = func.append_param(head, Type::int(32));
898        let above = func.create_block();
899        let arms = [func.create_block(), func.create_block()];
900        let join = func.create_block();
901        let param = func.append_param(join, Type::int(32));
902
903        // The entry reaches the first arm as well as the head does, so moving the arm's work into
904        // the head would leave the entry's path without it.
905        let mut build = Builder::new(&mut func, head);
906        let zero = build.iconst(Type::int(32), 0);
907        let first = build.icmp(IntPred::Slt, outside, zero);
908        build.br_if(first, above, &[], arms[0], &[]);
909        let mut build = Builder::new(&mut func, above);
910        let one = build.iconst(Type::int(32), 1);
911        let second = build.icmp(IntPred::Slt, outside, one);
912        build.br_if(second, arms[0], &[], arms[1], &[]);
913        for (arm, value) in arms.iter().zip([1, 2]) {
914            let mut build = Builder::new(&mut func, *arm);
915            let it = build.iconst(Type::int(32), value);
916            build.jump(join, &[it]);
917        }
918        let mut build = Builder::new(&mut func, join);
919        build.ret(&[param]);
920
921        let stats = phiopt(&mut func);
922        // Neither branch is a diamond. The head's first side goes to a block that is not the join
923        // and is not an arm either, since two edges reach it, and the second branch's first side
924        // is the same block for the same reason.
925        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
926        assert_eq!(goes_to(&func, 0), vec![1, 2]);
927        assert_eq!(goes_to(&func, 1), vec![2, 3]);
928    }
929
930    #[test]
931    fn fuel_stops_the_conversion_where_it_stands() {
932        let mut func = empty_arms();
933        let mut fuel = Fuel::of(0);
934        let stats = PhiOpt.run(&mut func, &mut Analyses::new(), &mut fuel);
935        assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
936        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
937        assert_eq!(goes_to(&func, 0), vec![1, 2]);
938    }
939}