rucc_opt/number.rs
1//! Two instructions in a block that compute the same thing from the same things are one value.
2//!
3//! Design: `spec/optimizer/16-gvn-and-pre.md` section 16.1. This is the other half of that
4//! document, the half [`crate::load`] deliberately did not do. Section 16.2 is candid that value
5//! numbering over arithmetic is worth less on C than people expect, because the front end does not
6//! generate the same expression twice and the programmer does not write it twice. That is true of
7//! the arithmetic somebody wrote. It is not true of the arithmetic the front end emits underneath
8//! it, and the address of a subscript is the case that matters: `a[i] = v; total += a[i];` is one
9//! subscript written twice in C and two separate runs of the same multiply and add in the IR,
10//! because lowering a subscript does not know it has lowered that subscript already.
11//!
12//! So this pass mostly does not pay for itself in what it removes. It pays for itself in what it
13//! lets the pass after it see. [`crate::load`] compares addresses by identity, so until the store's
14//! address and the load's address have one name it cannot forward a store to the load that reads it
15//! straight back, which is the shape it was written for. Giving them one name is this.
16//!
17//! # Block local, and why that is the whole of it
18//!
19//! One table per block, thrown away at the end of it. Inside a block an earlier instruction
20//! dominates a later one because there is no other way to reach the later one, so program order is
21//! the whole of the dominance question and there is no dominator tree here.
22//!
23//! The version over the dominator tree finds strictly more, and section 16.1 is where the argument
24//! for not writing it lives: under arms B and C of the e-graph experiment, hash-consing gives the
25//! acyclic case for nothing, and what is left over is the cyclic case, which wants Tarjan's
26//! algorithm over the SSA graph and belongs after the e-graph is built rather than before it. What
27//! is wanted before that exists is the part that makes the address of one subscript one value, and
28//! both halves of a subscript are in the block the subscript is in.
29//!
30//! # What counts as the same thing
31//!
32//! The opcode, the flags, the result type, whatever the instruction carries besides its operands,
33//! and the operands. All five, and a difference in any of them is two values.
34//!
35//! The flags are in the key rather than being merged or intersected. Two adds of the same pair
36//! where one of them says its result cannot wrap and the other says nothing are two entries, and
37//! the program keeps both. Merging them onto the one that promises more would hand the weaker
38//! instruction a promise nobody made about it, and merging them onto the one that promises less
39//! throws away something a later pass wanted. Keeping them apart costs an instruction that is
40//! rarely there and is the answer that needs no argument.
41//!
42//! The operands are looked up through what this pass has already decided, so a value that has been
43//! redirected onto an earlier one is compared as the earlier one. That is what lets a chain work:
44//! once two multiplies are one, the two adds on top of them have the same operands and become one
45//! too, and so does the address on top of those. A subscript is three or four instructions deep,
46//! so without this the pass would collapse the bottom of it and stop.
47//!
48//! An operand is a value and not an expression, so `(a + b) + c` and `a + (b + c)` are two values
49//! here. Making them one is reassociation, which is document 19 and a different pass.
50//!
51//! # What is allowed to move
52//!
53//! [`Opcode::has_effects`] answering no, which the IR defines as exactly the property this pass
54//! needs: an instruction that answers no can be deleted when nothing reads it, moved across a call,
55//! and merged with another one computing the same thing. It is written as a list of the pure
56//! opcodes rather than a list of the impure ones, so an opcode added to the IR later is impure
57//! until somebody says otherwise, and this pass leaves it alone.
58//!
59//! Three things beyond that are refused. `mem_entry` is pure and is not a computation, it is the
60//! name of memory on the way in, and merging two of them is a question for [`crate::memssa`] rather
61//! than an arithmetic identity. Anything producing other than exactly one result is refused, which
62//! is the checked arithmetic, whose second result would need redirecting alongside the first and
63//! which is not common enough to be worth the shape. Anything carrying something this pass cannot
64//! compare by value is refused, which in practice is `blockaddr` and nothing else, every other
65//! payload being on an opcode that has effects anyway.
66//!
67//! Division is on the allowed list and that is deliberate. Removing the second of two identical
68//! divisions is safe for a reason that is only true block locally: the first one is in the same
69//! block, so it has already run, and if it was going to trap the second one was never reached.
70//!
71//! # Calls
72//!
73//! Two calls to the same function with the same arguments are one value when the answer is a
74//! function of the arguments and nothing else. That is what `__attribute__((const))` says and
75//! [`crate::purity`] is what works it out, which makes this the third of the four consumers
76//! section 34.6 of `spec/optimizer/34-ipa.md` names for that analysis.
77//!
78//! A `pure` callee reads memory and writes none, so two of its calls are one value exactly when
79//! nothing wrote memory between them. Block local, that question needs no alias oracle and no
80//! memory SSA: count the writes from the top of the block and put the count in the key. Two pure
81//! calls with the same count had nothing written between them, because everything that could have
82//! written is in the block and was counted. A `const` callee reads nothing, so its count is always
83//! zero and a store between the two calls changes nothing.
84//!
85//! What counts as a write is [`Opcode::writes_memory`], which answers yes for a call because a
86//! call in general writes, and then the purity of that particular callee is asked. So an opaque
87//! call between two pure ones ends the pure one's answer and a second const call between them does
88//! not. The lifetime markers are writes here too, because they are the ones that say the bytes
89//! behind a local stopped meaning anything.
90//!
91//! The calls have their own table rather than sharing the one above. A call can have any number of
92//! arguments and the key above is a fixed size on purpose, so putting a call in it would mean
93//! paying for the call's shape on every add in the program. A call is rare enough beside an add
94//! that one allocation each is nothing.
95//!
96//! Only a direct call to a named function. A call through an address is a call to whatever the
97//! address held, and asking what that was is document 34.5's devirtualization rather than this.
98//!
99//! # What it does not do
100//!
101//! Nothing crosses a block boundary, no instruction moves, and the only thing that goes through
102//! memory is the write count that a `pure` call's answer is keyed on. A
103//! duplicate is removed where it stands and its readers are pointed at the first one, which is
104//! always above it. That means a computation in two arms of a branch stays in two arms: hoisting it
105//! to the common predecessor is [`crate::hoist`], and it wants the profitability question this pass
106//! does not ask.
107
108use std::collections::HashMap;
109
110use rucc_base::Symbol;
111use rucc_ir::{Block, Extra, Flags, FloatPred, Func, Inst, IntPred, Opcode, Sig, Type, Value};
112
113use crate::purity::{Callee, Facts};
114use crate::uses::substitute;
115use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
116
117/// Recorded for a removed address computation, which is what the pass is here for.
118const ADDRESS: &str = "address removed, an earlier one in the block computes the same address";
119
120/// Recorded for any other removed duplicate.
121const MERGED: &str = "instruction removed, an earlier one in the block computes the same thing";
122
123/// Recorded for a removed call, which is what the purity analysis bought this pass.
124const CALLED: &str = "call removed, an earlier call in the block computes the same thing";
125
126/// Recorded for a duplicate that would have gone if there had been fuel for it.
127const NO_FUEL: &str = "duplicate instruction kept, the pass ran out of fuel";
128
129/// The most operands any pure opcode has, which is the three of `select` and `fma`.
130const OPERANDS: usize = 3;
131
132/// What this pass is called, for the lists in [`crate::pipeline`] that name it.
133pub const NAME: &str = "number";
134
135/// The pass.
136#[derive(Debug)]
137pub struct Number;
138
139impl Pass for Number {
140 fn name(&self) -> &'static str {
141 NAME
142 }
143
144 fn describe(&self) -> &'static str {
145 "two instructions in a block computing the same thing from the same things are one value"
146 }
147
148 fn preserves(&self) -> Preserved {
149 // The shape of the function. No block is added, none is removed, no edge moves, and what
150 // is removed is pure, which no terminator is.
151 //
152 // The liveness is the one thing that does move, for the reason `crate::simplify` gives:
153 // pointing every reader of one value at another is one more place the second is live and
154 // one fewer the first is.
155 Preserved::ALL.without(Analysis::Liveness)
156 }
157
158 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
159 let facts = an.purity();
160 let mut stats = Stats::new();
161 let mut decided = Decided { same: HashMap::new(), gone: Vec::new() };
162
163 for block in func.blocks().collect::<Vec<Block>>() {
164 let mut seen: HashMap<Key, Value> = HashMap::new();
165 // The signature is beside the result rather than in the key, because a signature is
166 // pushed per call site and never interned, so two calls written the same way have two
167 // indices and comparing the indices would answer no to every question asked here. It
168 // is compared by value on a hit, which is once per duplicate rather than once per
169 // call.
170 let mut calls: HashMap<CallKey, (Sig, Value)> = HashMap::new();
171 // How many times memory has been written since the top of the block, which is what a
172 // `pure` call's answer is good for. Taken before the instruction runs, because a call
173 // that reads memory reads the version it was handed.
174 let mut memory = 0u32;
175 for inst in func.insts(block).collect::<Vec<Inst>>() {
176 if let Some((key, signature, result)) =
177 call_key(func, facts, &decided.same, inst, memory)
178 {
179 match calls.get(&key) {
180 Some(&(first_signature, first))
181 if func[first_signature] == func[signature] =>
182 {
183 decided.take(fuel, &mut stats, inst, result, first, CALLED);
184 }
185 Some(_) => (),
186 None => {
187 calls.insert(key, (signature, result));
188 }
189 }
190 continue;
191 }
192 if wrote_memory(func, facts, inst) {
193 memory += 1;
194 }
195 let Some((key, result)) = key(func, &decided.same, inst) else { continue };
196 match seen.get(&key) {
197 Some(&first) => {
198 let why = if is_address(func[inst].opcode) { ADDRESS } else { MERGED };
199 decided.take(fuel, &mut stats, inst, result, first, why);
200 }
201 None => {
202 seen.insert(key, result);
203 }
204 }
205 }
206 }
207
208 for inst in decided.gone {
209 func.remove_inst(inst);
210 }
211 if !decided.same.is_empty() {
212 substitute(func, &decided.same);
213 }
214 stats
215 }
216}
217
218/// What the walk has decided, which is one thing read as it goes and applied once at the end.
219struct Decided {
220 /// What each removed instruction's result is read as. It is also what an operand is looked up
221 /// through while the block is being walked, which is why it is built as the walk goes and
222 /// applied to the function at the end rather than either one alone.
223 same: HashMap<Value, Value>,
224 /// The instructions on their way out, in the order they were found.
225 gone: Vec<Inst>,
226}
227
228impl Decided {
229 /// Records that this instruction computes what an earlier one computed, or says why it stays.
230 ///
231 /// One place rather than two, because the arithmetic and the calls differ in how they are
232 /// keyed and not at all in what is done once a key has been found twice.
233 fn take(
234 &mut self,
235 fuel: &mut Fuel,
236 stats: &mut Stats,
237 inst: Inst,
238 result: Value,
239 first: Value,
240 why: &'static str,
241 ) {
242 if !fuel.take() {
243 // Out of fuel, which is a request to stop transforming and not to stop looking. The
244 // walk goes on so that the count of what could have gone is the same at every fuel
245 // setting, which is what makes a bisection over it monotonic. The table is left as it
246 // is, so the next duplicate of this same thing is counted against the same first
247 // instruction.
248 stats.missed(NO_FUEL);
249 return;
250 }
251 self.same.insert(result, first);
252 self.gone.push(inst);
253 stats.optimized(why);
254 }
255}
256
257/// Whether this instruction may have written memory, which is what ends a `pure` call's answer.
258///
259/// [`Opcode::writes_memory`] answers yes for a call, because a call in general writes. Which of
260/// them actually does is the question [`crate::purity`] was built to answer, so a call is sent
261/// there and everything else is taken at the opcode's word. A call whose callee nothing worked out
262/// is [`crate::Purity::Opaque`] and writes, which is the conservative answer and the right one.
263fn wrote_memory(func: &Func, facts: &Facts, inst: Inst) -> bool {
264 if !func[inst].opcode.writes_memory() {
265 return false;
266 }
267 match Callee::of(func, inst) {
268 Some(callee) => facts.purity_of(callee).writes_memory(),
269 None => true,
270 }
271}
272
273/// Whether an opcode is one of the two that compute an address.
274///
275/// Only for the counters, which want the two numbers apart because they answer different
276/// questions. The address count is what feeds [`crate::load`] and is the reason the pass exists.
277/// The other count is whatever else happened to be written twice, which on real C is not much.
278fn is_address(opcode: Opcode) -> bool {
279 matches!(opcode, Opcode::PtrAdd | Opcode::GlobalAddr)
280}
281
282/// What an instruction computes, as something two instructions can be equal on.
283///
284/// Fixed size and `Copy`, because a hash table entry per pure instruction in the program is enough
285/// work without an allocation for each of them.
286#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
287struct Key {
288 /// Which instruction it is.
289 opcode: Opcode,
290 /// What the optimizer was told it may assume about this one, which is not the same question as
291 /// what it may assume about another one of the same shape.
292 flags: Flags,
293 /// The type of its one result, which is what tells two casts of the same value apart.
294 ty: Type,
295 /// Whatever it carries besides operands, compared by value rather than by where it is stored.
296 tag: Tag,
297 /// Its operands, resolved, padded with `None`, and put in order if the opcode does not care.
298 args: [Option<Value>; OPERANDS],
299}
300
301/// What a call computes, as something two calls can be equal on.
302///
303/// Not `Copy` and not fixed size, which is why this is a second table and not a case of [`Key`].
304/// A call carries as many arguments as it was written with and there is no bound on that, so the
305/// arguments are a vector, and a vector on the arithmetic key would be an allocation per add.
306#[derive(Clone, Debug, PartialEq, Eq, Hash)]
307struct CallKey {
308 /// Which function. Always a named one, per the module documentation.
309 callee: Symbol,
310 /// What the optimizer was told it may assume about this call site, on the same argument the
311 /// flags are in [`Key`] for.
312 flags: Flags,
313 /// The type of its one result.
314 ty: Type,
315 /// Its arguments, resolved through what the block has decided so far.
316 args: Vec<Value>,
317 /// How many times memory had been written when the call ran.
318 ///
319 /// Always zero for a callee whose result is a function of its arguments alone, because
320 /// nothing that happened to memory can have changed the answer. The count itself for a
321 /// callee that reads memory, so that two of those are one value exactly when nothing wrote
322 /// between them. That is the block local half of the question
323 /// [`crate::Purity::depends_only_on_arguments`] hands to the alias analysis.
324 memory: u32,
325}
326
327/// An instruction's payload, as far as one can be compared with another.
328///
329/// [`Extra`] holds most of its payloads as an index into a side table, and two equal payloads
330/// written at two times are two indices, so an equality on the index would answer no to a question
331/// this pass is asking. This is the payload itself for the shapes a pure opcode has.
332#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
333enum Tag {
334 /// Nothing, which is all of the arithmetic.
335 None,
336 /// A constant's bits, for `iconst`, `fconst` and `splat`. The immediate table is not interned,
337 /// so this is the only reading under which two of the same constant are the same constant.
338 Bits(u128),
339 /// A name, for `global_addr`.
340 Symbol(Symbol),
341 /// Which comparison, for `icmp`.
342 IntPred(IntPred),
343 /// Which comparison, for `fcmp`.
344 FloatPred(FloatPred),
345}
346
347/// What an instruction computes and where its answer is, or nothing if it is not a candidate.
348///
349/// `same` is what the block has decided so far, and every operand goes through it, so an operand
350/// naming an instruction this pass is removing is compared as the instruction it is being removed
351/// in favour of. One lookup is the whole resolution rather than the first step of one: a value is
352/// either a key of `same`, meaning it is on its way out, or a value in `seen`, meaning it is
353/// staying, and it cannot be both, because a value only enters `same` on a hit and a hit never
354/// touches what the table already holds.
355fn key(func: &Func, same: &HashMap<Value, Value>, inst: Inst) -> Option<(Key, Value)> {
356 let data = &func[inst];
357 if data.opcode.has_effects() || data.opcode == Opcode::MemEntry {
358 return None;
359 }
360 let mut results = data.results();
361 let (Some(result), None) = (results.next(), results.next()) else { return None };
362 let tag = match data.extra {
363 Extra::None => Tag::None,
364 Extra::Imm(at) => Tag::Bits(func[at].bits()),
365 Extra::Symbol(name) => Tag::Symbol(name),
366 Extra::IntPred(pred) => Tag::IntPred(pred),
367 Extra::FloatPred(pred) => Tag::FloatPred(pred),
368 _ => return None,
369 };
370 let operands = &func[data.args];
371 if operands.len() > OPERANDS {
372 return None;
373 }
374 let mut args = [None; OPERANDS];
375 for (slot, &arg) in args.iter_mut().zip(operands) {
376 *slot = Some(same.get(&arg).copied().unwrap_or(arg));
377 }
378 // Two operands the opcode reads in either order are put in one order, so that `a + b` written
379 // once and `b + a` written once are one add. Sorting is by the position of the value in the
380 // function, which is an order that exists for no other reason and is fine because the only
381 // thing asked of it is that the two sides agree on it.
382 if data.opcode.is_commutative() && operands.len() == 2 {
383 args[..2].sort_unstable();
384 }
385 Some((Key { opcode: data.opcode, flags: data.flags, ty: func[result].ty, tag, args }, result))
386}
387
388/// What a call computes, the signature it computes it under, and where its answer is.
389///
390/// Nothing if it is not a candidate. `memory` is the write count at the call, which is used only
391/// if the callee reads memory. The signature comes back beside the key rather than in it, for the
392/// reason the table it goes into gives.
393fn call_key(
394 func: &Func,
395 facts: &Facts,
396 same: &HashMap<Value, Value>,
397 inst: Inst,
398 memory: u32,
399) -> Option<(CallKey, Sig, Value)> {
400 let data = &func[inst];
401 if data.opcode != Opcode::Call {
402 return None;
403 }
404 let Extra::Call(at) = data.extra else { return None };
405 let info = &func[at];
406 let callee = info.callee?;
407 // A call carrying an ABI note for an argument no parameter stands for, which is a structure
408 // passed through the ellipsis. Refused because the notes are not comparable, and a variadic
409 // function with no side effects is rare enough that nothing is lost by saying so.
410 if !func[info.varargs].is_empty() {
411 return None;
412 }
413 let purity = facts.purity_of(Callee::Direct(callee));
414 if purity.writes_memory() {
415 return None;
416 }
417 // A callee that may not come back is here as well as one that does, and the block is why. The
418 // first call is above this one in the same block, so it has already been made and control has
419 // already come back from it. A second call on the same arguments reading the same memory does
420 // what the first one did, which was come back.
421 let memory = if purity.depends_only_on_arguments() { 0 } else { memory };
422 let mut results = data.results();
423 let (Some(result), None) = (results.next(), results.next()) else { return None };
424 let args = func[data.args].iter().map(|&arg| same.get(&arg).copied().unwrap_or(arg)).collect();
425 let key = CallKey { callee, flags: data.flags, ty: func[result].ty, args, memory };
426 Some((key, info.signature, result))
427}
428
429#[cfg(test)]
430mod tests {
431 use rucc_ir::{
432 Block, Builder, Def, Extra, InstData, MemInfo, MemOrder, Restrict, Signature, Type,
433 };
434
435 use std::sync::Arc;
436
437 use rucc_base::Interner;
438 use rucc_ir::{AttrSet, FuncId, Module, Pic};
439 use rucc_target::{TargetInfo, Triple};
440
441 use super::*;
442 use crate::CallGraph;
443 use crate::purity::{Facts, infer};
444 use crate::stats::Kind;
445
446 /// An empty function with one block, which is where every test below builds.
447 fn blank() -> (Func, Block) {
448 let mut names = Interner::new();
449 let name = names.intern("f");
450 let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
451 let block = func.create_block();
452 (func, block)
453 }
454
455 /// An ordinary access of that alignment, with nothing said about its type.
456 fn plain(align: u32) -> MemInfo {
457 MemInfo {
458 size: 0,
459 align,
460 order: MemOrder::NotAtomic,
461 tbaa: None,
462 owns: 0,
463 restrict: Restrict::NONE,
464 }
465 }
466
467 /// An `alloca` of thirty-two bytes, which is an address nothing outside the function knows.
468 fn local(build: &mut Builder<'_>) -> Value {
469 let mem = build.func().add_mem(MemInfo { size: 32, ..plain(8) });
470 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
471 }
472
473 /// Runs the pass over the function with as much fuel as it wants.
474 fn run(func: &mut Func) -> Stats {
475 Number.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
476 }
477
478 /// How many instructions of that opcode are left in the function.
479 fn count(func: &Func, opcode: Opcode) -> usize {
480 func.blocks()
481 .flat_map(|block| func.insts(block).collect::<Vec<Inst>>())
482 .filter(|&inst| func[inst].opcode == opcode)
483 .count()
484 }
485
486 /// What the return statement hands back, after the pass has pointed it somewhere.
487 fn returned(func: &Func) -> Vec<Value> {
488 let block = func.blocks().last().expect("the function has a block");
489 let inst = func.terminator(block).expect("the block has a terminator");
490 func[func[inst].args].to_vec()
491 }
492
493 /// The operands of whatever instruction produced that value.
494 fn operands(func: &Func, value: Value) -> Vec<Value> {
495 let Def::Result { inst, .. } = func[value].def else { panic!("not an instruction result") };
496 func[func[inst].args].to_vec()
497 }
498
499 #[test]
500 fn the_same_arithmetic_on_the_same_operands_twice_is_one_instruction() {
501 let (mut func, block) = blank();
502 let mut build = Builder::new(&mut func, block);
503 let left = build.iconst(Type::int(64), 3);
504 let right = build.iconst(Type::int(64), 5);
505 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
506 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
507 build.ret(&[first, second]);
508
509 let stats = run(&mut func);
510 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
511 assert_eq!(count(&func, Opcode::Add), 1);
512 assert_eq!(returned(&func), vec![first, first]);
513 }
514
515 #[test]
516 fn a_commutative_pair_matches_with_its_operands_the_other_way_round() {
517 let (mut func, block) = blank();
518 let mut build = Builder::new(&mut func, block);
519 let left = build.iconst(Type::int(64), 3);
520 let right = build.iconst(Type::int(64), 5);
521 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
522 let second = build.binary(Opcode::Add, right, left, Flags::NONE);
523 build.ret(&[first, second]);
524
525 let stats = run(&mut func);
526 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
527 assert_eq!(returned(&func), vec![first, first]);
528 }
529
530 #[test]
531 fn a_subtraction_the_other_way_round_is_a_different_answer() {
532 let (mut func, block) = blank();
533 let mut build = Builder::new(&mut func, block);
534 let left = build.iconst(Type::int(64), 3);
535 let right = build.iconst(Type::int(64), 5);
536 let first = build.binary(Opcode::Sub, left, right, Flags::NONE);
537 let second = build.binary(Opcode::Sub, right, left, Flags::NONE);
538 build.ret(&[first, second]);
539
540 let stats = run(&mut func);
541 assert!(!stats.changed(), "three minus five is not five minus three");
542 assert_eq!(count(&func, Opcode::Sub), 2);
543 }
544
545 #[test]
546 fn two_adds_that_promise_different_things_stay_two_adds() {
547 let (mut func, block) = blank();
548 let mut build = Builder::new(&mut func, block);
549 let left = build.iconst(Type::int(64), 3);
550 let right = build.iconst(Type::int(64), 5);
551 let first = build.binary(Opcode::Add, left, right, Flags::NSW);
552 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
553 build.ret(&[first, second]);
554
555 // Merging them onto the first hands the second a promise nobody made about it, and
556 // merging them onto the second throws away a promise somebody did make.
557 let stats = run(&mut func);
558 assert!(!stats.changed());
559 assert_eq!(count(&func, Opcode::Add), 2);
560 }
561
562 #[test]
563 fn a_chain_collapses_all_the_way_up_and_not_just_at_the_bottom() {
564 let (mut func, block) = blank();
565 let mut build = Builder::new(&mut func, block);
566 let index = build.iconst(Type::int(64), 2);
567 let scale = build.iconst(Type::int(64), 8);
568 let first = build.binary(Opcode::Mul, index, scale, Flags::NONE);
569 let second = build.binary(Opcode::Mul, index, scale, Flags::NONE);
570 let up = build.binary(Opcode::Add, first, scale, Flags::NONE);
571 let down = build.binary(Opcode::Add, second, scale, Flags::NONE);
572 build.ret(&[up, down]);
573
574 // The second add's operand is a value on its way out, so it has to be compared as the
575 // value it is on its way out in favour of. Without that the pass takes the multiply and
576 // stops, which on a subscript is the bottom instruction of three or four.
577 let stats = run(&mut func);
578 assert_eq!(stats.count(Kind::Optimized, MERGED), 2);
579 assert_eq!(count(&func, Opcode::Mul), 1);
580 assert_eq!(count(&func, Opcode::Add), 1);
581 assert_eq!(returned(&func), vec![up, up]);
582 }
583
584 #[test]
585 fn the_same_constant_written_twice_is_one_constant() {
586 let (mut func, block) = blank();
587 let mut build = Builder::new(&mut func, block);
588 let first = build.iconst(Type::int(64), 7);
589 let second = build.iconst(Type::int(64), 7);
590 let narrow = build.iconst(Type::int(32), 7);
591 build.ret(&[first, second, narrow]);
592
593 // The immediate table is not interned, so the two sevens are two entries in it and only
594 // reading the bits back out finds that they are the same seven. The third is the same bits
595 // at another width, which is another value.
596 let stats = run(&mut func);
597 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
598 assert_eq!(count(&func, Opcode::IConst), 2);
599 assert_eq!(returned(&func), vec![first, first, narrow]);
600 }
601
602 #[test]
603 fn two_allocas_are_two_addresses_however_alike_they_look() {
604 let (mut func, block) = blank();
605 let mut build = Builder::new(&mut func, block);
606 let one = local(&mut build);
607 let two = local(&mut build);
608 build.ret(&[one, two]);
609
610 // An `alloca` has effects for exactly this reason. Two of them are two objects and the
611 // program can tell, by comparing their addresses if by nothing else.
612 let stats = run(&mut func);
613 assert!(!stats.changed());
614 assert_eq!(count(&func, Opcode::Alloca), 2);
615 }
616
617 #[test]
618 fn two_loads_of_one_address_are_left_to_the_pass_that_knows_about_memory() {
619 let (mut func, block) = blank();
620 let mut build = Builder::new(&mut func, block);
621 let slot = local(&mut build);
622 let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
623 let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
624 build.ret(&[first, second]);
625
626 // A load is not pure, because what it answers depends on what has been written since. It
627 // is `crate::load` that knows whether anything has been, and this pass never touches one.
628 let stats = run(&mut func);
629 assert!(!stats.changed());
630 assert_eq!(count(&func, Opcode::Load), 2);
631 }
632
633 #[test]
634 fn what_one_block_computes_does_not_reach_the_next_one() {
635 let (mut func, entry) = blank();
636 let next = func.create_block();
637 let mut build = Builder::new(&mut func, entry);
638 let left = build.iconst(Type::int(64), 3);
639 let right = build.iconst(Type::int(64), 5);
640 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
641 build.jump(next, &[]);
642 let mut build = Builder::new(&mut func, next);
643 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
644 build.ret(&[first, second]);
645
646 // The first add dominates the second and the version over the dominator tree takes it.
647 // Section 16.1 is where the argument for this being enough for now lives.
648 let stats = run(&mut func);
649 assert!(!stats.changed());
650 assert_eq!(count(&func, Opcode::Add), 2);
651 }
652
653 #[test]
654 fn one_name_for_the_address_is_what_lets_the_load_be_forwarded() {
655 let (mut func, block) = blank();
656 let mut build = Builder::new(&mut func, block);
657 let base = local(&mut build);
658 let index = build.iconst(Type::int(64), 2);
659 let scale = build.iconst(Type::int(64), 8);
660 let wrote = build.iconst(Type::int(64), 7);
661 let to = build.binary(Opcode::Mul, index, scale, Flags::NONE);
662 let to = build.binary(Opcode::PtrAdd, base, to, Flags::NONE);
663 build.store(wrote, to, plain(8), Flags::NONE);
664 let from = build.binary(Opcode::Mul, index, scale, Flags::NONE);
665 let from = build.binary(Opcode::PtrAdd, base, from, Flags::NONE);
666 let read = build.load(Type::int(64), from, plain(8), Flags::NONE);
667 build.ret(&[read]);
668
669 // This is `a[2] = 7; total += a[2];` as the front end emits it, with the subscript lowered
670 // twice because lowering it does not know it has been lowered already. Before this pass
671 // the store's address and the load's address are two values and `crate::load` compares
672 // addresses by identity, so it refuses. Afterwards they are one value and it forwards.
673 let stats = run(&mut func);
674 assert_eq!(stats.count(Kind::Optimized, ADDRESS), 1);
675 assert_eq!(stats.count(Kind::Optimized, MERGED), 1);
676 assert_eq!(count(&func, Opcode::PtrAdd), 1);
677
678 let mut analyses = crate::machine::fixtures::analyses();
679 let stats = crate::load::LoadForward.run(&mut func, &mut analyses, &mut Fuel::unlimited());
680 assert!(stats.changed(), "the two addresses are one value now");
681 assert_eq!(count(&func, Opcode::Load), 0);
682 assert_eq!(returned(&func), vec![wrote]);
683 }
684
685 #[test]
686 fn an_instruction_with_three_operands_is_matched_on_all_three() {
687 let (mut func, block) = blank();
688 let mut build = Builder::new(&mut func, block);
689 let left = build.iconst(Type::int(64), 3);
690 let right = build.iconst(Type::int(64), 5);
691 let which = build.icmp(IntPred::Slt, left, right);
692 let args = build.func().push_values(&[which, left, right]);
693 let pick = InstData { args, ..InstData::new(Opcode::Select) };
694 let first = build.value(pick, Type::int(64));
695 let second = build.value(pick, Type::int(64));
696 let args = build.func().push_values(&[which, right, left]);
697 let other = InstData { args, ..InstData::new(Opcode::Select) };
698 let other = build.value(other, Type::int(64));
699 build.ret(&[first, second, other]);
700
701 let stats = run(&mut func);
702 assert_eq!(stats.count(Kind::Optimized, MERGED), 1, "the arms the other way round differ");
703 assert_eq!(count(&func, Opcode::Select), 2);
704 assert_eq!(returned(&func), vec![first, first, other]);
705 }
706
707 #[test]
708 fn a_repeated_global_address_is_counted_as_an_address() {
709 let (mut func, block) = blank();
710 let mut names = Interner::new();
711 let global = names.intern("g");
712 let mut build = Builder::new(&mut func, block);
713 let named = InstData { extra: Extra::Symbol(global), ..InstData::new(Opcode::GlobalAddr) };
714 let first = build.value(named, Type::PTR);
715 let second = build.value(named, Type::PTR);
716 let offset = build.iconst(Type::int(64), 8);
717 let one = build.binary(Opcode::PtrAdd, first, offset, Flags::NONE);
718 let two = build.binary(Opcode::PtrAdd, second, offset, Flags::NONE);
719 build.ret(&[one, two]);
720
721 let stats = run(&mut func);
722 assert_eq!(stats.count(Kind::Optimized, ADDRESS), 2);
723 assert_eq!(count(&func, Opcode::GlobalAddr), 1);
724 assert_eq!(operands(&func, one), vec![first, offset]);
725 }
726
727 #[test]
728 fn without_fuel_the_duplicate_stays_and_the_chance_is_still_counted() {
729 let (mut func, block) = blank();
730 let mut build = Builder::new(&mut func, block);
731 let left = build.iconst(Type::int(64), 3);
732 let right = build.iconst(Type::int(64), 5);
733 let first = build.binary(Opcode::Add, left, right, Flags::NONE);
734 let second = build.binary(Opcode::Add, left, right, Flags::NONE);
735 build.ret(&[first, second]);
736
737 let stats =
738 Number.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
739 assert!(!stats.changed());
740 assert_eq!(stats.count(Kind::Missed, NO_FUEL), 1);
741 assert_eq!(count(&func, Opcode::Add), 2);
742 }
743
744 /// A module where `f` is built by `body` and every name in `declared` is a function with
745 /// those attributes and no body at all.
746 ///
747 /// No body on purpose. A translation unit is mostly made of calls to functions declared in a
748 /// header with an attribute on them and defined in another file, and that is the case the
749 /// purity analysis answers from the attribute alone. Every one of them takes an integer and
750 /// returns one, which is the shape `abs` has and is enough for every question here.
751 fn calling(
752 declared: &[(&str, AttrSet)],
753 body: fn(&mut Builder<'_>, &[Symbol], Sig) -> Vec<Value>,
754 ) -> (Module, FuncId, Analyses) {
755 let mut names = Interner::new();
756 let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
757 let mut module = Module::new(names.intern("t.c"), &target);
758 let shape = shape();
759 let mut called: Vec<Symbol> = Vec::new();
760 for &(name, attrs) in declared {
761 let name = names.intern(name);
762 let mut callee = Func::new(name, shape.clone());
763 callee.attrs.set = attrs;
764 module.add_func(callee);
765 called.push(name);
766 }
767 let returns = [Type::int(64), Type::int(64)];
768 let mut func = Func::new(names.intern("f"), Signature::new().with_returns(&returns));
769 let block = func.create_block();
770 let mut build = Builder::new(&mut func, block);
771 let signature = build.func().add_signature(shape);
772 let answers = body(&mut build, &called, signature);
773 build.ret(&answers);
774 let id = module.add_func(func);
775 let mut facts = Facts::of_module(&module, &names);
776 infer(&module, &CallGraph::of(&module, Pic::Executable), &mut facts);
777 let an = crate::machine::fixtures::analyses().calling(Arc::new(facts));
778 (module, id, an)
779 }
780
781 /// What every callee in these tests is declared as, which is what `abs` is declared as.
782 fn shape() -> Signature {
783 Signature::new().with_params(&[Type::int(64)]).with_returns(&[Type::int(64)])
784 }
785
786 /// Makes the call and hands back its one result.
787 fn call_of(build: &mut Builder<'_>, callee: Symbol, signature: Sig, arg: Value) -> Value {
788 let inst = build.call(callee, signature, &[arg]);
789 build.func()[inst].results().next().expect("the signature returns one value")
790 }
791
792 /// Stores something into a fresh local, which is a write to memory and nothing else.
793 fn write(build: &mut Builder<'_>) {
794 let slot = local(build);
795 let value = build.iconst(Type::int(64), 1);
796 build.store(value, slot, plain(8), Flags::NONE);
797 }
798
799 #[test]
800 fn two_calls_to_a_const_function_on_the_same_argument_are_one_call() {
801 let (mut module, id, mut an) = calling(&[("g", AttrSet::READNONE)], |build, at, sig| {
802 let arg = build.iconst(Type::int(64), 7);
803 vec![call_of(build, at[0], sig, arg), call_of(build, at[0], sig, arg)]
804 });
805 let stats = Number.run(&mut module[id], &mut an, &mut Fuel::unlimited());
806 assert_eq!(stats.count(Kind::Optimized, CALLED), 1);
807 assert_eq!(count(&module[id], Opcode::Call), 1);
808 let answers = returned(&module[id]);
809 assert_eq!(answers[0], answers[1]);
810 }
811
812 #[test]
813 fn two_calls_to_a_const_function_on_different_arguments_stay_two() {
814 let (mut module, id, mut an) = calling(&[("g", AttrSet::READNONE)], |build, at, sig| {
815 let one = build.iconst(Type::int(64), 7);
816 let other = build.iconst(Type::int(64), 8);
817 vec![call_of(build, at[0], sig, one), call_of(build, at[0], sig, other)]
818 });
819 assert!(!Number.run(&mut module[id], &mut an, &mut Fuel::unlimited()).changed());
820 assert_eq!(count(&module[id], Opcode::Call), 2);
821 }
822
823 #[test]
824 fn a_store_between_two_const_calls_changes_nothing() {
825 // Which is the whole difference between `const` and `pure`. The answer is a function of
826 // the argument, so what happened to memory in between is not part of the question.
827 let (mut module, id, mut an) = calling(&[("g", AttrSet::READNONE)], |build, at, sig| {
828 let arg = build.iconst(Type::int(64), 7);
829 let first = call_of(build, at[0], sig, arg);
830 write(build);
831 vec![first, call_of(build, at[0], sig, arg)]
832 });
833 let stats = Number.run(&mut module[id], &mut an, &mut Fuel::unlimited());
834 assert_eq!(stats.count(Kind::Optimized, CALLED), 1);
835 assert_eq!(count(&module[id], Opcode::Call), 1);
836 }
837
838 #[test]
839 fn two_calls_to_a_pure_function_with_nothing_written_between_them_are_one_call() {
840 let (mut module, id, mut an) = calling(&[("g", AttrSet::READONLY)], |build, at, sig| {
841 let arg = build.iconst(Type::int(64), 7);
842 vec![call_of(build, at[0], sig, arg), call_of(build, at[0], sig, arg)]
843 });
844 let stats = Number.run(&mut module[id], &mut an, &mut Fuel::unlimited());
845 assert_eq!(stats.count(Kind::Optimized, CALLED), 1);
846 assert_eq!(count(&module[id], Opcode::Call), 1);
847 }
848
849 #[test]
850 fn a_store_between_two_pure_calls_keeps_both_of_them() {
851 // The store is to a fresh local nothing else can name, so an alias oracle would say the
852 // callee cannot have read it. This pass has no oracle and does not ask one. A write is a
853 // write and the second call is a different value, which is the conservative answer and
854 // the one that needs no analysis to be right.
855 let (mut module, id, mut an) = calling(&[("g", AttrSet::READONLY)], |build, at, sig| {
856 let arg = build.iconst(Type::int(64), 7);
857 let first = call_of(build, at[0], sig, arg);
858 write(build);
859 vec![first, call_of(build, at[0], sig, arg)]
860 });
861 assert!(!Number.run(&mut module[id], &mut an, &mut Fuel::unlimited()).changed());
862 assert_eq!(count(&module[id], Opcode::Call), 2);
863 }
864
865 #[test]
866 fn a_const_call_between_two_pure_calls_changes_nothing() {
867 // A call is a write until something says otherwise, and here something does. Without
868 // that this would be the store case and the two reads would stay two.
869 let declared = [("g", AttrSet::READONLY), ("h", AttrSet::READNONE)];
870 let (mut module, id, mut an) = calling(&declared, |build, at, sig| {
871 let arg = build.iconst(Type::int(64), 7);
872 let first = call_of(build, at[0], sig, arg);
873 call_of(build, at[1], sig, arg);
874 vec![first, call_of(build, at[0], sig, arg)]
875 });
876 let stats = Number.run(&mut module[id], &mut an, &mut Fuel::unlimited());
877 assert_eq!(stats.count(Kind::Optimized, CALLED), 1);
878 assert_eq!(count(&module[id], Opcode::Call), 2);
879 }
880
881 #[test]
882 fn two_calls_to_a_function_that_may_write_anything_stay_two() {
883 let (mut module, id, mut an) = calling(&[("g", AttrSet::NONE)], |build, at, sig| {
884 let arg = build.iconst(Type::int(64), 7);
885 vec![call_of(build, at[0], sig, arg), call_of(build, at[0], sig, arg)]
886 });
887 assert!(!Number.run(&mut module[id], &mut an, &mut Fuel::unlimited()).changed());
888 assert_eq!(count(&module[id], Opcode::Call), 2);
889 }
890
891 #[test]
892 fn two_calls_stay_two_when_nothing_worked_the_purity_out() {
893 // Which is the `-O0` pipeline, and every caller that builds an analysis cache by hand. A
894 // pass has to be correct against the empty facts, because that is what it is handed until
895 // somebody fills them in.
896 let (mut module, id, _) = calling(&[("g", AttrSet::READNONE)], |build, at, sig| {
897 let arg = build.iconst(Type::int(64), 7);
898 vec![call_of(build, at[0], sig, arg), call_of(build, at[0], sig, arg)]
899 });
900 let mut an = crate::machine::fixtures::analyses();
901 assert!(!Number.run(&mut module[id], &mut an, &mut Fuel::unlimited()).changed());
902 assert_eq!(count(&module[id], Opcode::Call), 2);
903 }
904
905 #[test]
906 fn two_call_sites_with_their_own_signature_entries_are_still_one_call() {
907 // A signature is pushed per call site and never interned, so a function called twice has
908 // two entries that are equal and not the same index. Every call a real front end makes
909 // looks like this, which is why the fixture above, where both calls share one entry, is
910 // the case that does not happen and this one is the case that does.
911 let (mut module, id, mut an) = calling(&[("g", AttrSet::READNONE)], |build, at, sig| {
912 let arg = build.iconst(Type::int(64), 7);
913 let first = call_of(build, at[0], sig, arg);
914 let own = build.func().add_signature(shape());
915 assert_ne!(own, sig);
916 vec![first, call_of(build, at[0], own, arg)]
917 });
918 let stats = Number.run(&mut module[id], &mut an, &mut Fuel::unlimited());
919 assert_eq!(stats.count(Kind::Optimized, CALLED), 1);
920 assert_eq!(count(&module[id], Opcode::Call), 1);
921 }
922
923 #[test]
924 fn two_calls_under_signatures_that_are_not_the_same_stay_two() {
925 // The same name called two ways, which C does not let a translation unit write and the
926 // pass does not rely on C to prevent. The two signatures agree on what goes in and what
927 // comes out and disagree on whether there is an ellipsis, which is enough to make them a
928 // different call and is the smallest difference that says so.
929 let (mut module, id, mut an) = calling(&[("g", AttrSet::READNONE)], |build, at, sig| {
930 let arg = build.iconst(Type::int(64), 7);
931 let first = call_of(build, at[0], sig, arg);
932 let mut other = shape();
933 other.variadic = true;
934 let other = build.func().add_signature(other);
935 vec![first, call_of(build, at[0], other, arg)]
936 });
937 assert!(!Number.run(&mut module[id], &mut an, &mut Fuel::unlimited()).changed());
938 assert_eq!(count(&module[id], Opcode::Call), 2);
939 }
940}