Skip to main content

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}