Skip to main content

rucc_opt/
load.rs

1//! A load of something the block has already read or written is that value, not a second read.
2//!
3//! Design: `spec/optimizer/16-gvn-and-pre.md` section 16.2, which calls redundant load elimination
4//! the real prize of that document. Value numbering over arithmetic is worth less than people
5//! expect on C, because the front end does not generate the same expression twice and the
6//! programmer does not write it twice. Loads are different. `p->x` three times in a function is
7//! three reads of memory, and if nothing wrote through an aliasing pointer in between, two of them
8//! are work the program does not have to do.
9//!
10//! # The restricted version, and why this is it
11//!
12//! Section 16.2 asks for two of these. The one at `-O2` walks memory SSA back from each load to
13//! its clobbering definition, translates an address backwards through a block's parameters, and
14//! sees through a `memcpy`. The one here is the other: same block only, no phi translation, no
15//! `memcpy`, and no memory SSA at all. That is the version the section says should be at `-O1`,
16//! and it says why: it catches the repeated `p->x` in one basic block, which is the majority of the
17//! opportunities, for a fraction of the machinery.
18//!
19//! The two are not alternatives and this is not a stand-in for the other one. What it is is the
20//! part that can be written without an alias oracle, and the part whose cost is one walk over each
21//! block.
22//!
23//! # What it knows
24//!
25//! One table per block, from an address to the value that address holds, thrown away at the end of
26//! the block because what reaches a block from its predecessors is the question this version does
27//! not ask. A store writes what it stored. A load that had to happen writes what it read. Either
28//! way the next load of that address is that value.
29//!
30//! An address is one SSA value, compared by identity. Two pointers that are the same address by
31//! arithmetic and not by name are two addresses here, which costs opportunities and no
32//! correctness: an address computed twice out of the same parts is tracked twice and neither copy
33//! is forwarded to the other. What would give the two one name is value numbering over the
34//! arithmetic, which is the other half of document 16 and is not built. It costs more than it
35//! sounds like it should. `a[i] = v; total += a[i];` written in C is a store and a load whose
36//! addresses are two separate runs of the same multiply and add, because the front end emits the
37//! subscript twice, so the shape this pass is most obviously for is one it cannot see until that
38//! lands.
39//!
40//! # What throws the table away
41//!
42//! Anything that could write anywhere, and what that means is the alias oracle's answer. A call, a
43//! store and a safety plane access each take out the entries the oracle says they may write and
44//! leave the rest standing. Everything else that touches memory, which is an atomic, a fence, a
45//! `memcpy` and anything else [`Opcode::touches_memory`] is true of, empties the table and records
46//! nothing. That predicate is the conservative one, so an opcode added to the IR later throws the
47//! table away rather than being quietly assumed harmless.
48//!
49//! The plane access is there because of what it costs to leave it out. On a build with
50//! `-fsafety=detect` there is a `meta_` or a `check_` beside almost every access in the program, so
51//! a pass that empties the table at each of them has an empty table almost all of the time. None of
52//! them is a write to the address it names, which is what [`Opcode::touches_only_planes`] says and
53//! what the oracle now answers with.
54//!
55//! The oracle arrived late and this pass is the first consumer it has ever had. Until tamnd/rucc#1467
56//! a call and a store both emptied the whole table, because `crate::alias` wanted the module and a
57//! pass is handed one function. What the whole table cost was the second address: `*p = v; total +=
58//! *q;` with two locals was refused even where the two cannot be the same object. It is not refused
59//! now.
60//!
61//! What the oracle is worth here rests on the escape analysis more than on anything else in it.
62//! `spec/optimizer/08-alias-analysis.md` section 8.4 calls that the cheapest interprocedural
63//! flavoured fact there is, and it is what answers the ordinary case: a local whose address never
64//! leaves the function cannot be touched by any call in it, whatever the callee does.
65//!
66//! A volatile access empties the table and records nothing either way. Whether a volatile store
67//! could be forwarded from is an argument about what `volatile` promises, and this pass does not
68//! need to have it.
69//!
70//! # The width, which is where the miscompilation would be
71//!
72//! Section 16.6 names it as the single most likely wrong answer in that document: a load forwarded
73//! from a store of a different size. Section 09.5 has the three-way distinction, which is that a
74//! store covering the load exactly is the value, one covering it partially needs an extract, and
75//! one not covering it at all means the walk should continue.
76//!
77//! This pass only ever takes the first of the three. The address has to be the same SSA value and
78//! the type has to be equal, which is the same width and the same reading of the bits, and
79//! anything else is left alone and counted. Two-way is what somebody writes first and it is right
80//! most of the time, which is what makes it worth being explicit that this is not that.
81//!
82//! # What it leaves behind
83//!
84//! The load goes, rather than staying and having its result forwarded. [`crate::dce`] would not
85//! remove it: `has_effects` is true of every load, because a pass that removed one would need to
86//! know the address is dereferenced anyway, and this is the pass that knows it. The load being
87//! removed is safe for a reason nothing else in the pipeline has: something already read or wrote
88//! that exact address in this block, so the address is one the program dereferences whatever
89//! happens next.
90//!
91//! The store stays. Removing a store that a later store covers is dead store elimination, which is
92//! document 17 and a different pass.
93
94use std::collections::HashMap;
95
96use rucc_ir::{Block, Flags, Func, Inst, Opcode, Type, Value};
97
98use crate::alias::{Access, Alias};
99use crate::uses::substitute;
100use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
101
102/// What this pass is called, which the pipeline matches on to decide whether to build the module
103/// facts the oracle asks for.
104pub const NAME: &str = "load-forward";
105
106/// Recorded for a load that read what a store in the same block had just written.
107const FORWARDED: &str = "load replaced by the value a store in the same block wrote there";
108
109/// Recorded for a load of an address an earlier load in the same block had already read.
110const REUSED: &str = "load replaced by what an earlier load of the same address read";
111
112/// Recorded for a load whose address is known and whose type is not the type it is known at.
113const WIDTH: &str = "load kept, what is known about that address is a different type";
114
115/// Recorded for a load that would have gone if there had been fuel for it.
116const NO_FUEL: &str = "redundant load kept, the pass ran out of fuel";
117
118/// The pass.
119#[derive(Debug)]
120pub struct LoadForward;
121
122impl Pass for LoadForward {
123    fn name(&self) -> &'static str {
124        "load-forward"
125    }
126
127    fn describe(&self) -> &'static str {
128        "a load of an address the block has already read or written is that value"
129    }
130
131    fn preserves(&self) -> Preserved {
132        // The shape of the function. No block is added, none is removed, no edge moves, and the
133        // instructions that go are loads, which are never terminators.
134        //
135        // The liveness is the one thing that does move, for the reason `crate::simplify` gives:
136        // pointing every reader of one value at another is one more place the second is live and
137        // one fewer the first is.
138        Preserved::ALL.without(Analysis::Liveness)
139    }
140
141    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
142        let mut stats = Stats::new();
143        // What each removed load's result is read as, applied to the whole function once at the
144        // end. Rewriting each one where it is found would be a walk over the function per load,
145        // and there is nothing to gain by it: what this pass looks at is the address, and a
146        // redirection of a result does not change one.
147        let mut forward: HashMap<Value, Value> = HashMap::new();
148        let mut gone: Vec<Inst> = Vec::new();
149
150        // The oracle borrows the function, so the walk that reads it is a scope of its own and
151        // every edit happens after it. Built once, because the escape analysis inside it is one
152        // walk over the function and every query may ask it.
153        {
154            let mut alias = Alias::new(func, an.outside()).knowing(an.modref());
155            for block in func.blocks().collect::<Vec<Block>>() {
156                let mut known: HashMap<Value, Held> = HashMap::new();
157                for inst in func.insts(block).collect::<Vec<Inst>>() {
158                    match act(func, inst) {
159                        Act::Ignore => {}
160                        Act::Forget => known.clear(),
161                        Act::Ask => {
162                            known.retain(|_, held| alias.clobbered_by(&held.access, inst).is_no());
163                        }
164                        Act::Wrote { address, value, ty } => {
165                            // The entries this store may have written go, and the rest stay. Its
166                            // own goes in after, so that the address it just wrote survives its own
167                            // clearing whatever the oracle made of it.
168                            let Some(wrote) = alias.writes(inst) else {
169                                known.clear();
170                                continue;
171                            };
172                            known.retain(|_, held| alias.query(&held.access, &wrote).is_no());
173                            known.insert(address, Held { ty, value, stored: true, access: wrote });
174                        }
175                        Act::Read { address, result, ty } => {
176                            match known.get(&address).copied() {
177                                Some(held) if held.ty == ty => {
178                                    if fuel.take() {
179                                        forward.insert(result, held.value);
180                                        gone.push(inst);
181                                        stats.optimized(if held.stored {
182                                            FORWARDED
183                                        } else {
184                                            REUSED
185                                        });
186                                        continue;
187                                    }
188                                    // Out of fuel, which is a request to stop transforming and not
189                                    // to stop looking. The walk goes on so that the count of what
190                                    // could have gone is the same at every fuel setting, which is
191                                    // what makes a bisection over it monotonic.
192                                    stats.missed(NO_FUEL);
193                                }
194                                Some(_) => stats.missed(WIDTH),
195                                None => {}
196                            }
197                            // A load with no access is a load the oracle could say nothing about
198                            // later, so it is not recorded at all rather than recorded as
199                            // something no call can be asked about.
200                            if let Some(read) = alias.reads(inst) {
201                                known.insert(
202                                    address,
203                                    Held { ty, value: result, stored: false, access: read },
204                                );
205                            }
206                        }
207                    }
208                }
209            }
210        }
211
212        for inst in gone {
213            func.remove_inst(inst);
214        }
215        if !forward.is_empty() {
216            substitute(func, &forward);
217        }
218        stats
219    }
220}
221
222/// What the block knows about one address.
223///
224/// The value and the type are what the forwarding turns on. Whether it was stored or read is only
225/// for the counters, and they want it because the two numbers answer different questions. A
226/// forward from a store says the program wrote something and read it straight back, which is what
227/// an unrolled loop over an array looks like and what the constant folder can usually finish off.
228/// A reuse says the program read the same place twice, which is the `p->x` this pass is named for
229/// and which folds into nothing at all.
230#[derive(Clone, Copy)]
231struct Held {
232    /// The type the address is known at, which a load has to match exactly to be that value.
233    ty: Type,
234    /// What the address holds.
235    value: Value,
236    /// Whether a store put it there, rather than a load having read it.
237    stored: bool,
238    /// Which bytes it is, for asking the oracle whether a call or a store reaches them.
239    ///
240    /// The access of the instruction that put the entry here, which is the same address and the
241    /// same width as any load that will match it, since matching is by address value and by type.
242    access: Access,
243}
244
245/// What one instruction does to the table.
246enum Act {
247    /// Nothing. It touches no memory.
248    Ignore,
249    /// It could write anywhere the oracle cannot rule out, and nothing here says what it wrote.
250    Forget,
251    /// The oracle is asked what it wrote, because nothing about its shape says.
252    Ask,
253    /// It writes this value of this type at this address, and may write elsewhere.
254    Wrote { address: Value, value: Value, ty: Type },
255    /// It reads a value of this type from this address into this result.
256    Read { address: Value, result: Value, ty: Type },
257}
258
259/// Which of the five an instruction is.
260///
261/// The two interesting cases are narrow on purpose. A plain non-volatile `Load` with one address
262/// and one result, and a plain non-volatile `Store` of one value to one address. `AtomicLoad` and
263/// `AtomicStore` are separate opcodes in this IR and are not these, so an ordering never reaches
264/// here as something to forward, and neither does a load carrying a memory token, which is what
265/// more than one result would mean.
266///
267/// `Ask` is what an instruction that writes memory without an access saying where gets, and the
268/// oracle is asked a different question about one of those: `Alias::clobbered_by` rather than
269/// `Alias::query`, since there is no access of its own to hand over. A call is the obvious member
270/// and what it may write is what its attributes and its arguments say. The safety instrumentation
271/// is the other one, and there the answer is about the opcode rather than about the callee: a
272/// plane write is not a write to the address it names, which is what [`Opcode::touches_only_planes`]
273/// is for. Sending those to `Forget` instead is what this pass used to do, and it meant that on a
274/// build with `-fsafety=detect` the table was emptied beside almost every access and the pass did
275/// close to nothing, which is tamnd/rucc#1501.
276fn act(func: &Func, inst: Inst) -> Act {
277    let data = &func[inst];
278    if !data.opcode.touches_memory() {
279        return Act::Ignore;
280    }
281    if data.flags.contains(Flags::VOLATILE) {
282        return Act::Forget;
283    }
284    let args = &func[data.args];
285    match data.opcode {
286        Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => Act::Ask,
287        other if other.touches_only_planes() => Act::Ask,
288        Opcode::Load => {
289            let mut results = data.results();
290            let (Some(&address), Some(result), None) =
291                (args.first(), results.next(), results.next())
292            else {
293                return Act::Forget;
294            };
295            Act::Read { address, result, ty: func[result].ty }
296        }
297        Opcode::Store => {
298            let (Some(&value), Some(&address)) = (args.first(), args.get(1)) else {
299                return Act::Forget;
300            };
301            Act::Wrote { address, value, ty: func[value].ty }
302        }
303        _ => Act::Forget,
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use std::sync::Arc;
310
311    use rucc_base::Interner;
312    use rucc_ir::{
313        AttrSet, Attrs, Block, Builder, Extra, Flags, Func, InstData, MemInfo, MemOrder, Module,
314        Restrict, Signature, Type, Value,
315    };
316    use rucc_target::{TargetInfo, Triple};
317
318    use super::*;
319    use crate::Fuel;
320    use crate::outside::Outside;
321
322    /// An empty function with one block, which is where every test below builds.
323    fn blank() -> (Interner, Func, Block) {
324        let mut names = Interner::new();
325        let name = names.intern("f");
326        let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
327        let block = func.create_block();
328        (names, func, block)
329    }
330
331    /// An ordinary access of that alignment, with nothing said about its type.
332    fn plain(align: u32) -> MemInfo {
333        MemInfo {
334            size: 0,
335            align,
336            order: MemOrder::NotAtomic,
337            tbaa: None,
338            owns: 0,
339            restrict: Restrict::NONE,
340        }
341    }
342
343    /// An `alloca` of eight bytes, which is an address nothing outside the function knows.
344    fn local(build: &mut Builder<'_>) -> Value {
345        let mem = build.func().add_mem(MemInfo { size: 8, ..plain(8) });
346        build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
347    }
348
349    /// Runs the pass over the function with as much fuel as it wants and no module facts.
350    fn run(func: &mut Func) -> Stats {
351        LoadForward.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
352    }
353
354    /// The same, with what the oracle would know if this function were in that module.
355    fn run_in(func: &mut Func, module: &Module) -> Stats {
356        let mut an = crate::machine::fixtures::analyses().about(Arc::new(Outside::of(module)));
357        LoadForward.run(func, &mut an, &mut Fuel::unlimited())
358    }
359
360    /// How many loads are left in the function.
361    fn loads(func: &Func) -> usize {
362        func.blocks()
363            .flat_map(|block| func.insts(block).collect::<Vec<Inst>>())
364            .filter(|&inst| func[inst].opcode == Opcode::Load)
365            .count()
366    }
367
368    /// What the return statement hands back, after the pass has pointed it somewhere.
369    fn returned(func: &Func) -> Vec<Value> {
370        let block = func.blocks().next().expect("the function has a block");
371        let inst = func.terminator(block).expect("the block has a terminator");
372        func[func[inst].args].to_vec()
373    }
374
375    #[test]
376    fn a_load_of_what_a_store_just_wrote_is_the_stored_value() {
377        let (_, mut func, block) = blank();
378        let mut build = Builder::new(&mut func, block);
379        let slot = local(&mut build);
380        let wrote = build.iconst(Type::int(64), 7);
381        build.store(wrote, slot, plain(8), Flags::NONE);
382        let read = build.load(Type::int(64), slot, plain(8), Flags::NONE);
383        build.ret(&[read]);
384
385        let stats = run(&mut func);
386        assert_eq!(stats.count(crate::stats::Kind::Optimized, FORWARDED), 1);
387        assert_eq!(loads(&func), 0, "the load itself has to go, nothing else would remove it");
388        assert_eq!(returned(&func), vec![wrote]);
389    }
390
391    #[test]
392    fn the_second_load_of_an_address_is_what_the_first_one_read() {
393        let (_, mut func, block) = blank();
394        let mut build = Builder::new(&mut func, block);
395        let slot = local(&mut build);
396        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
397        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
398        let sum = build.binary(Opcode::Add, first, second, Flags::NONE);
399        build.ret(&[sum]);
400
401        let stats = run(&mut func);
402        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 1);
403        assert_eq!(loads(&func), 1, "one read of that address has to happen and only one");
404        let sum = returned(&func)[0];
405        let rucc_ir::Def::Result { inst, .. } = func[sum].def else { panic!("the add is gone") };
406        assert_eq!(func[func[inst].args].to_vec(), vec![first, first]);
407    }
408
409    #[test]
410    fn a_store_of_a_different_width_is_not_forwarded_through() {
411        let (_, mut func, block) = blank();
412        let mut build = Builder::new(&mut func, block);
413        let slot = local(&mut build);
414        let wrote = build.iconst(Type::int(32), 7);
415        build.store(wrote, slot, plain(4), Flags::NONE);
416        let read = build.load(Type::int(64), slot, plain(8), Flags::NONE);
417        build.ret(&[read]);
418
419        let stats = run(&mut func);
420        assert_eq!(stats.count(crate::stats::Kind::Missed, WIDTH), 1);
421        assert_eq!(loads(&func), 1, "a four byte store does not say what eight bytes hold");
422        assert_eq!(returned(&func), vec![read]);
423    }
424
425    #[test]
426    fn a_call_cannot_touch_a_local_whose_address_never_left_the_function() {
427        let (mut names, mut func, block) = blank();
428        let mut build = Builder::new(&mut func, block);
429        let slot = local(&mut build);
430        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
431        let signature = build.func().add_signature(Signature::new());
432        build.call(names.intern("g"), signature, &[]);
433        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
434        build.ret(&[first, second]);
435
436        // The escape analysis and nothing else. `g` is a name this module has never heard of and
437        // it could do anything at all, and it still cannot reach an address it was never given.
438        let stats = run(&mut func);
439        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 1);
440        assert_eq!(loads(&func), 1);
441        assert_eq!(returned(&func), vec![first, first]);
442    }
443
444    #[test]
445    fn a_call_handed_the_address_is_a_write_to_it() {
446        let (mut names, mut func, block) = blank();
447        let mut build = Builder::new(&mut func, block);
448        let slot = local(&mut build);
449        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
450        let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
451        build.call(names.intern("g"), signature, &[slot]);
452        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
453        build.ret(&[first, second]);
454
455        // The address escaped into the call, so the callee has it and nothing here says what it
456        // did with it. This is the half of the previous test that must not move.
457        let stats = run(&mut func);
458        assert!(!stats.changed());
459        assert_eq!(loads(&func), 2);
460    }
461
462    #[test]
463    fn a_store_to_an_address_that_cannot_be_this_one_leaves_it_alone() {
464        let (_, mut func, block) = blank();
465        let mut build = Builder::new(&mut func, block);
466        let slot = local(&mut build);
467        let other = local(&mut build);
468        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
469        build.store(first, other, plain(8), Flags::NONE);
470        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
471        build.ret(&[first, second]);
472
473        // Two allocas cannot be the same object, which is the oracle's first layer and the one
474        // that answers most of what real code asks. Until tamnd/rucc#1467 this was left on the
475        // table with a comment saying so.
476        let stats = run(&mut func);
477        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 1);
478        assert_eq!(loads(&func), 1);
479        assert_eq!(returned(&func), vec![first, first]);
480    }
481
482    #[test]
483    fn a_store_the_oracle_cannot_place_takes_the_table_with_it() {
484        let (mut names, mut func, entry) = blank();
485        let elsewhere = func.append_param(entry, Type::PTR);
486        let mut build = Builder::new(&mut func, entry);
487        let slot = local(&mut build);
488        // Handed out, so the local is one somebody else's pointer could be naming.
489        let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
490        build.call(names.intern("g"), signature, &[slot]);
491        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
492        build.store(first, elsewhere, plain(8), Flags::NONE);
493        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
494        build.ret(&[first, second]);
495
496        // A pointer handed in is an address the walk cannot follow back to an object, and this
497        // local's address did leave the function, so the escape layer has nothing to say either.
498        // Nothing left says these are two objects, so the store may be to this one.
499        let stats = run(&mut func);
500        assert!(!stats.changed());
501        assert_eq!(loads(&func), 2);
502    }
503
504    #[test]
505    fn a_store_through_a_pointer_from_nowhere_cannot_reach_a_local_that_stayed_here() {
506        let (_, mut func, entry) = blank();
507        let elsewhere = func.append_param(entry, Type::PTR);
508        let mut build = Builder::new(&mut func, entry);
509        let slot = local(&mut build);
510        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
511        build.store(first, elsewhere, plain(8), Flags::NONE);
512        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
513        build.ret(&[first, second]);
514
515        // The other half of the test above. Nothing ever handed this address out, so no pointer
516        // this function cannot follow is naming it, whatever that pointer is.
517        let stats = run(&mut func);
518        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 1);
519        assert_eq!(loads(&func), 1);
520    }
521
522    #[test]
523    fn a_volatile_load_is_not_reused_and_nothing_before_it_survives_it() {
524        let (_, mut func, block) = blank();
525        let mut build = Builder::new(&mut func, block);
526        let slot = local(&mut build);
527        let first = build.load(Type::int(64), slot, plain(8), Flags::VOLATILE);
528        let second = build.load(Type::int(64), slot, plain(8), Flags::VOLATILE);
529        let third = build.load(Type::int(64), slot, plain(8), Flags::NONE);
530        build.ret(&[first, second, third]);
531
532        let stats = run(&mut func);
533        assert!(!stats.changed(), "every volatile read has to happen");
534        assert_eq!(loads(&func), 3);
535    }
536
537    #[test]
538    fn the_safety_instrumentation_between_two_reads_leaves_the_table_standing() {
539        // A safety build puts a plane write and then a plane read beside almost every access, and
540        // for as long as those emptied the table this pass did close to nothing at
541        // `-fsafety=detect`. Neither is a write to the address it names, so the second load is
542        // still the first one's value.
543        let (_, mut func, block) = blank();
544        let mut build = Builder::new(&mut func, block);
545        let slot = local(&mut build);
546        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
547        let width = build.iconst(Type::int(64), 8);
548        let args = build.func().push_values(&[slot, width]);
549        build.inst(InstData { args, ..InstData::new(Opcode::MetaInit) }, &[]);
550        let args = build.func().push_values(&[slot]);
551        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
552        let args = build.func().push_values(&[capability, slot]);
553        build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
554        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
555        build.ret(&[first, second]);
556
557        let stats = run(&mut func);
558        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 1);
559        assert_eq!(loads(&func), 1);
560        assert_eq!(returned(&func), vec![first, first]);
561    }
562
563    #[test]
564    fn a_fence_between_two_reads_still_empties_the_table() {
565        // The other side of the line above. A fence touches memory, says nothing about where, and
566        // is not one of the plane opcodes, so it falls where everything unrecognized falls.
567        let (_, mut func, block) = blank();
568        let mut build = Builder::new(&mut func, block);
569        let slot = local(&mut build);
570        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
571        build.inst(InstData::new(Opcode::Fence), &[]);
572        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
573        build.ret(&[first, second]);
574
575        let stats = run(&mut func);
576        assert!(!stats.changed());
577        assert_eq!(loads(&func), 2);
578    }
579
580    #[test]
581    fn a_volatile_plane_access_is_read_as_volatile_first() {
582        // The volatile test comes before the opcode does, and it has to stay that way: an access
583        // the program asked to happen exactly as written happens, whatever the opcode would have
584        // said about which memory it is.
585        let (_, mut func, block) = blank();
586        let mut build = Builder::new(&mut func, block);
587        let slot = local(&mut build);
588        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
589        let width = build.iconst(Type::int(64), 8);
590        let args = build.func().push_values(&[slot, width]);
591        build.inst(
592            InstData { args, flags: Flags::VOLATILE, ..InstData::new(Opcode::MetaInit) },
593            &[],
594        );
595        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
596        build.ret(&[first, second]);
597
598        let stats = run(&mut func);
599        assert!(!stats.changed());
600        assert_eq!(loads(&func), 2);
601    }
602
603    #[test]
604    fn what_one_block_knows_does_not_reach_the_next_one() {
605        let (_, mut func, entry) = blank();
606        let next = func.create_block();
607        let mut build = Builder::new(&mut func, entry);
608        let slot = local(&mut build);
609        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
610        build.jump(next, &[]);
611        let mut build = Builder::new(&mut func, next);
612        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
613        build.ret(&[first, second]);
614
615        // The value is available and the `-O2` version of this pass finds it. Section 16.2 is
616        // explicit that this one does not, because reaching it means memory SSA.
617        let stats = run(&mut func);
618        assert!(!stats.changed());
619        assert_eq!(loads(&func), 2);
620    }
621
622    #[test]
623    fn a_chain_of_reads_all_come_from_the_first_one() {
624        let (_, mut func, block) = blank();
625        let mut build = Builder::new(&mut func, block);
626        let slot = local(&mut build);
627        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
628        build.load(Type::int(64), slot, plain(8), Flags::NONE);
629        let third = build.load(Type::int(64), slot, plain(8), Flags::NONE);
630        build.ret(&[third]);
631
632        let stats = run(&mut func);
633        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 2);
634        assert_eq!(loads(&func), 1);
635        assert_eq!(returned(&func), vec![first]);
636    }
637
638    #[test]
639    fn a_store_of_a_value_the_pass_is_removing_forwards_to_where_that_value_went() {
640        let (_, mut func, block) = blank();
641        let mut build = Builder::new(&mut func, block);
642        let slot = local(&mut build);
643        let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
644        let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
645        build.store(second, slot, plain(8), Flags::NONE);
646        let third = build.load(Type::int(64), slot, plain(8), Flags::NONE);
647        build.ret(&[third]);
648
649        // The store writes a value this pass is in the middle of taking away, so the third load
650        // has to land one step further back than what the table says. Landing on the second load's
651        // result would leave the function reading an instruction that is no longer in it.
652        let stats = run(&mut func);
653        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 1);
654        assert_eq!(stats.count(crate::stats::Kind::Optimized, FORWARDED), 1);
655        assert_eq!(loads(&func), 1);
656        assert_eq!(returned(&func), vec![first]);
657    }
658
659    #[test]
660    fn a_call_to_something_declared_to_read_no_memory_writes_none_either() {
661        // The module's half of the oracle, which is the one thing a pass handed a function cannot
662        // work out for itself. The address escaped into an earlier call, so the escape layer has
663        // nothing left to say and what answers is `g` being declared `const`.
664        let mut names = Interner::new();
665        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
666        let mut module = Module::new(names.intern("t.c"), &target);
667        let quiet = names.intern("g");
668        let mut callee = Func::new(quiet, Signature::new().with_params(&[Type::PTR]));
669        callee.attrs = Attrs { set: AttrSet::READNONE, ..Attrs::default() };
670        module.add_func(callee);
671
672        let name = names.intern("f");
673        let build = || {
674            let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(64)]));
675            let block = func.create_block();
676            let mut build = Builder::new(&mut func, block);
677            let slot = local(&mut build);
678            let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
679            build.call(quiet, signature, &[slot]);
680            let first = build.load(Type::int(64), slot, plain(8), Flags::NONE);
681            build.call(quiet, signature, &[slot]);
682            let second = build.load(Type::int(64), slot, plain(8), Flags::NONE);
683            build.ret(&[first, second]);
684            func
685        };
686
687        assert!(!run(&mut build()).changed(), "without the module there is nothing to read");
688        let mut func = build();
689        let stats = run_in(&mut func, &module);
690        assert_eq!(stats.count(crate::stats::Kind::Optimized, REUSED), 1);
691        assert_eq!(loads(&func), 1);
692    }
693
694    #[test]
695    fn without_fuel_the_load_stays_and_the_chance_is_still_counted() {
696        let (_, mut func, block) = blank();
697        let mut build = Builder::new(&mut func, block);
698        let slot = local(&mut build);
699        let wrote = build.iconst(Type::int(64), 7);
700        build.store(wrote, slot, plain(8), Flags::NONE);
701        let read = build.load(Type::int(64), slot, plain(8), Flags::NONE);
702        build.ret(&[read]);
703
704        let stats =
705            LoadForward.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(0));
706        assert!(!stats.changed());
707        assert_eq!(stats.count(crate::stats::Kind::Missed, NO_FUEL), 1);
708        assert_eq!(loads(&func), 1);
709    }
710}