Skip to main content

rucc_opt/
discharge.rs

1//! Taking out a safety check whose answer is already known.
2//!
3//! Design: `spec/safe-memory/07-check-elimination.md` section 7.3, which is the first half of the
4//! Tier E budget. `rucc-safety` puts a bounds check and a lifetime check in front of every access
5//! and does not try to be clever about it, on purpose: a walk that inserts everything is a walk
6//! anybody can read, and every check that is not needed is meant to be taken out here instead.
7//! This pass takes them out, and it does the case document 07 expects to be worth the most and to
8//! be the easiest to get right, which is a second access to bytes an earlier access already had
9//! checked. Both checks in front of that access are the pass's business, because `rucc-safety`
10//! emits the pair and taking out one of a pair is half a saving.
11//!
12//! # The two halves
13//!
14//! Section 7.7 asks for the pass and the condition to be separate things, and they are. What is in
15//! this file is a walk: which check runs before which, which pointer was computed from which, and
16//! how far apart two addresses are. Nothing here decides whether that is enough. The condition
17//! under which a check may go is a rule in `rules/safety.rules`, a solver has to agree with it
18//! before this crate finishes building, and `crate::rules::safety` is the table it compiles into.
19//!
20//! The split is worth the trouble because the two halves fail differently. A walk that gets the
21//! context wrong is a bug of the ordinary kind, and section 14.3's differential check accounting,
22//! which runs the instrumented program with every check and again with the discharged ones gone,
23//! is what looks for it. A removal condition that is wrong is arithmetic that is off at the ends of
24//! the type. It gives the right answer on every test anybody writes and lets one access through in
25//! the one case nobody thought of, and nothing observes that until somebody exploits it.
26//!
27//! # What it establishes and what it asks
28//!
29//! Walking the dominator tree from the entry, the pass carries a set of facts. A `check_bounds`
30//! that stays is a fact, because a check that passes says the bytes it was about lie inside one
31//! storage instance, and a check that fails does not return. A fact is remembered as the pointer's
32//! base and the constant offset from it, which is what a chain of `ptr_add` over constants comes
33//! to, plus how many bytes the access covers.
34//!
35//! At the next `check_bounds`, the pointer is normalized the same way. When a fact shares its base,
36//! the distance between the two accesses is the difference of the two offsets, and that is a number
37//! this pass has rather than a claim it makes: both addresses are the same value plus a constant.
38//! The question of whether the later bytes are inside the earlier ones is then handed to the table,
39//! which answers it in sixty four bit arithmetic rather than in the offsets, and the check goes
40//! only if the answer is yes.
41//!
42//! The capability operand has to be the `cap_of` of the check's own pointer, which is the shape
43//! `rucc-safety` emits and the shape the argument needs. The check being removed asks whether its
44//! bytes are inside the instance that owns its own pointer, its pointer is inside the range the
45//! earlier check established, and that range is inside one instance, so the answer is yes. A check
46//! whose capability came from somewhere else is asking about a different instance and is left
47//! alone. Nothing is required of the earlier check's capability, because all that is used of it is
48//! that the check passed, and a check that passed put its bytes inside one instance whatever
49//! capability it named.
50//!
51//! # The lifetime half, and what it borrows from the other one
52//!
53//! A `check_live` that stays is a fact too, and a smaller one than it looks: it says the storage
54//! instance holding its own address is alive, and it says nothing about the address four bytes
55//! along, because that address might be in a different instance. On its own that fact discharges
56//! only a second lifetime check of the very same address, and the shape `rucc-safety` emits is a
57//! lifetime check per field rather than per object, so on its own it would almost never fire.
58//!
59//! What makes it fire is the bounds fact sitting next to it. A `check_bounds` that passed put its
60//! whole range inside one instance, so if the lifetime check's address is in that range, the
61//! instance that was found alive is the instance the whole range is in, and the whole range is
62//! alive. So a lifetime fact is recorded as the widest checked range containing its address, and a
63//! later lifetime check is asked about as a single byte. The question of whether that byte is in
64//! that range is the same question the bounds half asks, put to the same rule.
65//!
66//! The order the two arrive in is what makes this work rather than a coincidence to be careful
67//! about: `rucc-safety` emits the bounds check first and the lifetime check second, so the range is
68//! established by the time there is a lifetime fact to widen. A lifetime check that arrives with no
69//! range around it keeps the narrow fact, which is correct and worth little.
70//!
71//! # Why a call throws the facts away, and which calls do not
72//!
73//! Section 7.3 says nothing kills a bounds fact except a redefinition of the capability, which in
74//! SSA is never, and this pass is stricter than that: a call, or anything else this pass cannot see
75//! through, drops every fact it is carrying.
76//!
77//! The case is a `free` and then an allocation of something smaller at the same address. The range
78//! established before the call is no longer inside one instance after it, and what document 07
79//! leaves that to is the lifetime judgement rather than this one. Today's lifetime check is about
80//! the address rather than about the version the capability was taken at, so it would not refuse
81//! the access either, and a rate this pass reports is worth less than a hole it opens. The strict
82//! version is what is written first.
83//!
84//! A `meta_end` and a `meta_transfer` drop the facts as well. Nothing emits either one yet, so
85//! this costs nothing today and is the difference between conservative and wrong on the day the
86//! instrumentation starts ending lifetimes. `crate::nofree` treats them the same way.
87//!
88//! A call that says it reaches nothing which can free is the exception, and it is not this pass
89//! being trusting. `crate::nofree` works the answer out over the whole module before the pipeline
90//! starts and writes it onto the call site as [`Flags::NOFREE`], because the fact belongs to the
91//! callee and a pass is given one function. Reading it here is reading what the IR says, the same
92//! way the pass reads an opcode. Nothing else about a call is believed: the facts still go across
93//! an unmarked call, a call through an address, and inline assembly.
94//!
95//! What the strictness still costs is measured rather than guessed. A check that a fact would have
96//! covered if a call had not intervened is counted, so `-fopt-info-missed` says per function what
97//! is left to win.
98
99use rucc_ir::{Def, Extra, Flags, Func, Inst, Opcode, Value};
100
101use crate::rules::{Piece, Subject, Table, safety};
102use crate::{Analyses, Fuel, Pass, Preserved, Stats};
103
104/// Recorded once for each bounds check taken out.
105const REMOVED: &str = "bounds check removed, a dominating check covers the same bytes";
106
107/// Recorded once for each lifetime check taken out.
108const REMOVED_LIVE: &str = "lifetime check removed, a dominating check covers the same storage";
109
110/// Recorded for a bounds check that would have gone if there had been fuel for it.
111const NO_FUEL: &str = "bounds check kept, the pass ran out of fuel";
112
113/// Recorded for a lifetime check that would have gone if there had been fuel for it.
114const NO_FUEL_LIVE: &str = "lifetime check kept, the pass ran out of fuel";
115
116/// Recorded for a bounds check a call cost, which is the honest price of the paragraph above.
117///
118/// This one is worth reading rather than skipping. It is the number of checks that are still being
119/// paid for because `crate::nofree` could not vouch for a call, so it says per function what the
120/// rest of section 7.5's summary work would be worth before anybody writes it.
121const PAST_A_CALL: &str =
122    "bounds check kept, a call between it and the check that covers it might free";
123
124/// The same, for a lifetime check. Section 8.8 is about this number rather than the one above.
125const PAST_A_CALL_LIVE: &str =
126    "lifetime check kept, a call between it and the check that covers it might free";
127
128/// Recorded for a bounds check whose operands this pass cannot read.
129const UNKNOWN_SHAPE: &str = "bounds check left alone, its pointer is not a base and a constant";
130
131/// Recorded for a lifetime check whose operands this pass cannot read.
132const UNKNOWN_SHAPE_LIVE: &str =
133    "lifetime check left alone, its pointer is not a base and a constant";
134
135/// The pass. It holds nothing, because everything it works out is about one function.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct Discharge;
138
139impl Pass for Discharge {
140    fn name(&self) -> &'static str {
141        "discharge"
142    }
143
144    fn describe(&self) -> &'static str {
145        "a bounds or lifetime check a dominating check already covered is removed"
146    }
147
148    fn preserves(&self) -> Preserved {
149        // Instructions go and blocks do not. A check is not a terminator and removing one leaves
150        // every edge where it was.
151        Preserved::ALL
152    }
153
154    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
155        let mut stats = Stats::new();
156        let Some(entry) = func.entry() else { return stats };
157        let dom = an.dominators(func).clone();
158
159        // The walk is a stack rather than recursion because the dominator tree of a long chain of
160        // blocks is as deep as the function is long, and a pass is not a place to find that out.
161        // Each block carries its own copy of what holds at its start, which is what makes a fact a
162        // call killed in one arm of a branch still hold in the other.
163        let mut going: Vec<(Inst, &'static str)> = Vec::new();
164        let mut work = vec![(entry, Scope::default())];
165        while let Some((block, mut scope)) = work.pop() {
166            for inst in func.insts(block).collect::<Vec<Inst>>() {
167                if opaque(func, inst) {
168                    scope.forget();
169                    continue;
170                }
171                match func[inst].opcode {
172                    Opcode::CheckBounds => {
173                        let Some(asked) = about(func, inst) else {
174                            stats.missed(UNKNOWN_SHAPE);
175                            continue;
176                        };
177                        if !scope.bounds.covers(&asked) {
178                            if scope.bounds.covered_before(&asked) {
179                                stats.missed(PAST_A_CALL);
180                            }
181                            // A check that stays is a check that runs, and a check that runs
182                            // establishes what it was about. One that was removed establishes
183                            // nothing new: whatever covered it covers everything it would have.
184                            scope.bounds.held.push(asked);
185                            continue;
186                        }
187                        if !fuel.take() {
188                            stats.missed(NO_FUEL);
189                            scope.bounds.held.push(asked);
190                            continue;
191                        }
192                        going.push((inst, REMOVED));
193                    }
194                    Opcode::CheckLive => {
195                        let Some(asked) = alive(func, inst) else {
196                            stats.missed(UNKNOWN_SHAPE_LIVE);
197                            continue;
198                        };
199                        if !scope.alive.covers(&asked) {
200                            if scope.alive.covered_before(&asked) {
201                                stats.missed(PAST_A_CALL_LIVE);
202                            }
203                            scope.alive.held.push(widened(&scope.bounds, asked));
204                            continue;
205                        }
206                        if !fuel.take() {
207                            stats.missed(NO_FUEL_LIVE);
208                            scope.alive.held.push(widened(&scope.bounds, asked));
209                            continue;
210                        }
211                        going.push((inst, REMOVED_LIVE));
212                    }
213                    _ => continue,
214                }
215            }
216            for child in dom.children(block) {
217                work.push((child, scope.clone()));
218            }
219        }
220
221        for (inst, why) in going {
222            func.remove_inst(inst);
223            stats.optimized(why);
224        }
225        stats
226    }
227}
228
229/// A range of bytes some check has already been passed on, or is being asked about.
230///
231/// The address is kept as the value it was computed from and the constant distance from it, rather
232/// than as the pointer itself, because that is what makes two of these comparable: the whole of
233/// what this pass knows about two addresses is that they are one value plus two constants.
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235struct Fact {
236    /// The value the address was computed from.
237    base: Value,
238    /// How far past it the access starts.
239    offset: i128,
240    /// How many bytes it covers.
241    size: i128,
242}
243
244/// One kind of fact, and what has become of it.
245#[derive(Debug, Clone, Default)]
246struct Known {
247    /// The ranges a check has been passed on and nothing has cast doubt on since.
248    held: Vec<Fact>,
249    /// The ones a call threw away, kept only so that the cost of throwing them away is a number
250    /// somebody can read rather than a paragraph somebody has to believe.
251    lost: Vec<Fact>,
252}
253
254impl Known {
255    /// Whether something still standing answers this.
256    fn covers(&self, asked: &Fact) -> bool {
257        self.held.iter().any(|fact| covers(fact, asked))
258    }
259
260    /// Whether something would have answered it before a call came along.
261    fn covered_before(&self, asked: &Fact) -> bool {
262        self.lost.iter().any(|fact| covers(fact, asked))
263    }
264
265    /// Gives up everything, because something happened that this pass cannot see through.
266    fn forget(&mut self) {
267        self.lost.append(&mut self.held);
268    }
269}
270
271/// What holds where the walk has got to.
272///
273/// The two kinds are apart because they are killed together and answered separately: a range being
274/// inside one instance and that instance being alive are different claims, and reporting them as
275/// one number would hide which of the two a check is still being paid for.
276#[derive(Debug, Clone, Default)]
277struct Scope {
278    /// Ranges a `check_bounds` established are inside one storage instance.
279    bounds: Known,
280    /// Ranges a `check_live` established are in an instance that is alive.
281    alive: Known,
282}
283
284impl Scope {
285    /// Gives up every fact of either kind.
286    fn forget(&mut self) {
287        self.bounds.forget();
288        self.alive.forget();
289    }
290}
291
292/// Whether this instruction could do something to memory that this pass cannot account for.
293///
294/// A call is the whole of it, in every spelling, and inline assembly with it. A `tail_call` ends
295/// the block and there is nothing after it to protect, and it is here anyway so that the reason a
296/// fact survives is never that the walk did not think of something.
297///
298/// A call carrying [`Flags::NOFREE`] reaches nothing that ends a lifetime, so there is nothing for
299/// it to have done to the bytes an earlier check was passed on. `crate::nofree` is what put the
300/// flag there and what argues for it.
301///
302/// A `meta_end` and a `meta_transfer` end a lifetime by saying so, which is the plainest way for a
303/// fact to stop being true, and neither is emitted today.
304fn opaque(func: &Func, inst: Inst) -> bool {
305    match func[inst].opcode {
306        Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => {
307            !func[inst].flags.contains(Flags::NOFREE)
308        }
309        Opcode::InlineAsm | Opcode::MetaEnd | Opcode::MetaTransfer => true,
310        _ => false,
311    }
312}
313
314/// What a `check_bounds` is about, when it is one this pass can read.
315fn about(func: &Func, check: Inst) -> Option<Fact> {
316    let (base, offset) = addressed(func, check)?;
317    let Extra::Mem(info) = func[check].extra else { return None };
318    Some(Fact { base, offset, size: i128::from(func[info].size) })
319}
320
321/// What a `check_live` is about, when it is one this pass can read.
322///
323/// One byte, because that is the whole of what the check says: the instance holding this address
324/// is alive, and nothing about the address next door. The widening to a range that makes the fact
325/// useful is [`widened`], and it needs a bounds fact to do it.
326fn alive(func: &Func, check: Inst) -> Option<Fact> {
327    let (base, offset) = addressed(func, check)?;
328    Some(Fact { base, offset, size: 1 })
329}
330
331/// The address a check is about, as a base and a constant.
332///
333/// The capability has to be the `cap_of` of the check's own pointer. That is the shape
334/// `rucc-safety` emits and it is what the removal argument in the module comment needs, so a check
335/// that does not have it is not a check this pass has anything to say about.
336fn addressed(func: &Func, check: Inst) -> Option<(Value, i128)> {
337    let args = &func[func[check].args];
338    let &capability = args.first()?;
339    let &pointer = args.get(1)?;
340    if operand_of(func, capability, Opcode::CapOf, 0) != Some(pointer) {
341        return None;
342    }
343    Some(normal(func, pointer))
344}
345
346/// A lifetime fact grown from one address to the checked range it sits in.
347///
348/// The argument is in the module comment: a `check_bounds` that passed put its whole range inside
349/// one instance, so the instance this lifetime check found alive is the instance that range is in.
350/// With no range around the address the fact stays as it came, which is correct and answers only a
351/// repeat of the very same check.
352fn widened(bounds: &Known, asked: Fact) -> Fact {
353    bounds.held.iter().find(|fact| covers(fact, &asked)).copied().unwrap_or(asked)
354}
355
356/// The value an address was computed from, and how far past it the address is.
357///
358/// A `ptr_add` over a constant is walked through, and anything else is where the answer stops. The
359/// arithmetic here is exact because it is done in `i128` over offsets that came out of the IR as
360/// sixty four bit constants, and whether it is small enough to mean anything at sixty four bits is
361/// the rule's question rather than this function's.
362fn normal(func: &Func, value: Value) -> (Value, i128) {
363    let mut base = value;
364    let mut offset: i128 = 0;
365    while let Some((from, step)) = walked(func, base) {
366        let Some(sum) = offset.checked_add(step) else { break };
367        base = from;
368        offset = sum;
369    }
370    (base, offset)
371}
372
373/// The pointer one `ptr_add` over a constant was computed from, and by how much.
374fn walked(func: &Func, value: Value) -> Option<(Value, i128)> {
375    let from = operand_of(func, value, Opcode::PtrAdd, 0)?;
376    let by = operand_of(func, value, Opcode::PtrAdd, 1)?;
377    Some((from, constant(func, by)?))
378}
379
380/// Operand `index` of the instruction that produced `value`, when that instruction is `opcode`.
381fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
382    let Def::Result { inst, .. } = func[value].def else { return None };
383    if func[inst].opcode != opcode {
384        return None;
385    }
386    func[func[inst].args].get(index).copied()
387}
388
389/// The value of an integer constant, read with its own sign.
390fn constant(func: &Func, value: Value) -> Option<i128> {
391    let Def::Result { inst, .. } = func[value].def else { return None };
392    if func[inst].opcode != Opcode::IConst {
393        return None;
394    }
395    let Extra::Imm(imm) = func[inst].extra else { return None };
396    let ty = func[value].ty;
397    ty.is_int().then(|| func[imm].signed(ty))
398}
399
400/// Whether an established fact answers the check being asked about.
401///
402/// This function decides nothing. It puts the two together into the term the rule file is written
403/// about and asks the table, which is the whole of section 7.7's split: the paragraph above worked
404/// out that the two addresses are one value a constant apart, and whether that is enough is
405/// somebody's proof rather than this file's opinion.
406fn covers(fact: &Fact, asked: &Fact) -> bool {
407    if fact.base != asked.base {
408        return false;
409    }
410    let Some(delta) = asked.offset.checked_sub(fact.offset) else { return false };
411    let mut question = Question::default();
412    let at = question.opaque();
413    let at = question.app("value.i64", &[at]);
414    let span = question.number(fact.size);
415    let span = question.app("iconst.i64", &[span]);
416    let far = question.number(delta);
417    let far = question.app("iconst.i64", &[far]);
418    let reach = question.number(asked.size);
419    let reach = question.app("iconst.i64", &[reach]);
420    let term = question.app("covered.i64", &[at, span, far, reach]);
421    match safety::TABLE.find(&question, term) {
422        Some(found) => yes(&safety::TABLE, found.rule),
423        None => false,
424    }
425}
426
427/// Whether the rule that fired answers yes.
428///
429/// A discharge rule replaces the question with a constant, and one is yes. Every rule in the file
430/// answers that today, and reading it off the rule rather than assuming it is what keeps this
431/// honest on the day one of them answers something else.
432fn yes(table: &Table, rule: usize) -> bool {
433    matches!(table.rules[rule].replacement, [Piece::App { .. }, Piece::Int(1)])
434}
435
436/// A term built to be asked about, and nothing else.
437///
438/// The rules are matched against this rather than against the function, because what is being asked
439/// about is not in the function: it is what the walk worked out about two of its instructions. So
440/// the subject is a small arena of exactly the term being asked, built fresh for each question and
441/// thrown away with the answer.
442#[derive(Debug, Default)]
443struct Question {
444    held: Vec<Held>,
445}
446
447/// One node of that term.
448#[derive(Debug)]
449enum Held {
450    /// A number the pattern can read and a guard can be about.
451    Int(i128),
452    /// A head and its arguments.
453    App(&'static str, Vec<usize>),
454    /// Something with no structure, which is how an address the rule only names is written.
455    Opaque,
456}
457
458impl Question {
459    /// Adds a constant and gives back where it went.
460    ///
461    /// Named for what it adds rather than for what it holds, because the arena also answers
462    /// [`Subject::int`] and one name for the two would read as though building a term and asking
463    /// about one were the same act.
464    fn number(&mut self, value: i128) -> usize {
465        self.held.push(Held::Int(value));
466        self.held.len() - 1
467    }
468
469    /// Adds an application of `head` to what is already in the arena.
470    fn app(&mut self, head: &'static str, args: &[usize]) -> usize {
471        self.held.push(Held::App(head, args.to_vec()));
472        self.held.len() - 1
473    }
474
475    /// Adds something the rule can bind and cannot look inside.
476    fn opaque(&mut self) -> usize {
477        self.held.push(Held::Opaque);
478        self.held.len() - 1
479    }
480}
481
482impl Subject for Question {
483    type Node = usize;
484
485    fn head(&self, node: usize) -> Option<(&str, usize)> {
486        match &self.held[node] {
487            Held::App(head, args) => Some((head, args.len())),
488            Held::Int(_) | Held::Opaque => None,
489        }
490    }
491
492    fn arg(&self, node: usize, index: usize) -> usize {
493        match &self.held[node] {
494            Held::App(_, args) => args[index],
495            // The walk only asks for an argument `head` said was there, so this is unreachable
496            // rather than a case with an answer.
497            Held::Int(_) | Held::Opaque => unreachable!("only an application has arguments"),
498        }
499    }
500
501    fn int(&self, node: usize) -> Option<i128> {
502        match self.held[node] {
503            Held::Int(value) => Some(value),
504            Held::App(..) | Held::Opaque => None,
505        }
506    }
507
508    fn same(&self, a: usize, b: usize) -> bool {
509        // Every node of a question is written once, so two places holding one thing are one place.
510        a == b
511    }
512}
513
514#[cfg(test)]
515mod tests {
516    use rucc_base::Interner;
517    use rucc_ir::{
518        AsmInfo, Block, BlockCallList, Builder, Extra, Flags, Func, InstData, MemInfo, MemOrder,
519        Opcode, Restrict, Signature, Type, Value,
520    };
521
522    use super::{Discharge, Fact};
523    use crate::stats::Kind;
524    use crate::{Analyses, Fuel, Pass};
525
526    /// A function taking a pointer, with one block, ready to have accesses put in it.
527    fn blank() -> (Interner, Func, Block, Value) {
528        let mut names = Interner::new();
529        let name = names.intern("f");
530        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR]));
531        let block = func.create_block();
532        let pointer = func.append_param(block, Type::PTR);
533        (names, func, block, pointer)
534    }
535
536    /// Puts `cap_of` and a `check_bounds` over `size` bytes at `pointer` into a block.
537    ///
538    /// The same shape `rucc-safety` emits, written out here rather than reached for, because
539    /// `rucc-opt` is rank 9 alongside `rucc-safety` and cannot depend on it.
540    fn check(build: &mut Builder<'_>, pointer: Value, size: u64) {
541        let args = build.func().push_values(&[pointer]);
542        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
543        let info = MemInfo {
544            size,
545            align: 1,
546            order: MemOrder::NotAtomic,
547            tbaa: None,
548            restrict: Restrict::NONE,
549        };
550        let args = build.func().push_values(&[capability, pointer]);
551        let extra = Extra::Mem(build.func().add_mem(info));
552        build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
553    }
554
555    /// Puts `cap_of` and a `check_live` at `pointer` into a block.
556    ///
557    /// `rucc-safety` emits this straight after the bounds check for the same access and shares the
558    /// one `cap_of` between the two. Sharing it is not what the pass reads, so the tests build a
559    /// second one, which is the harder shape for it to accept.
560    fn live(build: &mut Builder<'_>, pointer: Value) {
561        let args = build.func().push_values(&[pointer]);
562        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
563        let args = build.func().push_values(&[capability, pointer]);
564        build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
565    }
566
567    /// Both checks in front of one access, in the order `rucc-safety` writes them.
568    fn access(build: &mut Builder<'_>, pointer: Value, size: u64) {
569        check(build, pointer, size);
570        live(build, pointer);
571    }
572
573    /// A pointer `bytes` past another one.
574    fn past(build: &mut Builder<'_>, pointer: Value, bytes: i128) -> Value {
575        let offset = build.iconst(Type::int(64), bytes);
576        let args = build.func().push_values(&[pointer, offset]);
577        build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
578    }
579
580    /// How many checks are left in a function.
581    fn checks(func: &Func) -> usize {
582        func.blocks()
583            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
584            .filter(|&inst| func[inst].opcode == Opcode::CheckBounds)
585            .count()
586    }
587
588    /// How many lifetime checks are left in a function.
589    fn lives(func: &Func) -> usize {
590        func.blocks()
591            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
592            .filter(|&inst| func[inst].opcode == Opcode::CheckLive)
593            .count()
594    }
595
596    fn run(func: &mut Func) -> crate::Stats {
597        Discharge.run(func, &mut Analyses::new(), &mut Fuel::unlimited())
598    }
599
600    #[test]
601    fn a_second_check_of_the_same_bytes_goes() {
602        let (_, mut func, block, pointer) = blank();
603        let mut build = Builder::new(&mut func, block);
604        check(&mut build, pointer, 4);
605        check(&mut build, pointer, 4);
606        build.ret(&[]);
607        let stats = run(&mut func);
608        assert_eq!(checks(&func), 1);
609        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
610    }
611
612    #[test]
613    fn a_check_of_bytes_inside_a_checked_range_goes() {
614        // Four bytes at offset four, inside sixteen bytes at offset zero. This is the shape the
615        // whole pass is for: a struct whose fields are read one after another through one pointer.
616        let (_, mut func, block, pointer) = blank();
617        let mut build = Builder::new(&mut func, block);
618        check(&mut build, pointer, 16);
619        let field = past(&mut build, pointer, 4);
620        check(&mut build, field, 4);
621        build.ret(&[]);
622        run(&mut func);
623        assert_eq!(checks(&func), 1);
624    }
625
626    #[test]
627    fn a_check_of_bytes_past_the_end_of_a_checked_range_stays() {
628        // Four bytes at offset fourteen is two bytes past the end of the sixteen that were
629        // checked, and those two bytes are what the check is for.
630        let (_, mut func, block, pointer) = blank();
631        let mut build = Builder::new(&mut func, block);
632        check(&mut build, pointer, 16);
633        let over = past(&mut build, pointer, 14);
634        check(&mut build, over, 4);
635        build.ret(&[]);
636        assert!(!run(&mut func).changed());
637        assert_eq!(checks(&func), 2);
638    }
639
640    #[test]
641    fn a_check_of_bytes_before_a_checked_range_stays() {
642        // The guard's `delta` is not negative, and this is why. A read four bytes below what was
643        // checked is a read of somebody else's memory, and it is the bug the check exists for.
644        let (_, mut func, block, pointer) = blank();
645        let mut build = Builder::new(&mut func, block);
646        check(&mut build, pointer, 16);
647        let under = past(&mut build, pointer, -4);
648        check(&mut build, under, 4);
649        build.ret(&[]);
650        assert!(!run(&mut func).changed());
651        assert_eq!(checks(&func), 2);
652    }
653
654    #[test]
655    fn a_check_through_a_pointer_nothing_relates_to_the_first_stays() {
656        let mut names = Interner::new();
657        let name = names.intern("two");
658        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::PTR]));
659        let block = func.create_block();
660        let one = func.append_param(block, Type::PTR);
661        let other = func.append_param(block, Type::PTR);
662        let mut build = Builder::new(&mut func, block);
663        check(&mut build, one, 16);
664        check(&mut build, other, 4);
665        build.ret(&[]);
666        assert!(!run(&mut func).changed());
667        assert_eq!(checks(&func), 2);
668    }
669
670    #[test]
671    fn a_check_a_call_stands_between_stays_and_is_counted() {
672        // The conservatism the module comment argues for, and the number that says what it costs.
673        let (mut names, mut func, block, pointer) = blank();
674        let mut build = Builder::new(&mut func, block);
675        check(&mut build, pointer, 16);
676        let callee = names.intern("might_free");
677        let signature = build.func().add_signature(Signature::new());
678        build.call(callee, signature, &[]);
679        check(&mut build, pointer, 4);
680        build.ret(&[]);
681        let stats = run(&mut func);
682        assert!(!stats.changed());
683        assert_eq!(checks(&func), 2);
684        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
685    }
686
687    #[test]
688    fn a_check_a_call_that_cannot_free_stands_between_goes() {
689        // The other side of the paragraph above. The summary said this call reaches nothing that
690        // ends a lifetime, so the range the first check established is still one range.
691        let (mut names, mut func, block, pointer) = blank();
692        let mut build = Builder::new(&mut func, block);
693        check(&mut build, pointer, 16);
694        let callee = names.intern("counts_them");
695        let signature = build.func().add_signature(Signature::new());
696        let call = build.call(callee, signature, &[]);
697        check(&mut build, pointer, 4);
698        build.ret(&[]);
699        func[call].flags |= Flags::NOFREE;
700        let stats = run(&mut func);
701        assert_eq!(checks(&func), 1);
702        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
703        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
704    }
705
706    #[test]
707    fn inline_assembly_throws_the_facts_away_whatever_it_is_flagged() {
708        // There is no flag that would make this safe. The template is text the compiler does not
709        // read, so nothing worked anything out about what it reaches.
710        let (mut names, mut func, block, pointer) = blank();
711        let mut build = Builder::new(&mut func, block);
712        check(&mut build, pointer, 16);
713        build.inline_asm(
714            AsmInfo {
715                template: names.intern("nop"),
716                constraints: names.intern(""),
717                clobbers: names.intern(""),
718                targets: BlockCallList::EMPTY,
719            },
720            &[],
721            &[],
722            Flags::NONE,
723        );
724        check(&mut build, pointer, 4);
725        build.ret(&[]);
726        let stats = run(&mut func);
727        assert!(!stats.changed());
728        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
729    }
730
731    #[test]
732    fn a_check_that_only_one_path_covers_stays() {
733        // The dominator tree is what makes this right. The check in the arm covers the one in the
734        // join on one path and not on the other, and a check that goes has to be one that ran.
735        let (_, mut func, block, pointer) = blank();
736        let arm = func.create_block();
737        let join = func.create_block();
738        let mut build = Builder::new(&mut func, block);
739        let condition = build.iconst(Type::int(32), 1);
740        build.br_if(condition, arm, &[], join, &[]);
741        let mut build = Builder::new(&mut func, arm);
742        check(&mut build, pointer, 16);
743        build.jump(join, &[]);
744        let mut build = Builder::new(&mut func, join);
745        check(&mut build, pointer, 4);
746        build.ret(&[]);
747        assert!(!run(&mut func).changed());
748        assert_eq!(checks(&func), 2);
749    }
750
751    #[test]
752    fn a_check_a_dominating_block_covers_goes() {
753        let (_, mut func, block, pointer) = blank();
754        let after = func.create_block();
755        let mut build = Builder::new(&mut func, block);
756        check(&mut build, pointer, 16);
757        build.jump(after, &[]);
758        let mut build = Builder::new(&mut func, after);
759        let field = past(&mut build, pointer, 8);
760        check(&mut build, field, 8);
761        build.ret(&[]);
762        run(&mut func);
763        assert_eq!(checks(&func), 1);
764    }
765
766    #[test]
767    fn fuel_stops_the_removing_and_not_the_looking() {
768        let (_, mut func, block, pointer) = blank();
769        let mut build = Builder::new(&mut func, block);
770        check(&mut build, pointer, 4);
771        check(&mut build, pointer, 4);
772        check(&mut build, pointer, 4);
773        build.ret(&[]);
774        let mut fuel = Fuel::of(1);
775        let stats = Discharge.run(&mut func, &mut Analyses::new(), &mut fuel);
776        assert_eq!(checks(&func), 2);
777        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
778        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
779    }
780
781    #[test]
782    fn a_second_lifetime_check_of_the_same_address_goes() {
783        // The narrow fact on its own, with no range around it to widen into.
784        let (_, mut func, block, pointer) = blank();
785        let mut build = Builder::new(&mut func, block);
786        live(&mut build, pointer);
787        live(&mut build, pointer);
788        build.ret(&[]);
789        let stats = run(&mut func);
790        assert_eq!(lives(&func), 1);
791        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
792    }
793
794    #[test]
795    fn a_lifetime_check_inside_a_checked_range_goes() {
796        // The shape the pass is for, with both halves of it. Sixteen bytes are checked and found
797        // alive, then a field four bytes in is read, and neither check in front of it survives.
798        let (_, mut func, block, pointer) = blank();
799        let mut build = Builder::new(&mut func, block);
800        access(&mut build, pointer, 16);
801        let field = past(&mut build, pointer, 4);
802        access(&mut build, field, 4);
803        build.ret(&[]);
804        let stats = run(&mut func);
805        assert_eq!(checks(&func), 1);
806        assert_eq!(lives(&func), 1);
807        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
808        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
809    }
810
811    #[test]
812    fn a_lifetime_check_outside_every_checked_range_stays() {
813        // Four bytes at offset twenty are past the sixteen that were checked, so nothing says the
814        // address is in the instance that was found alive, and it might be in no instance at all.
815        let (_, mut func, block, pointer) = blank();
816        let mut build = Builder::new(&mut func, block);
817        access(&mut build, pointer, 16);
818        let over = past(&mut build, pointer, 20);
819        live(&mut build, over);
820        build.ret(&[]);
821        assert!(!run(&mut func).changed());
822        assert_eq!(lives(&func), 2);
823    }
824
825    #[test]
826    fn a_lifetime_check_with_no_range_around_it_does_not_widen() {
827        // Without the bounds check the first lifetime check speaks only for its own address, so
828        // the one four bytes along is a different question and stays.
829        let (_, mut func, block, pointer) = blank();
830        let mut build = Builder::new(&mut func, block);
831        live(&mut build, pointer);
832        let field = past(&mut build, pointer, 4);
833        live(&mut build, field);
834        build.ret(&[]);
835        assert!(!run(&mut func).changed());
836        assert_eq!(lives(&func), 2);
837    }
838
839    #[test]
840    fn a_lifetime_check_a_call_stands_between_stays_and_is_counted() {
841        // Section 8.8's number. This is the one the summaries were written for.
842        let (mut names, mut func, block, pointer) = blank();
843        let mut build = Builder::new(&mut func, block);
844        access(&mut build, pointer, 16);
845        let callee = names.intern("might_free");
846        let signature = build.func().add_signature(Signature::new());
847        build.call(callee, signature, &[]);
848        let field = past(&mut build, pointer, 4);
849        live(&mut build, field);
850        build.ret(&[]);
851        let stats = run(&mut func);
852        assert!(!stats.changed());
853        assert_eq!(lives(&func), 2);
854        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
855    }
856
857    #[test]
858    fn a_lifetime_check_a_call_that_cannot_free_stands_between_goes() {
859        let (mut names, mut func, block, pointer) = blank();
860        let mut build = Builder::new(&mut func, block);
861        access(&mut build, pointer, 16);
862        let callee = names.intern("counts_them");
863        let signature = build.func().add_signature(Signature::new());
864        let call = build.call(callee, signature, &[]);
865        let field = past(&mut build, pointer, 4);
866        live(&mut build, field);
867        build.ret(&[]);
868        func[call].flags |= Flags::NOFREE;
869        let stats = run(&mut func);
870        assert_eq!(lives(&func), 1);
871        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
872    }
873
874    #[test]
875    fn ending_a_lifetime_throws_the_facts_away() {
876        // Nothing emits `meta_end` yet, so this is the test that says what will happen when
877        // something does, rather than a test of anything the compiler does today.
878        let (_, mut func, block, pointer) = blank();
879        let mut build = Builder::new(&mut func, block);
880        access(&mut build, pointer, 16);
881        let size = build.iconst(Type::int(64), 16);
882        let args = build.func().push_values(&[pointer, size]);
883        build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
884        access(&mut build, pointer, 16);
885        build.ret(&[]);
886        let stats = run(&mut func);
887        assert!(!stats.changed());
888        assert_eq!(checks(&func), 2);
889        assert_eq!(lives(&func), 2);
890        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
891        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
892    }
893
894    #[test]
895    fn fuel_runs_out_over_both_kinds_of_check() {
896        let (_, mut func, block, pointer) = blank();
897        let mut build = Builder::new(&mut func, block);
898        access(&mut build, pointer, 16);
899        access(&mut build, pointer, 4);
900        build.ret(&[]);
901        let mut fuel = Fuel::of(1);
902        let stats = Discharge.run(&mut func, &mut Analyses::new(), &mut fuel);
903        assert_eq!(checks(&func), 1);
904        assert_eq!(lives(&func), 2);
905        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
906        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
907    }
908
909    #[test]
910    fn a_distance_too_large_to_be_a_real_access_is_not_discharged() {
911        // The guard's bound. The two readings of the arithmetic agree while the numbers stay
912        // small, so a rule proved at sixty four bits is not asked about anything else. Nothing
913        // here is wrong, it simply is not proved, and a check that is not proved to be unnecessary
914        // stays.
915        let huge = i128::from(u64::MAX) * 4;
916        let fact = Fact { base: Value::new(0), offset: 0, size: huge };
917        let asked = Fact { base: Value::new(0), offset: huge / 2, size: 4 };
918        assert!(!super::covers(&fact, &asked));
919    }
920}