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