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 // A block an image holds the address of has a way in the graph does not show: what arrives
507 // there is a `goto *p` that can be in another function, so the one predecessor below is not the
508 // only one and the block is not one this pass may take out.
509 if func.block_name(block).is_some() {
510 return None;
511 }
512 match cfg.predecessors(block) {
513 [only] if *only == head => {}
514 _ => return None,
515 }
516 let term = func.terminator(block)?;
517 if func[term].opcode != Opcode::Jump {
518 return None;
519 }
520 Some(func.successors(term).next()?.block)
521}
522
523/// Why this diamond is left alone, or `None` when nothing is in the way.
524///
525/// The store plan is passed in because the two stores it names are the one pair of instructions
526/// with effects this pass is allowed to move, and everything else with an effect still refuses.
527fn refused(
528 func: &Func,
529 shape: &Diamond,
530 store: Option<&Stored>,
531 implied: &[Option<usize>],
532) -> Option<&'static str> {
533 // A branch nobody has to take is not a branch worth removing. `simplify-cfg` runs after this
534 // pass and turns a decided branch into a jump, and then the arm that cannot run is deleted
535 // whole. Converting first replaces a branch that costs nothing with a select that costs
536 // something, and the fold that would undo it is a rule the set does not have yet, so the work
537 // in the arm that never ran survives into the machine code. The corpus found this on `if (1)`.
538 //
539 // The question is put to `simplify-cfg` rather than answered again here, for the reason its
540 // own documentation gives: two answers about when a branch is decided would be two compilers.
541 // It matters in this case, because the condition on `if (1)` is not a constant, it is a
542 // comparison of two constants, which `fold` deliberately leaves standing.
543 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
544 if simplify_cfg::taken(func, term, &Bindings::new()).is_some() {
545 return Some(CONDITION_IS_DECIDED);
546 }
547 let moving = store.map(|one| one.insts);
548 for &arm in shape.arms.iter().flatten() {
549 for inst in func.insts(arm) {
550 if func.is_terminator(inst) || moving.is_some_and(|two| two.contains(&inst)) {
551 continue;
552 }
553 if func[inst].opcode == Opcode::Store {
554 // Named separately from the effects below because it is a different answer rather
555 // than a stricter one. A store the other side does not make is section 22.2's
556 // fourth transformation without its proof, and section 22.6 calls it the worst bug
557 // in the document: making it on both paths writes memory the program was not going
558 // to write, which is not a no-op if another thread is writing the same bytes and is
559 // not a no-op if the page is read only. What would license it is knowing the
560 // location is written whatever happens, and nothing here knows that yet.
561 return Some(mismatch(func, shape));
562 }
563 if func[inst].opcode.has_effects() {
564 return Some(ARM_HAS_EFFECTS);
565 }
566 if !speculatable(func, inst) {
567 return Some(ARM_MAY_TRAP);
568 }
569 }
570 }
571 let params = func[shape.join].params.to_vec();
572 for (index, ¶m) in params.iter().enumerate() {
573 // The two sides agreeing about a parameter is the common case in a triangle, where one
574 // side passes on what it was already holding, and it needs no select at all. Neither does
575 // one the condition settles, which is why this is asked after that question rather than
576 // before it: a value of a type nothing can select between is fine when nothing has to.
577 if agree(func, shape.args[0][index], shape.args[1][index]) {
578 continue;
579 }
580 if implied.get(index).copied().flatten().is_some() {
581 continue;
582 }
583 if !selectable(func[param].ty) {
584 return Some(NO_SELECT_AT_THAT_WIDTH);
585 }
586 }
587 None
588}
589
590/// Whether the two sides hand the join the same thing, so that no `select` is needed for it.
591///
592/// The same value is the easy answer and it is the one a triangle gives, where one side passes on
593/// what it was already holding. The same constant is the answer the corpus asked for. `x ? 7 : 7`
594/// arrives here as two `iconst.i32 7` instructions, one in each arm, which are two values because
595/// nothing has hash consed them into one. Asking only about the value builds a `select` between two
596/// sevens, which costs a compare, a byte and a conditional move to work out that seven is seven.
597/// The module comment says what the general answer would be and why it is not available yet.
598fn agree(func: &Func, then: Value, other: Value) -> bool {
599 if then == other {
600 return true;
601 }
602 let (Some((left, lty)), Some((right, rty))) = (constant(func, then), constant(func, other))
603 else {
604 return false;
605 };
606 lty == rty && left == right
607}
608
609/// Whether doing this on a path that was not going to do it is harmless.
610///
611/// Only division asks anything here, because the caller has already refused everything with an
612/// effect and what is left is arithmetic. Zero is the divisor everybody knows about. Minus one is
613/// the other one: the smallest signed number divided by it is not representable and x86 raises the
614/// same exception it raises for zero.
615pub(crate) fn speculatable(func: &Func, inst: Inst) -> bool {
616 let opcode = func[inst].opcode;
617 if !matches!(opcode, Opcode::SDiv | Opcode::UDiv | Opcode::SRem | Opcode::URem) {
618 return true;
619 }
620 let Some(&divisor) = func[func[inst].args].get(1) else { return false };
621 let Some((imm, ty)) = constant(func, divisor) else { return false };
622 if imm.unsigned() == 0 {
623 return false;
624 }
625 imm.signed(ty) != -1
626}
627
628/// A store both arms make to the same place, which becomes one store below the branch.
629///
630/// Section 22.2's fourth transformation, in the half of it that needs no proof. `if (c) *p = a;
631/// else *p = b;` is `*p = c ? a : b`, and the number of stores is one before and one after, to the
632/// same address, of a value the program was going to write there on one path or the other.
633struct Stored {
634 /// The store each side wrote, which goes when the one copy below replaces both.
635 insts: [Inst; 2],
636 /// What each side wrote, taken in the order the branch names its targets.
637 values: [Value; 2],
638 /// The address, which is one value both sides named.
639 addr: Value,
640 /// The store to write once, whose value operand is replaced by the select above it.
641 data: InstData,
642}
643
644/// Which side's value serves for both, for each join parameter the branch condition settles.
645///
646/// Section 22.2's second transformation, `value_replacement`. `x = (a == b) ? b : a` is `x = a`,
647/// because the only way to arrive carrying `b` is along the edge where `a` and `b` are the same
648/// number. The select goes, and so does whichever arm was only there to work the other value out.
649///
650/// The answer for a parameter is the side whose value is passed on. If the two are known equal on
651/// one side's edge, then that side's value is the other side's value there, and the other side's
652/// value is right on both edges. Availability comes for free: everything both arms worked out is
653/// moved into the head before the jump is written, so a value that came from an arm is defined
654/// above the point it is now read at.
655///
656/// # Why the condition has to be an equality
657///
658/// This is the pass's one expensive question and section 22.7 says so. What answers it is document
659/// 10's relational oracle, which records what a dominating edge established between two values, and
660/// the edge out of a `br_if` establishes something about two values only when the branch is on a
661/// comparison of them. A branch on `x < n` says nothing about whether two other values are equal,
662/// so asking is a query that cannot come back with anything. Gating on the comparison being an
663/// equality is what turns this from a query per diamond into a query per diamond that could
664/// possibly answer, which on the corpus is a small fraction of them. GCC gates the same way, on
665/// `EQ_EXPR` and `NE_EXPR` at `gcc/tree-ssa-phiopt.cc`.
666fn implied(func: &Func, an: &mut Analyses, shape: &Diamond) -> Vec<Option<usize>> {
667 let count = shape.args[0].len();
668 let mut answers = vec![None; count];
669 if !equality(func, shape.cond) {
670 return answers;
671 }
672 let asking: Vec<usize> = (0..count).filter(|&index| worth_asking(func, shape, index)).collect();
673 if asking.is_empty() {
674 return answers;
675 }
676 // Cloned because the two are held at once and the cache hands out one borrow at a time. It is
677 // paid for only by a diamond that got this far, which the two gates above have already made
678 // rare, and section 22.7 is where the cost of this query was budgeted.
679 let cfg = an.cfg(func);
680 let dom = an.dominators(func);
681 let mut ranges = Ranges::new(func, cfg, dom);
682 for index in asking {
683 let pair = [shape.args[0][index], shape.args[1][index]];
684 for side in 0..2 {
685 let Some(block) = shape.arms[side] else { continue };
686 if ranges.compare(IntPred::Eq, pair[0], pair[1], block) == Truth::Always {
687 answers[index] = Some(1 - side);
688 break;
689 }
690 }
691 }
692 answers
693}
694
695/// Whether the branch is on a comparison that says two values are the same or are not.
696fn equality(func: &Func, cond: Value) -> bool {
697 let Def::Result { inst, .. } = func[cond].def else { return false };
698 if func[inst].opcode != Opcode::ICmp {
699 return false;
700 }
701 matches!(func[inst].extra, Extra::IntPred(IntPred::Eq | IntPred::Ne))
702}
703
704/// Whether this join parameter is one the oracle could have something to say about.
705///
706/// Two sides that agree need nothing. Two constants that are not the same number are not the same
707/// number on any edge, and asking is a query whose answer is already in hand.
708fn worth_asking(func: &Func, shape: &Diamond, index: usize) -> bool {
709 let pair = [shape.args[0][index], shape.args[1][index]];
710 if agree(func, pair[0], pair[1]) {
711 return false;
712 }
713 constant(func, pair[0]).is_none() || constant(func, pair[1]).is_none()
714}
715
716/// Which of the two store refusals this diamond is, once it is known to be one of them.
717///
718/// The two are worth separating because they say different things about what would fix them. One
719/// path storing is section 22.6's predicate, which is a proof nothing here can do. Both paths
720/// storing and not matching is usually two arms that worked the same address out separately, which
721/// is `a[i] = ...` on both sides, and what fixes that is document 16's value numbering making the
722/// two into one value rather than anything about memory.
723fn mismatch(func: &Func, shape: &Diamond) -> &'static str {
724 let [Some(then), Some(other)] = shape.arms else { return STORE_ON_ONE_PATH };
725 match (stored_in(func, then), stored_in(func, other)) {
726 (Some(_), Some(_)) => STORES_DO_NOT_MATCH,
727 _ => STORE_ON_ONE_PATH,
728 }
729}
730
731/// The store this diamond can move below the branch, if it has one.
732///
733/// Both sides have to have a block, which is what makes this the safe half of the transformation.
734/// A triangle has one side that is the join, and a store in the join already runs whichever way the
735/// branch went, so there is nothing here to move and the shape that reaches this with one arm is
736/// the one where a store happens on one path only. That one is refused above.
737fn storing(func: &Func, shape: &Diamond) -> Option<Stored> {
738 let [Some(then), Some(other)] = shape.arms else { return None };
739 let insts = [stored_in(func, then)?, stored_in(func, other)?];
740 let data = [func[insts[0]], func[insts[1]]];
741 // The flags are what the optimizer is licensed to assume about the access, so one store written
742 // under the union of two sets of assumptions would be claiming on one path something only the
743 // other path established. `volatile` is refused outright rather than by disagreeing, because
744 // section 22.6 says never and because the reason is not the flag matching: both how many
745 // accesses there are and what order they come in are observable, and a value that arrives
746 // through a select is a different program from one that arrives through a branch.
747 if data[0].flags != data[1].flags || data[0].flags.contains(Flags::VOLATILE) {
748 return None;
749 }
750 let (Extra::Mem(one), Extra::Mem(two)) = (data[0].extra, data[1].extra) else { return None };
751 // The alignment, the size, the aliasing node and the `restrict` scope, all of which the one
752 // store carries forward, so two that disagree about any of them have no single answer to carry.
753 if func[one] != func[two] || func[one].order != MemOrder::NotAtomic {
754 return None;
755 }
756 // A store names what it writes and then where, which is the order the builder takes them in.
757 let &[then, addr] = func[data[0].args].first_chunk::<2>()?;
758 let &[other, addr_two] = func[data[1].args].first_chunk::<2>()?;
759 // The same value for the address, which is stronger than the same address and is what can be
760 // checked without an alias analysis. It also settles where that value comes from: neither arm
761 // dominates the other, so a value both of them name is one worked out at or above the head, and
762 // the one store is written in the head where it is available.
763 if addr != addr_two || func[then].ty != func[other].ty {
764 return None;
765 }
766 if !agree(func, then, other) && !selectable(func[then].ty) {
767 return None;
768 }
769 Some(Stored { insts, values: [then, other], addr, data: data[0] })
770}
771
772/// The one store this arm makes, if it makes exactly one and does nothing else that has to happen.
773///
774/// Exactly one, because two stores below one select is two selects and a shape nothing has asked
775/// for. Nothing else with an effect, because everything else with an effect is still refused and
776/// this is the check that says so: an arm that stores and also calls something has a call that only
777/// happens on the path it is on, and no amount of agreement about the store changes that.
778fn stored_in(func: &Func, arm: Block) -> Option<Inst> {
779 let mut store = None;
780 for inst in func.insts(arm) {
781 if func.is_terminator(inst) || !func[inst].opcode.has_effects() {
782 continue;
783 }
784 if func[inst].opcode != Opcode::Store || store.is_some() {
785 return None;
786 }
787 store = Some(inst);
788 }
789 store
790}
791
792/// One join argument both arms worked out the same way, and the one operand they disagreed about.
793///
794/// Section 22.2's third transformation. `cond ? f(a) : f(b)` is `f(cond ? a : b)`, which is one
795/// operation where there were two and one select either way, and it is structural rather than a
796/// rewrite rule because the two `f`s are in different blocks and no pattern spans blocks.
797struct Factored {
798 /// The instruction each side wrote, which goes when the one copy below replaces both.
799 insts: [Inst; 2],
800 /// What each side handed that instruction, taken from the side taken when the condition holds.
801 operands: Vec<Value>,
802 /// The one position the two sides put different values in, and what each of them put there.
803 ///
804 /// `None` when they agree in every position, which is both arms computing the same thing from
805 /// the same operands. Then one copy serves both and there is no select at all.
806 differ: Option<(usize, [Value; 2])>,
807 /// The instruction to write once, whose operand list is replaced by the one above.
808 data: InstData,
809 /// What it produces.
810 ty: Type,
811}
812
813/// What can be factored out of each of the join's parameters, in the order the join takes them.
814///
815/// A triangle factors nothing. One of its sides is the join itself, so there is no block on that
816/// side holding an operation to pair the other one with, and what that side hands the join is a
817/// value worked out before the branch.
818fn factoring(func: &Func, shape: &Diamond, implied: &[Option<usize>]) -> Vec<Option<Factored>> {
819 let count = shape.args[0].len();
820 let [Some(then), Some(other)] = shape.arms else {
821 return (0..count).map(|_| None).collect();
822 };
823 (0..count)
824 .map(|index| {
825 // A value the condition settled is passed on whole, so there is no operation to write
826 // once below and the two that worked the two values out are left for dead code.
827 if implied.get(index).copied().flatten().is_some() {
828 return None;
829 }
830 factored(func, shape, [then, other], index)
831 })
832 .collect()
833}
834
835/// Whether this join argument is the same operation on both sides, and what to write instead.
836fn factored(func: &Func, shape: &Diamond, arms: [Block; 2], index: usize) -> Option<Factored> {
837 let sides = [shape.args[0][index], shape.args[1][index]];
838 // Two sides that agree need no operation written at all, and the caller passes the value on.
839 if agree(func, sides[0], sides[1]) {
840 return None;
841 }
842 let insts = [written_in(func, arms[0], sides[0])?, written_in(func, arms[1], sides[1])?];
843 let data = [func[insts[0]], func[insts[1]]];
844 // Everything about the two has to match except the operands. The flags are what the optimizer
845 // is licensed to assume, so writing one copy under the union of two sets of assumptions would
846 // be claiming on one path something only the other path established. The extra is whatever the
847 // instruction carries that is not an operand, which for a comparison is the predicate, and two
848 // predicates that differ are two different questions.
849 if data[0].opcode != data[1].opcode || data[0].flags != data[1].flags {
850 return None;
851 }
852 if data[0].extra != data[1].extra || func[sides[0]].ty != func[sides[1]].ty {
853 return None;
854 }
855 let operands = [func[data[0].args].to_vec(), func[data[1].args].to_vec()];
856 if operands[0].len() != operands[1].len() {
857 return None;
858 }
859 let mut apart =
860 operands[0].iter().zip(&operands[1]).enumerate().filter(|(_, (one, two))| one != two);
861 let differ = match (apart.next(), apart.next()) {
862 // Two positions apart would need two selects, and two selects and one operation is what
863 // one select and two operations already cost. There is nothing to win, so it is left.
864 (_, Some(_)) => return None,
865 (Some((at, (&one, &two))), None) => {
866 if func[one].ty != func[two].ty || !selectable(func[one].ty) {
867 return None;
868 }
869 Some((at, [one, two]))
870 }
871 (None, None) => None,
872 };
873 let ty = func[sides[0]].ty;
874 Some(Factored { insts, operands: operands[0].clone(), differ, data: data[0], ty })
875}
876
877/// The instruction in this arm that works out this value, if the arm is where it comes from and the
878/// only thing that reads it is the jump to the join.
879///
880/// Both halves are needed. The arm has to be where it is worked out, because an operation to factor
881/// out is one this pass is about to stop writing and it can only stop writing what it can find.
882/// Nothing else can read it, because the one copy that replaces the two is written after the arms
883/// have gone and a second reader in the arm would have been left pointing at an instruction that is
884/// no longer in any block.
885fn written_in(func: &Func, arm: Block, value: Value) -> Option<Inst> {
886 let inst = func
887 .insts(arm)
888 .find(|&inst| func[inst].results == 1 && func[inst].first_result == Some(value))?;
889 let mut seen = 0;
890 for inst in func.insts(arm) {
891 seen += func[func[inst].args].iter().filter(|&&arg| arg == value).count();
892 for call in func.successors(inst) {
893 seen += func[call.args].iter().filter(|&&arg| arg == value).count();
894 }
895 }
896 (seen == 1).then_some(inst)
897}
898
899/// Whether a value of this type is one a `select` can choose.
900///
901/// The four widths `crates/rucc-ir/src/term.rs` names a `select` at. A wider integer, a float, a
902/// pointer, a bit or a vector has no head, so a `select` of one would be a term the rule set has
903/// no lowering for and the failure would be at instruction selection rather than here.
904///
905/// This function is also the whole answer to whether a `select` at any of those types can exist at
906/// all, since this pass is the only one that turns a choice into one and every other writer of one
907/// in the tree is choosing between integers it built itself. `crates/rucc-codegen/src/quad.rs`
908/// leans on that where it says a conditional expression over two `_Float128`s stays a branch.
909fn selectable(ty: Type) -> bool {
910 ty.is_scalar() && ty.is_int() && matches!(ty.bits(), 8 | 16 | 32 | 64)
911}
912
913/// How much work an arm does, not counting the jump that is about to go.
914pub(crate) fn length(func: &Func, block: Block) -> u32 {
915 let count = func.insts(block).filter(|&inst| !func.is_terminator(inst)).count();
916 u32::try_from(count).unwrap_or(u32::MAX)
917}
918
919/// Whether the estimate leaves enough doubt about this branch to be worth removing it.
920pub(crate) fn unpredictable(taken: Probability) -> bool {
921 let margin = heuristics::PHIOPT_UNPREDICTABLE_MARGIN_PERCENT * (Probability::SCALE / 100);
922 taken.parts() >= margin && taken.parts() <= Probability::SCALE - margin
923}
924
925/// Moves the arms into the head, builds the selects and takes the branch out.
926///
927/// The order matters and is the reason this is one function. The branch goes first, so that what
928/// the arms were doing can be appended to the head without anything having to be threaded around a
929/// terminator. The selects are built after that work has moved, since they read what it produced.
930/// The jump goes last because it is the terminator.
931fn convert(
932 func: &mut Func,
933 shape: &Diamond,
934 plan: &[Option<Factored>],
935 store: Option<&Stored>,
936 implied: &[Option<usize>],
937) {
938 let term = func.terminator(shape.head).expect("the head of a diamond ends in its branch");
939 let span = func.span(term);
940 func.remove_inst(term);
941 let mut dropped: Vec<Inst> = plan.iter().flatten().flat_map(|one| one.insts).collect();
942 dropped.extend(store.iter().flat_map(|one| one.insts));
943 for &arm in shape.arms.iter().flatten() {
944 for inst in func.insts(arm).collect::<Vec<Inst>>() {
945 if func.is_terminator(inst) {
946 continue;
947 }
948 func.remove_inst(inst);
949 // A factored operation is not moved, it is replaced. One copy of it is written below,
950 // after the selects it reads, and these two are what that copy is instead of.
951 if !dropped.contains(&inst) {
952 func.append_inst(shape.head, inst);
953 }
954 }
955 }
956 let mut build = Builder::new(func, shape.head).at(span);
957 let mut args = Vec::with_capacity(shape.args[0].len());
958 for (index, (&then, &other)) in shape.args[0].iter().zip(&shape.args[1]).enumerate() {
959 // A value the condition settled, passed on as it is. The side named is the one whose value
960 // is right on both edges, which is the side the two were not shown to be equal on.
961 if let Some(side) = implied.get(index).copied().flatten() {
962 args.push(shape.args[side][index]);
963 continue;
964 }
965 if let Some(one) = &plan[index] {
966 let mut operands = one.operands.clone();
967 if let Some((at, sides)) = one.differ {
968 operands[at] = build.select(shape.cond, sides[0], sides[1]);
969 }
970 let list = build.func().push_values(&operands);
971 args.push(build.value(InstData { args: list, ..one.data }, one.ty));
972 continue;
973 }
974 // The condition holds on the first side, which is the side `select` takes when the bit is
975 // one, so the order the branch named its targets in is the order the arguments go in.
976 let same = agree(build.func(), then, other);
977 args.push(if same { then } else { build.select(shape.cond, then, other) });
978 }
979 // After everything the arms were doing has moved, because the value being stored is often one
980 // of the things they were working out, and before the jump because the jump is the terminator.
981 if let Some(one) = store {
982 let [then, other] = one.values;
983 let same = agree(build.func(), then, other);
984 let what = if same { then } else { build.select(shape.cond, then, other) };
985 let list = build.func().push_values(&[what, one.addr]);
986 build.inst(InstData { args: list, ..one.data }, &[]);
987 }
988 build.jump(shape.join, &args);
989 // Nothing arrives at the arms now, and section 6.5 makes taking an unreachable block out the
990 // standing obligation of whichever pass stranded it rather than something the next pass tidies
991 // up. The verifier holds every pass to that.
992 for &arm in shape.arms.iter().flatten() {
993 func.remove_block(arm);
994 }
995}
996
997#[cfg(test)]
998mod tests {
999 use rucc_base::Interner;
1000 use rucc_ir::{
1001 Block, Builder, Flags, Float, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict,
1002 Signature, Type, Value,
1003 };
1004
1005 use super::PhiOpt;
1006 use crate::profile::{Probability, Quality};
1007 use crate::stats::Kind;
1008 use crate::{Fuel, Pass, Stats};
1009
1010 /// Runs the pass with as much fuel as it wants.
1011 fn phiopt(func: &mut Func) -> Stats {
1012 PhiOpt.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1013 }
1014
1015 /// The blocks the function still has, by number.
1016 fn blocks(func: &Func) -> Vec<usize> {
1017 func.blocks().map(Block::index).collect()
1018 }
1019
1020 /// Where a block's terminator goes, as block numbers.
1021 fn goes_to(func: &Func, block: usize) -> Vec<usize> {
1022 let block = Block::from_usize(block);
1023 let term = func.terminator(block).expect("every block here has one");
1024 func.successors(term).map(|call| call.block.index()).collect()
1025 }
1026
1027 /// The opcodes a block holds, in order.
1028 fn opcodes(func: &Func, block: usize) -> Vec<Opcode> {
1029 let block = Block::from_usize(block);
1030 func.insts(block).map(|inst| func[inst].opcode).collect()
1031 }
1032
1033 /// What a block's terminator carries on its first edge.
1034 fn carries(func: &Func, block: usize) -> Vec<Value> {
1035 let block = Block::from_usize(block);
1036 let term = func.terminator(block).expect("every block here has one");
1037 let call = func.successors(term).next().expect("a terminator here has an edge");
1038 func[call.args].to_vec()
1039 }
1040
1041 /// Four aligned bytes, ordinary, with nothing known about aliasing.
1042 fn plain() -> MemInfo {
1043 MemInfo {
1044 size: 4,
1045 align: 4,
1046 order: MemOrder::NotAtomic,
1047 tbaa: None,
1048 owns: 0,
1049 restrict: Restrict::NONE,
1050 }
1051 }
1052
1053 /// A store, which is the instruction used here whenever something has to happen.
1054 fn store_something(build: &mut Builder<'_>) {
1055 let what = build.iconst(Type::int(32), 7);
1056 let address = build.iconst(Type::int(64), 16);
1057 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
1058 build.store(what, address, plain(), Flags::NONE);
1059 }
1060
1061 /// `if (x < 0) *p = a; else *p = b;`, with both stores told the same thing about the access.
1062 ///
1063 /// The address is a function parameter, so it is one value both arms name, and the two values
1064 /// written are the other two parameters. Block 0 is the head, blocks 1 and 2 are the arms and
1065 /// block 3 is the join, which takes nothing and returns.
1066 fn both_arms_store(info: MemInfo, flags: [Flags; 2], addresses: bool) -> Func {
1067 let mut names = Interner::new();
1068 let ints = [Type::PTR, Type::int(32), Type::int(32), Type::PTR];
1069 let signature = Signature::new().with_params(&ints);
1070 let mut func = Func::new(names.intern("f"), signature);
1071 let head = func.create_block();
1072 let address = func.append_param(head, Type::PTR);
1073 let written =
1074 [func.append_param(head, Type::int(32)), func.append_param(head, Type::int(32))];
1075 let elsewhere = func.append_param(head, Type::PTR);
1076 let arms = [func.create_block(), func.create_block()];
1077 let join = func.create_block();
1078
1079 let mut build = Builder::new(&mut func, head);
1080 let zero = build.iconst(Type::int(32), 0);
1081 let test = build.icmp(IntPred::Slt, written[0], zero);
1082 build.br_if(test, arms[0], &[], arms[1], &[]);
1083 for (index, arm) in arms.iter().enumerate() {
1084 let mut build = Builder::new(&mut func, *arm);
1085 let where_to = if addresses && index == 1 { elsewhere } else { address };
1086 build.store(written[index], where_to, info, flags[index]);
1087 build.jump(join, &[]);
1088 }
1089 let mut build = Builder::new(&mut func, join);
1090 build.ret(&[]);
1091 func
1092 }
1093
1094 /// `x < y ? a : b`, as a diamond whose two arms are empty.
1095 ///
1096 /// Block 0 is the head and takes the two values it compares as function parameters, blocks 1
1097 /// and 2 are the arms and carry one of two constants, and block 3 is the join and returns what
1098 /// it was given.
1099 fn empty_arms() -> Func {
1100 let mut names = Interner::new();
1101 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
1102 let mut func = Func::new(names.intern("f"), signature);
1103 let head = func.create_block();
1104 let left = func.append_param(head, Type::int(32));
1105 let right = func.append_param(head, Type::int(32));
1106 let arms = [func.create_block(), func.create_block()];
1107 let join = func.create_block();
1108 let param = func.append_param(join, Type::int(32));
1109
1110 let mut build = Builder::new(&mut func, head);
1111 let test = build.icmp(IntPred::Slt, left, right);
1112 build.br_if(test, arms[0], &[], arms[1], &[]);
1113 for (arm, value) in arms.iter().zip([1, 2]) {
1114 let mut build = Builder::new(&mut func, *arm);
1115 let it = build.iconst(Type::int(32), value);
1116 build.jump(join, &[it]);
1117 }
1118 let mut build = Builder::new(&mut func, join);
1119 build.ret(&[param]);
1120 func
1121 }
1122
1123 #[test]
1124 fn a_branch_that_is_already_decided_is_left_for_simplify_cfg() {
1125 // What `if (1)` looks like by the time it gets here. Converting would build a select on a
1126 // constant and keep the arm that cannot run, and the pass that would fold it does not
1127 // exist, so the answer is to leave the branch alone and let the arm be deleted whole.
1128 let mut names = Interner::new();
1129 let mut func = Func::new(names.intern("f"), Signature::new());
1130 let head = func.create_block();
1131 let arms = [func.create_block(), func.create_block()];
1132 let join = func.create_block();
1133 let param = func.append_param(join, Type::int(32));
1134
1135 let mut build = Builder::new(&mut func, head);
1136 // What `if (1)` reaches this pass as. Not a constant, a comparison of two constants, since
1137 // `fold` will not turn an `icmp` into an `i1` that nothing lowers.
1138 let one = build.iconst(Type::int(32), 1);
1139 let zero = build.iconst(Type::int(32), 0);
1140 let test = build.icmp(IntPred::Ne, one, zero);
1141 build.br_if(test, arms[0], &[], arms[1], &[]);
1142 for (arm, value) in arms.iter().zip([1, 2]) {
1143 let mut build = Builder::new(&mut func, *arm);
1144 let it = build.iconst(Type::int(32), value);
1145 build.jump(join, &[it]);
1146 }
1147 let mut build = Builder::new(&mut func, join);
1148 build.ret(&[param]);
1149
1150 let stats = phiopt(&mut func);
1151 assert_eq!(stats.count(Kind::Missed, super::CONDITION_IS_DECIDED), 1);
1152 assert_eq!(blocks(&func), vec![0, 1, 2, 3]);
1153 }
1154
1155 #[test]
1156 fn a_diamond_whose_arms_are_empty_becomes_a_select() {
1157 let mut func = empty_arms();
1158 let stats = phiopt(&mut func);
1159 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1160 // The two constants moved up with the arms, and the select is what the branch was.
1161 assert_eq!(
1162 opcodes(&func, 0),
1163 vec![Opcode::ICmp, Opcode::IConst, Opcode::IConst, Opcode::Select, Opcode::Jump]
1164 );
1165 assert_eq!(goes_to(&func, 0), vec![3]);
1166 assert_eq!(blocks(&func), vec![0, 3]);
1167 }
1168
1169 #[test]
1170 fn the_side_the_condition_holds_on_is_the_side_the_select_takes_first() {
1171 let mut func = empty_arms();
1172 phiopt(&mut func);
1173 let select = func
1174 .insts(Block::from_usize(0))
1175 .find(|&inst| func[inst].opcode == Opcode::Select)
1176 .expect("the select the pass just built");
1177 let args = func[func[select].args].to_vec();
1178 let one = crate::fold::constant(&func, args[1]).expect("the true arm carried a constant");
1179 let two = crate::fold::constant(&func, args[2]).expect("the false arm carried a constant");
1180 assert_eq!(one.0.unsigned(), 1, "the arm the branch named first");
1181 assert_eq!(two.0.unsigned(), 2, "the arm the branch named second");
1182 }
1183
1184 /// A triangle: one side goes straight to the join carrying what it already had.
1185 #[test]
1186 fn a_triangle_whose_empty_side_goes_straight_to_the_join_is_converted() {
1187 let mut names = Interner::new();
1188 let signature = Signature::new().with_params(&[Type::int(32)]);
1189 let mut func = Func::new(names.intern("f"), signature);
1190 let head = func.create_block();
1191 let outside = func.append_param(head, Type::int(32));
1192 let arm = func.create_block();
1193 let join = func.create_block();
1194 let param = func.append_param(join, Type::int(32));
1195
1196 let mut build = Builder::new(&mut func, head);
1197 let zero = build.iconst(Type::int(32), 0);
1198 let test = build.icmp(IntPred::Slt, outside, zero);
1199 build.br_if(test, arm, &[], join, &[outside]);
1200 let mut build = Builder::new(&mut func, arm);
1201 let it = build.iconst(Type::int(32), 0);
1202 build.jump(join, &[it]);
1203 let mut build = Builder::new(&mut func, join);
1204 build.ret(&[param]);
1205
1206 let stats = phiopt(&mut func);
1207 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1208 assert_eq!(blocks(&func), vec![0, 2]);
1209 assert_eq!(goes_to(&func, 0), vec![2]);
1210 assert_eq!(opcodes(&func, 0).last(), Some(&Opcode::Jump));
1211 }
1212
1213 #[test]
1214 fn a_parameter_both_sides_agree_about_needs_no_select() {
1215 let mut names = Interner::new();
1216 let signature = Signature::new().with_params(&[Type::int(32)]);
1217 let mut func = Func::new(names.intern("f"), signature);
1218 let head = func.create_block();
1219 let outside = func.append_param(head, Type::int(32));
1220 let arms = [func.create_block(), func.create_block()];
1221 let join = func.create_block();
1222 let param = func.append_param(join, Type::int(32));
1223
1224 let mut build = Builder::new(&mut func, head);
1225 let zero = build.iconst(Type::int(32), 0);
1226 let test = build.icmp(IntPred::Slt, outside, zero);
1227 build.br_if(test, arms[0], &[], arms[1], &[]);
1228 for arm in arms {
1229 let mut build = Builder::new(&mut func, arm);
1230 build.jump(join, &[outside]);
1231 }
1232 let mut build = Builder::new(&mut func, join);
1233 build.ret(&[param]);
1234
1235 let stats = phiopt(&mut func);
1236 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1237 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried the same value");
1238 assert_eq!(carries(&func, 0), vec![outside]);
1239 }
1240
1241 #[test]
1242 fn two_sides_carrying_the_same_number_need_no_select_either() {
1243 // `x ? 7 : 7`, which the corpus has eight of. The two sevens are two values, because
1244 // nothing has hash consed them into one, so asking only whether the values are equal
1245 // builds a select between two sevens and pays a compare and a conditional move for it.
1246 let mut names = Interner::new();
1247 let signature = Signature::new().with_params(&[Type::int(32)]);
1248 let mut func = Func::new(names.intern("f"), signature);
1249 let head = func.create_block();
1250 let outside = func.append_param(head, Type::int(32));
1251 let arms = [func.create_block(), func.create_block()];
1252 let join = func.create_block();
1253 let param = func.append_param(join, Type::int(32));
1254
1255 let mut build = Builder::new(&mut func, head);
1256 let zero = build.iconst(Type::int(32), 0);
1257 let test = build.icmp(IntPred::Slt, outside, zero);
1258 build.br_if(test, arms[0], &[], arms[1], &[]);
1259 for arm in arms {
1260 let mut build = Builder::new(&mut func, arm);
1261 let seven = build.iconst(Type::int(32), 7);
1262 build.jump(join, &[seven]);
1263 }
1264 let mut build = Builder::new(&mut func, join);
1265 build.ret(&[param]);
1266
1267 let stats = phiopt(&mut func);
1268 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1269 assert!(!opcodes(&func, 0).contains(&Opcode::Select), "both sides carried a seven");
1270 }
1271
1272 #[test]
1273 fn two_sides_carrying_different_numbers_still_get_a_select() {
1274 let mut func = empty_arms();
1275 let stats = phiopt(&mut func);
1276 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1277 assert!(opcodes(&func, 0).contains(&Opcode::Select), "one and two are not the same number");
1278 }
1279
1280 /// `x = (a == b) ? b : a`, as the diamond it arrives here as.
1281 ///
1282 /// Block 0 is the head and takes the two values, blocks 1 and 2 are the arms and each carries
1283 /// one of them to block 3, which returns what it was given. The comparison's predicate and the
1284 /// type of the two values are what the tests below vary.
1285 fn condition_settles_it(pred: IntPred, ty: Type) -> Func {
1286 let mut names = Interner::new();
1287 let signature = Signature::new().with_params(&[ty, ty]);
1288 let mut func = Func::new(names.intern("f"), signature);
1289 let head = func.create_block();
1290 let left = func.append_param(head, ty);
1291 let right = func.append_param(head, ty);
1292 let arms = [func.create_block(), func.create_block()];
1293 let join = func.create_block();
1294 let param = func.append_param(join, ty);
1295
1296 let mut build = Builder::new(&mut func, head);
1297 let test = build.icmp(pred, left, right);
1298 build.br_if(test, arms[0], &[], arms[1], &[]);
1299 // The side taken when the condition holds carries the right hand value, the other side
1300 // carries the left, which is what makes the two the same number on one edge and not the
1301 // other. Which side that is follows the predicate.
1302 for (arm, value) in arms.iter().zip([right, left]) {
1303 let mut build = Builder::new(&mut func, *arm);
1304 build.jump(join, &[value]);
1305 }
1306 let mut build = Builder::new(&mut func, join);
1307 build.ret(&[param]);
1308 func
1309 }
1310
1311 #[test]
1312 fn a_value_the_condition_says_is_the_other_one_needs_no_select() {
1313 let mut func = condition_settles_it(IntPred::Eq, Type::int(32));
1314 let stats = phiopt(&mut func);
1315 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1316 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1317 assert_eq!(opcodes(&func, 0), vec![Opcode::ICmp, Opcode::Jump]);
1318 // The value passed on is the one carried by the side the two were not shown equal on.
1319 let params = func[Block::from_usize(0)].params.to_vec();
1320 assert_eq!(carries(&func, 0), vec![params[0]]);
1321 assert_eq!(blocks(&func), vec![0, 3]);
1322 }
1323
1324 /// The same with the branch the other way round, where the equal side is the one not taken.
1325 #[test]
1326 fn an_inequality_settles_it_from_the_other_side() {
1327 let mut func = condition_settles_it(IntPred::Ne, Type::int(32));
1328 let stats = phiopt(&mut func);
1329 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1330 let params = func[Block::from_usize(0)].params.to_vec();
1331 assert_eq!(carries(&func, 0), vec![params[1]]);
1332 }
1333
1334 /// A pointer has no `select`, and a value nothing has to choose between does not need one.
1335 #[test]
1336 fn a_value_of_a_type_with_no_select_is_still_settled_by_the_condition() {
1337 let mut func = condition_settles_it(IntPred::Eq, Type::PTR);
1338 let stats = phiopt(&mut func);
1339 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 0);
1340 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 1);
1341 assert_eq!(opcodes(&func, 0), vec![Opcode::ICmp, Opcode::Jump]);
1342 }
1343
1344 /// A branch on anything but an equality is not asked about, and the select is written as usual.
1345 #[test]
1346 fn a_branch_that_is_not_an_equality_gets_its_select() {
1347 let mut func = condition_settles_it(IntPred::Slt, Type::int(32));
1348 let stats = phiopt(&mut func);
1349 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 0);
1350 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1351 assert!(opcodes(&func, 0).contains(&Opcode::Select));
1352 }
1353
1354 /// Two constants that are not the same number are not the same number on any edge.
1355 #[test]
1356 fn two_different_constants_are_not_asked_about() {
1357 let mut func = empty_arms();
1358 let stats = phiopt(&mut func);
1359 assert_eq!(stats.count(Kind::Optimized, super::VALUE_IMPLIED), 0);
1360 assert!(opcodes(&func, 0).contains(&Opcode::Select));
1361 }
1362
1363 #[test]
1364 fn a_store_both_arms_make_to_one_place_is_made_once_below_the_branch() {
1365 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1366 let stats = phiopt(&mut func);
1367 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1368 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1369 // One store, below the select that chooses what it writes, and no branch above either.
1370 assert_eq!(
1371 opcodes(&func, 0),
1372 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Store, Opcode::Jump]
1373 );
1374 assert_eq!(blocks(&func), vec![0, 3]);
1375 assert_eq!(goes_to(&func, 0), vec![3]);
1376 }
1377
1378 #[test]
1379 fn the_one_store_writes_what_the_side_the_condition_holds_on_was_writing() {
1380 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1381 phiopt(&mut func);
1382 let head = Block::from_usize(0);
1383 let select = func
1384 .insts(head)
1385 .find(|&inst| func[inst].opcode == Opcode::Select)
1386 .expect("the select the pass just built");
1387 let store = func
1388 .insts(head)
1389 .find(|&inst| func[inst].opcode == Opcode::Store)
1390 .expect("the one store that is left");
1391 let chosen = func[func[select].args].to_vec();
1392 let written = func[func[store].args].to_vec();
1393 // The head's parameters in order: the address, then what each side writes.
1394 let params = func[head].params.to_vec();
1395 assert_eq!(chosen[1], params[1], "the arm the branch named first");
1396 assert_eq!(chosen[2], params[2], "the arm the branch named second");
1397 assert_eq!(written[0], func[select].first_result.expect("a select produces one value"));
1398 assert_eq!(written[1], params[0], "the address both arms named");
1399 }
1400
1401 /// Both arms writing the same value needs no select, only the one store.
1402 #[test]
1403 fn two_arms_that_write_the_same_thing_get_a_store_and_no_select() {
1404 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1405 // Point the second arm's store at the first arm's value, which is what the front end
1406 // produces when both branches of a conditional assign the same thing.
1407 let head = Block::from_usize(0);
1408 let params = func[head].params.to_vec();
1409 let store = func
1410 .insts(Block::from_usize(2))
1411 .find(|&inst| func[inst].opcode == Opcode::Store)
1412 .expect("the second arm's store");
1413 let args = func.push_values(&[params[1], params[0]]);
1414 func[store].args = args;
1415
1416 let stats = phiopt(&mut func);
1417 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1418 assert_eq!(
1419 opcodes(&func, 0),
1420 vec![Opcode::IConst, Opcode::ICmp, Opcode::Store, Opcode::Jump]
1421 );
1422 }
1423
1424 /// Two stores to two different places is two writes, and doing both is writing one of them twice.
1425 #[test]
1426 fn two_arms_that_store_to_different_addresses_keep_their_branch() {
1427 let mut func = both_arms_store(plain(), [Flags::NONE; 2], true);
1428 let stats = phiopt(&mut func);
1429 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1430 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1431 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1432 }
1433
1434 #[test]
1435 fn a_volatile_store_keeps_its_branch_even_when_both_arms_make_it() {
1436 let mut func = both_arms_store(plain(), [Flags::VOLATILE; 2], false);
1437 let stats = phiopt(&mut func);
1438 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1439 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1440 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1441 }
1442
1443 #[test]
1444 fn an_atomic_store_keeps_its_branch_even_when_both_arms_make_it() {
1445 let mut func = both_arms_store(
1446 MemInfo { order: MemOrder::SeqCst, ..plain() },
1447 [Flags::NONE; 2],
1448 false,
1449 );
1450 let stats = phiopt(&mut func);
1451 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1452 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1453 }
1454
1455 /// Two stores told different things about the access have no one answer to carry downward.
1456 #[test]
1457 fn two_stores_that_disagree_about_the_access_keep_their_branch() {
1458 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1459 let store = func
1460 .insts(Block::from_usize(2))
1461 .find(|&inst| func[inst].opcode == Opcode::Store)
1462 .expect("the second arm's store");
1463 let mem = func.add_mem(MemInfo { align: 1, ..plain() });
1464 func[store].extra = rucc_ir::Extra::Mem(mem);
1465
1466 let stats = phiopt(&mut func);
1467 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 0);
1468 assert_eq!(stats.count(Kind::Missed, super::STORES_DO_NOT_MATCH), 1);
1469 }
1470
1471 /// The store comes off the work count, because one of the two arms was always going to make it.
1472 ///
1473 /// Each arm here holds the store and as much other work as the rule allows, so counting the
1474 /// store as work would put both arms one over the limit and the branch would stay.
1475 #[test]
1476 fn a_store_each_way_does_not_count_against_how_long_the_arms_may_be() {
1477 let mut func = both_arms_store(plain(), [Flags::NONE; 2], false);
1478 let params = func[Block::from_usize(0)].params.to_vec();
1479 for arm in [1, 2] {
1480 let block = Block::from_usize(arm);
1481 let term = func.terminator(block).expect("an arm ends in its jump");
1482 func.remove_inst(term);
1483 let mut build = Builder::new(&mut func, block);
1484 let mut value = params[1];
1485 for _ in 0..rucc_cost::heuristics::PHIOPT_ARM_INSTRUCTIONS {
1486 value = build.binary(Opcode::Add, value, params[2], Flags::NONE);
1487 }
1488 func.append_inst(block, term);
1489 }
1490
1491 let stats = phiopt(&mut func);
1492 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
1493 assert_eq!(stats.count(Kind::Optimized, super::STORE_REPLACED), 1);
1494 }
1495
1496 /// A store one side makes and the other does not, which is the transformation with no proof.
1497 #[test]
1498 fn an_arm_that_stores_where_the_other_does_not_keeps_its_branch() {
1499 let mut names = Interner::new();
1500 let signature = Signature::new().with_params(&[Type::int(32)]);
1501 let mut func = Func::new(names.intern("f"), signature);
1502 let head = func.create_block();
1503 let outside = func.append_param(head, Type::int(32));
1504 let arms = [func.create_block(), func.create_block()];
1505 let join = func.create_block();
1506 let param = func.append_param(join, Type::int(32));
1507
1508 let mut build = Builder::new(&mut func, head);
1509 let zero = build.iconst(Type::int(32), 0);
1510 let test = build.icmp(IntPred::Slt, outside, zero);
1511 build.br_if(test, arms[0], &[], arms[1], &[]);
1512 let mut build = Builder::new(&mut func, arms[0]);
1513 store_something(&mut build);
1514 let it = build.iconst(Type::int(32), 1);
1515 build.jump(join, &[it]);
1516 let mut build = Builder::new(&mut func, arms[1]);
1517 let it = build.iconst(Type::int(32), 2);
1518 build.jump(join, &[it]);
1519 let mut build = Builder::new(&mut func, join);
1520 build.ret(&[param]);
1521
1522 let stats = phiopt(&mut func);
1523 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1524 assert_eq!(stats.count(Kind::Missed, super::STORE_ON_ONE_PATH), 1);
1525 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1526 }
1527
1528 /// A load, which is the effect that is not a store and gets the general answer.
1529 #[test]
1530 fn an_arm_that_does_something_else_keeps_its_branch() {
1531 let mut names = Interner::new();
1532 let signature = Signature::new().with_params(&[Type::int(32)]);
1533 let mut func = Func::new(names.intern("f"), signature);
1534 let head = func.create_block();
1535 let outside = func.append_param(head, Type::int(32));
1536 let arms = [func.create_block(), func.create_block()];
1537 let join = func.create_block();
1538 let param = func.append_param(join, Type::int(32));
1539
1540 let mut build = Builder::new(&mut func, head);
1541 let zero = build.iconst(Type::int(32), 0);
1542 let test = build.icmp(IntPred::Slt, outside, zero);
1543 build.br_if(test, arms[0], &[], arms[1], &[]);
1544 let mut build = Builder::new(&mut func, arms[0]);
1545 let address = build.iconst(Type::int(64), 16);
1546 let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
1547 let it = build.load(Type::int(32), address, plain(), Flags::NONE);
1548 build.jump(join, &[it]);
1549 let mut build = Builder::new(&mut func, arms[1]);
1550 let it = build.iconst(Type::int(32), 2);
1551 build.jump(join, &[it]);
1552 let mut build = Builder::new(&mut func, join);
1553 build.ret(&[param]);
1554
1555 let stats = phiopt(&mut func);
1556 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1557 assert_eq!(stats.count(Kind::Missed, super::ARM_HAS_EFFECTS), 1);
1558 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1559 }
1560
1561 /// A division whose divisor is not known cannot be moved onto the path that skipped it.
1562 #[test]
1563 fn an_arm_that_divides_by_something_unknown_keeps_its_branch() {
1564 let mut names = Interner::new();
1565 let signature = Signature::new().with_params(&[Type::int(32), Type::int(32)]);
1566 let mut func = Func::new(names.intern("f"), signature);
1567 let head = func.create_block();
1568 let left = func.append_param(head, Type::int(32));
1569 let right = func.append_param(head, Type::int(32));
1570 let arms = [func.create_block(), func.create_block()];
1571 let join = func.create_block();
1572 let param = func.append_param(join, Type::int(32));
1573
1574 let mut build = Builder::new(&mut func, head);
1575 let zero = build.iconst(Type::int(32), 0);
1576 let test = build.icmp(IntPred::Ne, right, zero);
1577 build.br_if(test, arms[0], &[], arms[1], &[]);
1578 let mut build = Builder::new(&mut func, arms[0]);
1579 let it = build.binary(Opcode::SDiv, left, right, Flags::NONE);
1580 build.jump(join, &[it]);
1581 let mut build = Builder::new(&mut func, arms[1]);
1582 let it = build.iconst(Type::int(32), 0);
1583 build.jump(join, &[it]);
1584 let mut build = Builder::new(&mut func, join);
1585 build.ret(&[param]);
1586
1587 let stats = phiopt(&mut func);
1588 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1589 assert_eq!(stats.count(Kind::Missed, super::ARM_MAY_TRAP), 1);
1590 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1591 }
1592
1593 #[test]
1594 fn a_division_by_a_constant_that_is_not_zero_or_minus_one_is_moved() {
1595 let mut names = Interner::new();
1596 let signature = Signature::new().with_params(&[Type::int(32)]);
1597 let mut func = Func::new(names.intern("f"), signature);
1598 let head = func.create_block();
1599 let outside = func.append_param(head, Type::int(32));
1600 let arms = [func.create_block(), func.create_block()];
1601 let join = func.create_block();
1602 let param = func.append_param(join, Type::int(32));
1603
1604 let mut build = Builder::new(&mut func, head);
1605 let zero = build.iconst(Type::int(32), 0);
1606 let test = build.icmp(IntPred::Slt, outside, zero);
1607 build.br_if(test, arms[0], &[], arms[1], &[]);
1608 let mut build = Builder::new(&mut func, arms[0]);
1609 let three = build.iconst(Type::int(32), 3);
1610 let it = build.binary(Opcode::SDiv, outside, three, Flags::NONE);
1611 build.jump(join, &[it]);
1612 let mut build = Builder::new(&mut func, arms[1]);
1613 let it = build.iconst(Type::int(32), 0);
1614 build.jump(join, &[it]);
1615 let mut build = Builder::new(&mut func, join);
1616 build.ret(&[param]);
1617
1618 let stats = phiopt(&mut func);
1619 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1620 assert!(opcodes(&func, 0).contains(&Opcode::SDiv));
1621 }
1622
1623 /// Nothing chooses between two pointers, so the shape is matched and then left alone.
1624 #[test]
1625 fn a_value_no_select_is_lowered_for_keeps_its_branch() {
1626 let mut names = Interner::new();
1627 let signature = Signature::new().with_params(&[Type::int(32)]);
1628 let mut func = Func::new(names.intern("f"), signature);
1629 let head = func.create_block();
1630 let outside = func.append_param(head, Type::int(32));
1631 let arms = [func.create_block(), func.create_block()];
1632 let join = func.create_block();
1633 func.append_param(join, Type::PTR);
1634
1635 let mut build = Builder::new(&mut func, head);
1636 let zero = build.iconst(Type::int(32), 0);
1637 let test = build.icmp(IntPred::Slt, outside, zero);
1638 build.br_if(test, arms[0], &[], arms[1], &[]);
1639 for (arm, value) in arms.iter().zip([16, 32]) {
1640 let mut build = Builder::new(&mut func, *arm);
1641 let it = build.iconst(Type::int(64), value);
1642 let it = build.unary(Opcode::IntToPtr, it, Type::PTR);
1643 build.jump(join, &[it]);
1644 }
1645 let mut build = Builder::new(&mut func, join);
1646 build.ret(&[]);
1647
1648 let stats = phiopt(&mut func);
1649 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1650 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
1651 }
1652
1653 /// And nothing chooses between two floats either, at any format.
1654 ///
1655 /// Worth its own test although the answer is the pointer one's, because this is the pass that
1656 /// decides it for every float in the language and `crates/rucc-codegen/src/quad.rs` says so in
1657 /// its own documentation: a conditional expression over two quads is a branch and a phi and
1658 /// stays one, so the back end never sees a `select` at that format and needs no lowering for
1659 /// one. A quad here rather than a `double` since the quad is the format with no register of its
1660 /// own arithmetic, which makes it the one a later change is most likely to reach for.
1661 #[test]
1662 fn a_choice_between_two_quads_keeps_its_branch_as_well() {
1663 let mut names = Interner::new();
1664 let quad = Type::float(Float::F128);
1665 let signature = Signature::new().with_params(&[Type::int(32)]);
1666 let mut func = Func::new(names.intern("f"), signature);
1667 let head = func.create_block();
1668 let outside = func.append_param(head, Type::int(32));
1669 let arms = [func.create_block(), func.create_block()];
1670 let join = func.create_block();
1671 func.append_param(join, quad);
1672
1673 let mut build = Builder::new(&mut func, head);
1674 let zero = build.iconst(Type::int(32), 0);
1675 let test = build.icmp(IntPred::Slt, outside, zero);
1676 build.br_if(test, arms[0], &[], arms[1], &[]);
1677 for (arm, bits) in arms.iter().zip([0x3fff_u128 << 112, 0x4000_u128 << 112]) {
1678 let mut build = Builder::new(&mut func, *arm);
1679 let it = build.fconst(quad, bits);
1680 build.jump(join, &[it]);
1681 }
1682 let mut build = Builder::new(&mut func, join);
1683 build.ret(&[]);
1684
1685 let stats = phiopt(&mut func);
1686 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1687 assert_eq!(stats.count(Kind::Missed, super::NO_SELECT_AT_THAT_WIDTH), 1);
1688 }
1689
1690 #[test]
1691 fn arms_with_more_work_in_them_than_the_budget_keep_their_branch() {
1692 let mut names = Interner::new();
1693 let signature = Signature::new().with_params(&[Type::int(32)]);
1694 let mut func = Func::new(names.intern("f"), signature);
1695 let head = func.create_block();
1696 let outside = func.append_param(head, Type::int(32));
1697 let arms = [func.create_block(), func.create_block()];
1698 let join = func.create_block();
1699 let param = func.append_param(join, Type::int(32));
1700
1701 let mut build = Builder::new(&mut func, head);
1702 let zero = build.iconst(Type::int(32), 0);
1703 let test = build.icmp(IntPred::Slt, outside, zero);
1704 build.br_if(test, arms[0], &[], arms[1], &[]);
1705 let mut build = Builder::new(&mut func, arms[0]);
1706 // Four instructions, which is past the budget however cheap each of them is.
1707 let mut it = outside;
1708 for _ in 0..4 {
1709 it = build.binary(Opcode::Add, it, outside, Flags::NONE);
1710 }
1711 build.jump(join, &[it]);
1712 let mut build = Builder::new(&mut func, arms[1]);
1713 let it = build.iconst(Type::int(32), 0);
1714 build.jump(join, &[it]);
1715 let mut build = Builder::new(&mut func, join);
1716 build.ret(&[param]);
1717
1718 let stats = phiopt(&mut func);
1719 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1720 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 1);
1721 }
1722
1723 /// The margin, at the two ends of it and just outside.
1724 ///
1725 /// A pass level test of the refusal it guards is not written, and the module doc says why: a
1726 /// diamond is the one shape none of document 11's one sided predictors can key on, so every
1727 /// branch this pass matches comes back even until `__builtin_expect` is wired through the
1728 /// front end. The arithmetic is what there is to check today.
1729 #[test]
1730 fn the_margin_is_a_quarter_in_from_each_end() {
1731 let guessed = |percent: u32| Probability::percent(percent, Quality::Guessed);
1732 assert!(super::unpredictable(Probability::even()));
1733 assert!(super::unpredictable(guessed(25)));
1734 assert!(super::unpredictable(guessed(75)));
1735 assert!(!super::unpredictable(guessed(24)));
1736 assert!(!super::unpredictable(guessed(76)));
1737 assert!(!super::unpredictable(Probability::always()));
1738 assert!(!super::unpredictable(Probability::never()));
1739 }
1740
1741 #[test]
1742 fn an_arm_that_two_edges_reach_is_not_an_arm() {
1743 let mut names = Interner::new();
1744 let signature = Signature::new().with_params(&[Type::int(32)]);
1745 let mut func = Func::new(names.intern("f"), signature);
1746 let head = func.create_block();
1747 let outside = func.append_param(head, Type::int(32));
1748 let above = func.create_block();
1749 let arms = [func.create_block(), func.create_block()];
1750 let join = func.create_block();
1751 let param = func.append_param(join, Type::int(32));
1752
1753 // The entry reaches the first arm as well as the head does, so moving the arm's work into
1754 // the head would leave the entry's path without it.
1755 let mut build = Builder::new(&mut func, head);
1756 let zero = build.iconst(Type::int(32), 0);
1757 let first = build.icmp(IntPred::Slt, outside, zero);
1758 build.br_if(first, above, &[], arms[0], &[]);
1759 let mut build = Builder::new(&mut func, above);
1760 let one = build.iconst(Type::int(32), 1);
1761 let second = build.icmp(IntPred::Slt, outside, one);
1762 build.br_if(second, arms[0], &[], arms[1], &[]);
1763 for (arm, value) in arms.iter().zip([1, 2]) {
1764 let mut build = Builder::new(&mut func, *arm);
1765 let it = build.iconst(Type::int(32), value);
1766 build.jump(join, &[it]);
1767 }
1768 let mut build = Builder::new(&mut func, join);
1769 build.ret(&[param]);
1770
1771 let stats = phiopt(&mut func);
1772 // Neither branch is a diamond. The head's first side goes to a block that is not the join
1773 // and is not an arm either, since two edges reach it, and the second branch's first side
1774 // is the same block for the same reason.
1775 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1776 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1777 assert_eq!(goes_to(&func, 1), vec![2, 3]);
1778 }
1779
1780 #[test]
1781 fn fuel_stops_the_conversion_where_it_stands() {
1782 let mut func = empty_arms();
1783 let mut fuel = Fuel::of(0);
1784 let stats = PhiOpt.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
1785 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 0);
1786 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1787 assert_eq!(goes_to(&func, 0), vec![1, 2]);
1788 }
1789
1790 /// `x < y ? f(a, k) : f(b, k)`, as a diamond whose two arms do the same thing to different
1791 /// operands.
1792 ///
1793 /// Block 0 is the head, taking the two values it compares and the two operands and working out
1794 /// the operand both arms share. Blocks 1 and 2 are the arms, each applying every one of
1795 /// `steps` to its own operand and that shared value, and block 3 is the join, taking one
1796 /// parameter for each of them.
1797 fn same_operation(steps: &[Opcode]) -> Func {
1798 let mut names = Interner::new();
1799 let int = Type::int(32);
1800 let signature = Signature::new().with_params(&[int, int, int, int]);
1801 let mut func = Func::new(names.intern("f"), signature);
1802 let head = func.create_block();
1803 let left = func.append_param(head, int);
1804 let right = func.append_param(head, int);
1805 let operands = [func.append_param(head, int), func.append_param(head, int)];
1806 let arms = [func.create_block(), func.create_block()];
1807 let join = func.create_block();
1808 let params: Vec<Value> = steps.iter().map(|_| func.append_param(join, int)).collect();
1809
1810 let mut build = Builder::new(&mut func, head);
1811 // In the head rather than in each arm, so that the two sides share this operand as one
1812 // value. Two arms that each work out their own three are two operations apart, not one.
1813 let shared = build.iconst(int, 3);
1814 let test = build.icmp(IntPred::Slt, left, right);
1815 build.br_if(test, arms[0], &[], arms[1], &[]);
1816 for (&arm, operand) in arms.iter().zip(operands) {
1817 let mut build = Builder::new(&mut func, arm);
1818 let carried: Vec<Value> = steps
1819 .iter()
1820 .map(|&opcode| build.binary(opcode, operand, shared, Flags::default()))
1821 .collect();
1822 build.jump(join, &carried);
1823 }
1824 let mut build = Builder::new(&mut func, join);
1825 build.ret(¶ms);
1826 func
1827 }
1828
1829 /// The transformation. Two adds become one add of a select, rather than one select of two adds.
1830 #[test]
1831 fn an_operation_both_arms_did_is_done_once_below_the_branch() {
1832 let mut func = same_operation(&[Opcode::Add]);
1833 let stats = phiopt(&mut func);
1834 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
1835 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1836 assert_eq!(
1837 opcodes(&func, 0),
1838 vec![Opcode::IConst, Opcode::ICmp, Opcode::Select, Opcode::Add, Opcode::Jump],
1839 "the select chooses the operand and the add happens once"
1840 );
1841 assert_eq!(blocks(&func), vec![0, 3]);
1842 }
1843
1844 /// The select goes under the operation, so what it chooses between is the operands and not the
1845 /// answers. Getting that the wrong way round would be a select of two adds that happens to have
1846 /// the right opcodes in it.
1847 #[test]
1848 fn the_select_chooses_the_operands_and_not_the_answers() {
1849 let mut func = same_operation(&[Opcode::Add]);
1850 phiopt(&mut func);
1851 let head = Block::from_usize(0);
1852 let select = func
1853 .insts(head)
1854 .find(|&inst| func[inst].opcode == Opcode::Select)
1855 .expect("the select the pass just built");
1856 let add = func
1857 .insts(head)
1858 .find(|&inst| func[inst].opcode == Opcode::Add)
1859 .expect("the add the pass just wrote");
1860 let chosen = func[func[select].args].to_vec();
1861 let params = func[head].params.to_vec();
1862 assert_eq!(&chosen[1..], ¶ms[2..], "the two operands the arms differed in");
1863 let added = func[func[add].args].to_vec();
1864 assert_eq!(added[0], func[select].first_result.expect("a select has a result"));
1865 assert_eq!(carries(&func, 0), vec![func[add].first_result.expect("an add has a result")]);
1866 }
1867
1868 /// Nothing is speculated by an operation both arms were doing, so the length rule is about what
1869 /// is left after the factoring rather than about what the arms arrived holding. Three
1870 /// instructions an arm is over the limit, and three instructions that all factor is none.
1871 #[test]
1872 fn arms_that_factor_away_entirely_are_not_too_long() {
1873 let steps = [Opcode::Add, Opcode::Sub, Opcode::Mul];
1874 let mut func = same_operation(&steps);
1875 let stats = phiopt(&mut func);
1876 assert_eq!(stats.count(Kind::Missed, super::ARMS_TOO_LONG), 0);
1877 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 3);
1878 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1879 let written = opcodes(&func, 0);
1880 assert_eq!(written.iter().filter(|&&op| op == Opcode::Select).count(), 3);
1881 for step in steps {
1882 assert_eq!(written.iter().filter(|&&op| op == step).count(), 1, "{step:?} once");
1883 }
1884 }
1885
1886 /// Both arms doing the same thing to the same operands is a common subexpression nothing has
1887 /// numbered, and one copy of it serves both sides with no select at all.
1888 #[test]
1889 fn arms_that_agree_in_every_operand_need_no_select() {
1890 let mut names = Interner::new();
1891 let int = Type::int(32);
1892 let signature = Signature::new().with_params(&[int, int, int]);
1893 let mut func = Func::new(names.intern("f"), signature);
1894 let head = func.create_block();
1895 let left = func.append_param(head, int);
1896 let right = func.append_param(head, int);
1897 let operand = func.append_param(head, int);
1898 let arms = [func.create_block(), func.create_block()];
1899 let join = func.create_block();
1900 let param = func.append_param(join, int);
1901
1902 let mut build = Builder::new(&mut func, head);
1903 let shared = build.iconst(int, 3);
1904 let test = build.icmp(IntPred::Slt, left, right);
1905 build.br_if(test, arms[0], &[], arms[1], &[]);
1906 for &arm in &arms {
1907 let mut build = Builder::new(&mut func, arm);
1908 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
1909 build.jump(join, &[it]);
1910 }
1911 let mut build = Builder::new(&mut func, join);
1912 build.ret(&[param]);
1913
1914 let stats = phiopt(&mut func);
1915 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 1);
1916 assert_eq!(
1917 opcodes(&func, 0),
1918 vec![Opcode::IConst, Opcode::ICmp, Opcode::Add, Opcode::Jump],
1919 "one add and nothing to choose between"
1920 );
1921 }
1922
1923 /// Two different operations are two operations, and the pass falls back to hoisting both and
1924 /// selecting between what they produced.
1925 #[test]
1926 fn arms_that_do_different_things_are_not_factored() {
1927 let mut names = Interner::new();
1928 let int = Type::int(32);
1929 let signature = Signature::new().with_params(&[int, int, int, int]);
1930 let mut func = Func::new(names.intern("f"), signature);
1931 let head = func.create_block();
1932 let left = func.append_param(head, int);
1933 let right = func.append_param(head, int);
1934 let operands = [func.append_param(head, int), func.append_param(head, int)];
1935 let arms = [func.create_block(), func.create_block()];
1936 let join = func.create_block();
1937 let param = func.append_param(join, int);
1938
1939 let mut build = Builder::new(&mut func, head);
1940 let shared = build.iconst(int, 3);
1941 let test = build.icmp(IntPred::Slt, left, right);
1942 build.br_if(test, arms[0], &[], arms[1], &[]);
1943 for ((&arm, operand), opcode) in arms.iter().zip(operands).zip([Opcode::Add, Opcode::Sub]) {
1944 let mut build = Builder::new(&mut func, arm);
1945 let it = build.binary(opcode, operand, shared, Flags::default());
1946 build.jump(join, &[it]);
1947 }
1948 let mut build = Builder::new(&mut func, join);
1949 build.ret(&[param]);
1950
1951 let stats = phiopt(&mut func);
1952 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1953 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1954 assert_eq!(
1955 opcodes(&func, 0),
1956 vec![
1957 Opcode::IConst,
1958 Opcode::ICmp,
1959 Opcode::Add,
1960 Opcode::Sub,
1961 Opcode::Select,
1962 Opcode::Jump
1963 ],
1964 "both operations hoisted and a select between their answers"
1965 );
1966 }
1967
1968 /// Two operand positions apart needs two selects and one operation, which is what one select
1969 /// and two operations already cost, so there is nothing to win and it is left alone.
1970 #[test]
1971 fn arms_that_differ_in_two_operands_are_not_factored() {
1972 let mut names = Interner::new();
1973 let int = Type::int(32);
1974 let signature = Signature::new().with_params(&[int, int, int, int, int, int]);
1975 let mut func = Func::new(names.intern("f"), signature);
1976 let head = func.create_block();
1977 let left = func.append_param(head, int);
1978 let right = func.append_param(head, int);
1979 let first = [func.append_param(head, int), func.append_param(head, int)];
1980 let second = [func.append_param(head, int), func.append_param(head, int)];
1981 let arms = [func.create_block(), func.create_block()];
1982 let join = func.create_block();
1983 let param = func.append_param(join, int);
1984
1985 let mut build = Builder::new(&mut func, head);
1986 let test = build.icmp(IntPred::Slt, left, right);
1987 build.br_if(test, arms[0], &[], arms[1], &[]);
1988 for ((&arm, one), two) in arms.iter().zip(first).zip(second) {
1989 let mut build = Builder::new(&mut func, arm);
1990 let it = build.binary(Opcode::Add, one, two, Flags::default());
1991 build.jump(join, &[it]);
1992 }
1993 let mut build = Builder::new(&mut func, join);
1994 build.ret(&[param]);
1995
1996 let stats = phiopt(&mut func);
1997 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
1998 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
1999 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
2000 }
2001
2002 /// The one copy is written after the arms have gone, so an operation something else in the arm
2003 /// reads cannot be one of the two it replaces. Here each arm hands its answer to the join
2004 /// twice, which is two readers and not one.
2005 #[test]
2006 fn an_operation_read_more_than_once_is_not_factored() {
2007 let mut names = Interner::new();
2008 let int = Type::int(32);
2009 let signature = Signature::new().with_params(&[int, int, int, int]);
2010 let mut func = Func::new(names.intern("f"), signature);
2011 let head = func.create_block();
2012 let left = func.append_param(head, int);
2013 let right = func.append_param(head, int);
2014 let operands = [func.append_param(head, int), func.append_param(head, int)];
2015 let arms = [func.create_block(), func.create_block()];
2016 let join = func.create_block();
2017 let params = [func.append_param(join, int), func.append_param(join, int)];
2018
2019 let mut build = Builder::new(&mut func, head);
2020 let shared = build.iconst(int, 3);
2021 let test = build.icmp(IntPred::Slt, left, right);
2022 build.br_if(test, arms[0], &[], arms[1], &[]);
2023 for (&arm, operand) in arms.iter().zip(operands) {
2024 let mut build = Builder::new(&mut func, arm);
2025 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
2026 build.jump(join, &[it, it]);
2027 }
2028 let mut build = Builder::new(&mut func, join);
2029 build.ret(¶ms);
2030
2031 let stats = phiopt(&mut func);
2032 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
2033 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2034 assert_eq!(opcodes(&func, 0).iter().filter(|&&op| op == Opcode::Add).count(), 2);
2035 }
2036
2037 /// A triangle has a block on one side only, so there is no second operation to pair the first
2038 /// one with and nothing to factor.
2039 #[test]
2040 fn a_triangle_factors_nothing() {
2041 let mut names = Interner::new();
2042 let int = Type::int(32);
2043 let signature = Signature::new().with_params(&[int, int, int]);
2044 let mut func = Func::new(names.intern("f"), signature);
2045 let head = func.create_block();
2046 let left = func.append_param(head, int);
2047 let right = func.append_param(head, int);
2048 let operand = func.append_param(head, int);
2049 let arm = func.create_block();
2050 let join = func.create_block();
2051 let param = func.append_param(join, int);
2052
2053 let mut build = Builder::new(&mut func, head);
2054 let shared = build.iconst(int, 3);
2055 let test = build.icmp(IntPred::Slt, left, right);
2056 build.br_if(test, arm, &[], join, &[operand]);
2057 let mut build = Builder::new(&mut func, arm);
2058 let it = build.binary(Opcode::Add, operand, shared, Flags::default());
2059 build.jump(join, &[it]);
2060 let mut build = Builder::new(&mut func, join);
2061 build.ret(&[param]);
2062
2063 let stats = phiopt(&mut func);
2064 assert_eq!(stats.count(Kind::Optimized, super::FACTORED), 0);
2065 assert_eq!(stats.count(Kind::Optimized, super::CONVERTED), 1);
2066 }
2067}