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. All three kinds `rucc-safety` emits are the pass's business, the bounds check and the
10//! lifetime check in front of an access and the derivation check after a walk, because they are
11//! emitted together and taking out one of three is a third of a saving.
12//!
13//! # The two halves
14//!
15//! Section 7.7 asks for the pass and the condition to be separate things, and they are. What is in
16//! this file is a walk: which check runs before which, which pointer was computed from which, and
17//! how far apart two addresses are. Nothing here decides whether that is enough. The condition
18//! under which a check may go is a rule in `rules/safety.rules`, a solver has to agree with it
19//! before this crate finishes building, and `crate::rules::safety` is the table it compiles into.
20//!
21//! The split is worth the trouble because the two halves fail differently. A walk that gets the
22//! context wrong is a bug of the ordinary kind, and section 14.3's differential check accounting,
23//! which runs the instrumented program with every check and again with the discharged ones gone,
24//! is what looks for it. A removal condition that is wrong is arithmetic that is off at the ends of
25//! the type. It gives the right answer on every test anybody writes and lets one access through in
26//! the one case nobody thought of, and nothing observes that until somebody exploits it.
27//!
28//! # What it establishes and what it asks
29//!
30//! Walking the dominator tree from the entry, the pass carries a set of facts. A `check_bounds`
31//! that stays is a fact, because a check that passes says the bytes it was about lie inside one
32//! storage instance, and a check that fails does not return. A fact is remembered as the pointer's
33//! base and the constant offset from it, which is what a chain of `ptr_add` over constants comes
34//! to, plus how many bytes the access covers.
35//!
36//! At the next `check_bounds`, the pointer is normalized the same way. When a fact shares its base,
37//! the distance between the two accesses is the difference of the two offsets, and that is a number
38//! this pass has rather than a claim it makes: both addresses are the same value plus a constant.
39//! The question of whether the later bytes are inside the earlier ones is then handed to the table,
40//! which answers it in sixty four bit arithmetic rather than in the offsets, and the check goes
41//! only if the answer is yes.
42//!
43//! A check whose extent is an operand is left out of all of this, in both directions. Section 7.4's
44//! hoisted check covers as many bytes as its loop runs times, and every range compared here is a
45//! pair of numbers, so such a check is neither read as a fact nor asked about. Reading its payload
46//! would be worse than skipping it, since the size there is one element of the walk rather than the
47//! range the check is about, and a fact recorded from it would be smaller than the truth in one
48//! direction and a question asked from it smaller in the other.
49//!
50//! The capability operand has to be the `cap_of` of the check's own pointer, which is the shape
51//! `rucc-safety` emits and the shape the argument needs. The check being removed asks whether its
52//! bytes are inside the instance that owns its own pointer, its pointer is inside the range the
53//! earlier check established, and that range is inside one instance, so the answer is yes. A check
54//! whose capability came from somewhere else is asking about a different instance and is left
55//! alone. Nothing is required of the earlier check's capability, because all that is used of it is
56//! that the check passed, and a check that passed put its bytes inside one instance whatever
57//! capability it named.
58//!
59//! # The fact nobody had to check for
60//!
61//! Section 7.2 lists four sources of a discharge and puts the frontend first, because the majority
62//! of accesses in real C are to a local or a global at a constant offset and the bounds of either
63//! are not something anybody has to find out. An `alloca` of a fixed size makes one storage
64//! instance of that many bytes and says so in its payload, so the range from its address to that
65//! many further along is inside one instance for exactly the reason a passing `check_bounds` says
66//! its own range is. When the address a check is about normalizes to such an `alloca`, that range
67//! is the fact, and the question put to the table is the same question with the same rule
68//! answering it.
69//!
70//! Two things make it worth more than a fact a check established. It is there before anything has
71//! run, so the first access to a local is discharged rather than only the second. And no call takes
72//! it away: a callee cannot free a frame slot, whatever it does to whatever the slot points at, so
73//! this fact is asked separately rather than kept in the set the walk throws away at the first call
74//! it cannot see through.
75//!
76//! Only the fixed size form. A variable length array is an `alloca` with an operand and a payload
77//! whose size field reads zero, and reading it anyway would discharge every check in the array.
78//!
79//! A global is the same fact about the other half of section 7.2's sentence, and it arrives here
80//! differently for one reason: how big a global is lives on the module and this pass is given one
81//! function. So `crate::extents` works it out over the module before the pipeline starts, asks the
82//! same rule, and writes the answer onto the check as [`Flags::STATIC`], which is what
83//! `crate::nofree` does with what a call reaches and for the same reason. What is read here is what
84//! the IR says, the same way the pass reads an opcode.
85//!
86//! It answers a lifetime check as well as a bounds check, which a local does not. What a local
87//! gives is an extent, and how long it stays alive is the block it was declared in, which is a
88//! question this pass has nothing to say about. A global has static storage duration and is alive
89//! wherever the question is asked.
90//!
91//! # The walk that stops at a step it cannot read
92//!
93//! Everything above needs the address to be a base and a constant, and an array index is not a
94//! constant. The walk stops at the first `ptr_add` whose step is a value, and what comes out is a
95//! fact about a base whose size nobody knows, which answers nothing.
96//!
97//! Section 7.2's third source is what gets past it. Document 10's ranges know something about the
98//! step even though it is not a number: an index the program has already tested against a length,
99//! or one whose low bits are all that is used, is bounded. So the walk carries on, adding the low
100//! end of the step's range to the offset and the width of the range to the size, and what it ends
101//! up with is the range of addresses the access can land in.
102//!
103//! Whether an object holding all of that range holds the one address the access actually uses is
104//! its own rule, `reached.i64`, which leaves the distance opaque so that one answer covers every
105//! value the step could take. It is a rule of its own rather than the containment rule asked about
106//! the far end of the range, and the reason is section 7.7's: turning a range of addresses into one
107//! containment question is arithmetic on the thing being proved, and a pass doing that quietly is
108//! what the split between the walk and the rule exists to stop.
109//!
110//! The range is only ever asked with and never recorded. What a check proves when it runs is that
111//! the address the program used was inside the object, and nothing at all about the rest of a
112//! range this pass made up around it. So a check discharged this way records the narrow fact, the
113//! bytes the access really wanted, which is the thing that was proved and is what a second check
114//! of the same bytes is answered by.
115//!
116//! The ranges are built only for a function that has a walk by a value in it, because they cost a
117//! copy of the control flow graph and a function without one would never ask them anything.
118//!
119//! # The lifetime half, and what it borrows from the other one
120//!
121//! A `check_live` that stays is a fact too, and a smaller one than it looks: it says the storage
122//! instance holding its own address is alive, and it says nothing about the address four bytes
123//! along, because that address might be in a different instance. On its own that fact discharges
124//! only a second lifetime check of the very same address, and the shape `rucc-safety` emits is a
125//! lifetime check per field rather than per object, so on its own it would almost never fire.
126//!
127//! What makes it fire is the bounds fact sitting next to it. A `check_bounds` that passed put its
128//! whole range inside one instance, so if the lifetime check's address is in that range, the
129//! instance that was found alive is the instance the whole range is in, and the whole range is
130//! alive. So a lifetime fact is recorded as the widest checked range containing its address, and a
131//! later lifetime check is asked about as a single byte. The question of whether that byte is in
132//! that range is the same question the bounds half asks, put to the same rule.
133//!
134//! The order the two arrive in is what makes this work rather than a coincidence to be careful
135//! about: `rucc-safety` emits the bounds check first and the lifetime check second, so the range is
136//! established by the time there is a lifetime fact to widen. A lifetime check that arrives with no
137//! range around it keeps the narrow fact, which is correct and worth little.
138//!
139//! # The derivation half, which is one question rather than two
140//!
141//! `rucc-safety` puts a `check_deriv` after every `ptr_add` off a pointer, and what it asks is not
142//! about a range at all: it asks whether the pointer that came out is still in the storage instance
143//! the pointer that went in belongs to. The runtime has some slack in it for a pointer that walked
144//! exactly off either end, and none of that slack is used here, because the case this pass answers
145//! is the one where both ends are plainly inside something.
146//!
147//! What answers it is one fact holding both ends. A `check_bounds` that passed put its whole range
148//! inside one instance, so if the address that went in and the address that came out are both in
149//! that range, the second is in the instance the first belongs to, which is the question. It has to
150//! be one fact and not one for each end: two facts saying two addresses are each inside some
151//! instance say nothing about whether it is the same instance, and that is the only thing being
152//! asked. A local is a fact of exactly this shape and is asked the same way.
153//!
154//! Both ends are asked about as a single byte, the way a lifetime check is, and for the same reason.
155//! Nothing here is claiming anything about how many bytes are readable at either address.
156//!
157//! A `check_deriv` that stays leaves no fact behind. What it establishes is that two addresses share
158//! an instance, which is not a range of bytes and does not fit in what this walk carries, and the
159//! `covered.i64` rule has nothing to say about it. Recording it would mean a second kind of fact and
160//! a second rule, and the pointer it is about nearly always gets a `check_bounds` of its own a few
161//! instructions later that establishes the range properly.
162//!
163//! # Why a call throws the facts away, and which calls do not
164//!
165//! Section 7.3 says nothing kills a bounds fact except a redefinition of the capability, which in
166//! SSA is never, and this pass is stricter than that: a call, or anything else this pass cannot see
167//! through, drops every fact it is carrying.
168//!
169//! The case is a `free` and then an allocation of something smaller at the same address. The range
170//! established before the call is no longer inside one instance after it, and what document 07
171//! leaves that to is the lifetime judgement rather than this one. Today's lifetime check is about
172//! the address rather than about the version the capability was taken at, so it would not refuse
173//! the access either, and a rate this pass reports is worth less than a hole it opens. The strict
174//! version is what is written first.
175//!
176//! A `meta_end` and a `meta_transfer` drop the facts as well. Nothing emits either one yet, so
177//! this costs nothing today and is the difference between conservative and wrong on the day the
178//! instrumentation starts ending lifetimes. `crate::nofree` treats them the same way.
179//!
180//! The two facts nobody had to check for go across a call untouched, and neither is an exception to
181//! the paragraph above because neither is in the set being thrown away. A callee cannot free a
182//! frame slot and cannot free a global, so a check the declaration answers is answered on the far
183//! side of any call at all.
184//!
185//! A call that says it reaches nothing which can free is the exception, and it is not this pass
186//! being trusting. `crate::nofree` works the answer out over the whole module before the pipeline
187//! starts and writes it onto the call site as [`Flags::NOFREE`], because the fact belongs to the
188//! callee and a pass is given one function. Reading it here is reading what the IR says, the same
189//! way the pass reads an opcode. Nothing else about a call is believed: the facts still go across
190//! an unmarked call, a call through an address, and inline assembly.
191//!
192//! What the strictness still costs is measured rather than guessed. A check that a fact would have
193//! covered if a call had not intervened is counted, so `-fopt-info-missed` says per function what
194//! is left to win.
195
196use std::collections::{HashMap, HashSet};
197
198use rucc_ir::{Block, Def, Extra, Flags, Func, Inst, Opcode, Value};
199
200use crate::range::query::Ranges;
201use crate::rules::{Piece, Subject, Table, safety};
202use crate::{Analyses, Cfg, Fuel, Pass, Preserved, Stats, heap};
203
204/// Recorded once for each bounds check taken out.
205const REMOVED: &str = "bounds check removed, a dominating check covers the same bytes";
206
207/// Recorded once for each bounds check taken out because it was inside a local.
208const REMOVED_LOCAL: &str = "bounds check removed, its bytes are inside a local this function \
209                             declares";
210
211/// Recorded once for each bounds check taken out because it was inside a global.
212const REMOVED_STATIC: &str = "bounds check removed, its bytes are inside an object of static \
213                              storage duration";
214
215/// Recorded once for each bounds check taken out because every caller hands in the object.
216const REMOVED_HANDED: &str = "bounds check removed, its bytes are inside an object every call to \
217                              this function hands it";
218
219/// Recorded once for each bounds check taken out because an allocator made the object.
220const REMOVED_MADE: &str = "bounds check removed, its bytes are inside an object an allocator made \
221                            and this function has tested";
222
223/// Recorded once for each bounds check taken out because a range answered the step it walked by.
224const REMOVED_RANGE: &str = "bounds check removed, every address the walk can reach is inside the \
225                             object it started from";
226
227/// Recorded once for each lifetime check taken out.
228const REMOVED_LIVE: &str = "lifetime check removed, a dominating check covers the same storage";
229
230/// Recorded once for each lifetime check taken out because it was inside a global.
231const REMOVED_LIVE_STATIC: &str =
232    "lifetime check removed, its storage lives as long as the program does";
233
234/// Recorded once for each lifetime check taken out because every caller hands in the object.
235const REMOVED_LIVE_HANDED: &str = "lifetime check removed, its storage is an object every call to \
236                                   this function hands it";
237
238/// Recorded once for each lifetime check taken out because it was inside a frame slot.
239const REMOVED_LIVE_LOCAL: &str =
240    "lifetime check removed, its storage is a frame slot of this function";
241
242/// Recorded once for each lifetime check taken out because a range answered the step it walked by.
243const REMOVED_LIVE_RANGE: &str = "lifetime check removed, every address the walk can reach is in \
244                                  storage a check found alive";
245
246/// Recorded for a bounds check that would have gone if there had been fuel for it.
247const NO_FUEL: &str = "bounds check kept, the pass ran out of fuel";
248
249/// Recorded for a lifetime check that would have gone if there had been fuel for it.
250const NO_FUEL_LIVE: &str = "lifetime check kept, the pass ran out of fuel";
251
252/// Recorded once for each derivation check taken out because a range answered the step it walked by.
253const REMOVED_DERIV_RANGE: &str = "derivation check removed, every address either end can reach is \
254                                   inside one checked range";
255
256/// Recorded for a bounds check a call cost, which is the honest price of the paragraph above.
257///
258/// This one is worth reading rather than skipping. It is the number of checks that are still being
259/// paid for because `crate::nofree` could not vouch for a call, so it says per function what the
260/// rest of section 7.5's summary work would be worth before anybody writes it.
261const PAST_A_CALL: &str =
262    "bounds check kept, a call between it and the check that covers it might free";
263
264/// The same, for a lifetime check. Section 8.8 is about this number rather than the one above.
265const PAST_A_CALL_LIVE: &str =
266    "lifetime check kept, a call between it and the check that covers it might free";
267
268/// Recorded for a bounds check whose operands this pass cannot read.
269const UNKNOWN_SHAPE: &str = "bounds check left alone, its pointer is not a base and a constant";
270
271/// Recorded for a bounds check about a range the program worked out.
272const COMPUTED_EXTENT: &str =
273    "bounds check left alone, how many bytes it covers is a number only the program has";
274
275/// Recorded for a lifetime check whose operands this pass cannot read.
276const UNKNOWN_SHAPE_LIVE: &str =
277    "lifetime check left alone, its pointer is not a base and a constant";
278
279/// Recorded once for each derivation check taken out.
280const REMOVED_DERIV: &str =
281    "derivation check removed, one checked range holds both the pointer and where it walked to";
282
283/// Recorded once for each derivation check taken out because it walked inside a local.
284const REMOVED_DERIV_LOCAL: &str =
285    "derivation check removed, it walks inside a local this function declares";
286
287/// Recorded once for each derivation check taken out because it walked inside a global.
288const REMOVED_DERIV_STATIC: &str =
289    "derivation check removed, it walks inside an object of static storage duration";
290
291/// Recorded once for each derivation check taken out because every caller hands in the object.
292const REMOVED_DERIV_HANDED: &str = "derivation check removed, it walks inside an object every call \
293                                    to this function hands it";
294
295/// Recorded once for each derivation check taken out because an allocator made the object.
296const REMOVED_DERIV_MADE: &str = "derivation check removed, it walks inside an object an allocator \
297                                  made and this function has tested";
298
299/// Recorded for a derivation check that would have gone if there had been fuel for it.
300const NO_FUEL_DERIV: &str = "derivation check kept, the pass ran out of fuel";
301
302/// Recorded for a derivation check a call cost.
303const PAST_A_CALL_DERIV: &str =
304    "derivation check kept, a call between it and the range that holds both ends might free";
305
306/// Recorded for a derivation check whose operands this pass cannot read.
307const UNKNOWN_SHAPE_DERIV: &str =
308    "derivation check left alone, its two pointers are not one base and two constants";
309
310/// The pass. It holds nothing, because everything it works out is about one function.
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub struct Discharge;
313
314impl Pass for Discharge {
315    fn name(&self) -> &'static str {
316        "discharge"
317    }
318
319    fn describe(&self) -> &'static str {
320        "a bounds, lifetime or derivation check whose answer is already known is removed"
321    }
322
323    fn preserves(&self) -> Preserved {
324        // Instructions go and blocks do not. A check is not a terminator and removing one leaves
325        // every edge where it was.
326        Preserved::ALL
327    }
328
329    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
330        let mut stats = Stats::new();
331        let Some(entry) = func.entry() else { return stats };
332        let dom = an.dominators(func).clone();
333
334        // The graph is built for two reasons and neither is the common one, so a function with
335        // neither pays for no copy of it. The ranges want it when there is a walk the constant
336        // reader gives up on, and the allocation rule wants it to find where the program has tested
337        // what an allocator gave it.
338        let walks = walks_by_a_value(func);
339        let cfg = (walks || heap::allocates(func)).then(|| an.cfg(func).clone());
340        let mut ranges = cfg.as_ref().filter(|_| walks).map(|cfg| Ranges::new(&*func, cfg, &dom));
341
342        // One answer per allocation rather than one per check, because a function that reads twenty
343        // fields of the same object asks the same question about the same pointer twenty times.
344        let mut checked: HashMap<Value, HashSet<Block>> = HashMap::new();
345
346        // Whether anything in here says a lifetime is over. Read once over the whole function
347        // rather than carried down the walk, because what the frame slot rule needs is that no
348        // `meta_end` runs before the check on any path, and a fact carried down the dominator
349        // tree only ever says something about the paths that go through one block.
350        let ends = ends_a_lifetime(func);
351
352        // The walk is a stack rather than recursion because the dominator tree of a long chain of
353        // blocks is as deep as the function is long, and a pass is not a place to find that out.
354        // Each block carries its own copy of what holds at its start, which is what makes a fact a
355        // call killed in one arm of a branch still hold in the other.
356        let mut going: Vec<(Inst, &'static str)> = Vec::new();
357        let mut work = vec![(entry, Scope::default())];
358        while let Some((block, mut scope)) = work.pop() {
359            for inst in func.insts(block).collect::<Vec<Inst>>() {
360                if opaque(func, inst) {
361                    scope.forget();
362                    continue;
363                }
364                match func[inst].opcode {
365                    Opcode::CheckBounds => {
366                        if func[func[inst].args].len() > 2 {
367                            stats.missed(COMPUTED_EXTENT);
368                            continue;
369                        }
370                        let Some(asked) = about(func, inst) else {
371                            stats.missed(UNKNOWN_SHAPE);
372                            continue;
373                        };
374                        // The four objects whose extent is known without anybody having checked
375                        // it. A global was worked out over the module by `crate::extents` and an
376                        // object every caller hands in by `crate::params`, both of which arrive as
377                        // a flag; a local is read off its `alloca` here and an allocation off the
378                        // call `crate::heap` marked. All four are asked of the same rule as every
379                        // other fact. The reach of a walk the constant reader could not finish is
380                        // asked last, because it is the only one that costs an analysis to answer.
381                        let why = if func[inst].flags.contains(Flags::STATIC) {
382                            Some(REMOVED_STATIC)
383                        } else if func[inst].flags.contains(Flags::HANDED) {
384                            Some(REMOVED_HANDED)
385                        } else if declared(func, asked.base)
386                            .is_some_and(|local| covers(&local, &asked))
387                        {
388                            Some(REMOVED_LOCAL)
389                        } else if allocated(func, cfg.as_ref(), &mut checked, block, &[&asked]) {
390                            Some(REMOVED_MADE)
391                        } else if scope.bounds.covers(&asked) {
392                            Some(REMOVED)
393                        } else {
394                            reach(func, ranges.as_mut(), &asked, inst)
395                                .filter(|wide| {
396                                    declared(func, wide.base)
397                                        .is_some_and(|local| reaches(&local, wide))
398                                        || scope.bounds.reaches(wide)
399                                })
400                                .map(|_| REMOVED_RANGE)
401                        };
402                        let Some(why) = why else {
403                            if scope.bounds.covered_before(&asked) {
404                                stats.missed(PAST_A_CALL);
405                            }
406                            // A check that stays is a check that runs, and a check that runs
407                            // establishes what it was about. One that was removed establishes
408                            // nothing new: whatever covered it covers everything it would have.
409                            scope.bounds.held.push(asked);
410                            continue;
411                        };
412                        if !fuel.take() {
413                            stats.missed(NO_FUEL);
414                            scope.bounds.held.push(asked);
415                            continue;
416                        }
417                        // A check that goes normally establishes nothing new, because whatever
418                        // answered it covers everything it would have. The range is the one
419                        // exception: what answered it was a fact about a made up range around the
420                        // address, and the next check on these bytes has to ask for that range
421                        // again and may not get the same answer. So the narrow fact goes in, which
422                        // is the thing that was actually proved.
423                        if why == REMOVED_RANGE {
424                            scope.bounds.held.push(asked);
425                        }
426                        going.push((inst, why));
427                    }
428                    Opcode::CheckLive => {
429                        let Some(asked) = alive(func, inst) else {
430                            stats.missed(UNKNOWN_SHAPE_LIVE);
431                            continue;
432                        };
433                        // A global is alive as long as the program is, and a frame slot is alive
434                        // until the function returns, so both objects whose extent is known
435                        // without anybody having checked it answer this as well as a bounds
436                        // check. `ends` is what makes the second one true: where a local stops
437                        // being alive is written into the IR as `meta_end` and not read off the
438                        // shape of the source, so a function with one in it is a function this
439                        // does not claim anything about.
440                        let why = if func[inst].flags.contains(Flags::STATIC) {
441                            Some(REMOVED_LIVE_STATIC)
442                        } else if func[inst].flags.contains(Flags::HANDED) {
443                            Some(REMOVED_LIVE_HANDED)
444                        } else if !ends
445                            && declared(func, asked.base)
446                                .is_some_and(|local| covers(&local, &asked))
447                        {
448                            Some(REMOVED_LIVE_LOCAL)
449                        } else if scope.alive.covers(&asked) {
450                            Some(REMOVED_LIVE)
451                        } else {
452                            // A lifetime fact and not a bounds one, because what is being asked
453                            // is whether the storage is alive and a bounds check that passed says
454                            // nothing about that. The widening argument is the bounds arm's: a
455                            // range known alive that holds every address the walk can reach holds
456                            // the one it actually uses.
457                            reach(func, ranges.as_mut(), &asked, inst)
458                                .filter(|wide| {
459                                    (!ends
460                                        && declared(func, wide.base)
461                                            .is_some_and(|local| reaches(&local, wide)))
462                                        || scope.alive.reaches(wide)
463                                })
464                                .map(|_| REMOVED_LIVE_RANGE)
465                        };
466                        let Some(why) = why else {
467                            if scope.alive.covered_before(&asked) {
468                                stats.missed(PAST_A_CALL_LIVE);
469                            }
470                            scope.alive.held.push(widened(func, &scope.bounds, asked));
471                            continue;
472                        };
473                        if !fuel.take() {
474                            stats.missed(NO_FUEL_LIVE);
475                            scope.alive.held.push(widened(func, &scope.bounds, asked));
476                            continue;
477                        }
478                        // The bounds arm's exception, for its reason. A range answered a made up
479                        // range around this address, so what was proved is about the address.
480                        if why == REMOVED_LIVE_RANGE {
481                            scope.alive.held.push(widened(func, &scope.bounds, asked));
482                        }
483                        going.push((inst, why));
484                    }
485                    Opcode::CheckDeriv => {
486                        let narrow = derives(func, inst);
487                        let why = narrow.and_then(|(from, to)| {
488                            if func[inst].flags.contains(Flags::STATIC) {
489                                Some(REMOVED_DERIV_STATIC)
490                            } else if func[inst].flags.contains(Flags::HANDED) {
491                                Some(REMOVED_DERIV_HANDED)
492                            } else if declared(func, from.base)
493                                .is_some_and(|local| covers(&local, &from) && covers(&local, &to))
494                            {
495                                Some(REMOVED_DERIV_LOCAL)
496                            } else if allocated(
497                                func,
498                                cfg.as_ref(),
499                                &mut checked,
500                                block,
501                                &[&from, &to],
502                            ) {
503                                Some(REMOVED_DERIV_MADE)
504                            } else if scope.bounds.holds_both(&from, &to) {
505                                Some(REMOVED_DERIV)
506                            } else {
507                                None
508                            }
509                        });
510                        // Asked last, and asked off the check's own operands rather than off what
511                        // `derives` worked out, because the case it is for is the one `derives`
512                        // cannot read at all: past a step the constant reader gives up on the two
513                        // ends are not one base and two constants. One thing has to hold both of
514                        // the ranges, for the same reason one thing has to hold both of the
515                        // addresses, which is that two things saying each end is inside something
516                        // say nothing about it being the same something.
517                        let why = why.or_else(|| {
518                            spread(func, ranges.as_mut(), inst, inst)
519                                .filter(|(near, far)| {
520                                    declared(func, near.base).is_some_and(|local| {
521                                        reaches(&local, near) && reaches(&local, far)
522                                    }) || scope.bounds.reaches_both(near, far)
523                                })
524                                .map(|_| REMOVED_DERIV_RANGE)
525                        });
526                        let Some(why) = why else {
527                            match narrow {
528                                Some((from, to)) => {
529                                    if scope.bounds.held_both_before(&from, &to) {
530                                        stats.missed(PAST_A_CALL_DERIV);
531                                    }
532                                }
533                                None => stats.missed(UNKNOWN_SHAPE_DERIV),
534                            }
535                            continue;
536                        };
537                        if !fuel.take() {
538                            stats.missed(NO_FUEL_DERIV);
539                            continue;
540                        }
541                        going.push((inst, why));
542                    }
543                    _ => continue,
544                }
545            }
546            for child in dom.children(block) {
547                work.push((child, scope.clone()));
548            }
549        }
550
551        for (inst, why) in going {
552            func.remove_inst(inst);
553            stats.optimized(why);
554        }
555        stats
556    }
557}
558
559/// A range of bytes some check has already been passed on, or is being asked about.
560///
561/// The address is kept as the value it was computed from and the constant distance from it, rather
562/// than as the pointer itself, because that is what makes two of these comparable: the whole of
563/// what this pass knows about two addresses is that they are one value plus two constants.
564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
565pub(crate) struct Fact {
566    /// The value the address was computed from.
567    pub(crate) base: Value,
568    /// How far past it the access starts.
569    offset: i128,
570    /// How many bytes it covers.
571    size: i128,
572}
573
574impl Fact {
575    /// The whole of an object whose extent is known, starting at its own address.
576    ///
577    /// The two sources of one of these are an `alloca` of a fixed size and a global, and what they
578    /// have in common is that the size is said by something other than a check that passed.
579    pub(crate) fn whole(base: Value, size: i128) -> Self {
580        Self { base, offset: 0, size }
581    }
582}
583
584/// A range of addresses an access can land in, and how many bytes it takes when it does.
585///
586/// What [`reach`] works out and the only thing it is used for. It is deliberately not a [`Fact`]:
587/// a fact is something that was established and may be recorded, and this is a question and may
588/// not. The address the program uses is `base` plus somewhere between `low` and `low` plus `width`
589/// further along, and what a check proves when it runs is about that one address rather than about
590/// the range this was made out of.
591#[derive(Debug, Clone, Copy)]
592struct Reach {
593    /// The value the address was computed from.
594    base: Value,
595    /// The nearest the access can start to it.
596    low: i128,
597    /// How much further than that it can start.
598    width: i128,
599    /// How many bytes it covers.
600    size: i128,
601}
602
603/// One kind of fact, and what has become of it.
604#[derive(Debug, Clone, Default)]
605struct Known {
606    /// The ranges a check has been passed on and nothing has cast doubt on since.
607    held: Vec<Fact>,
608    /// The ones a call threw away, kept only so that the cost of throwing them away is a number
609    /// somebody can read rather than a paragraph somebody has to believe.
610    lost: Vec<Fact>,
611}
612
613impl Known {
614    /// Whether something still standing answers this.
615    fn covers(&self, asked: &Fact) -> bool {
616        self.held.iter().any(|fact| covers(fact, asked))
617    }
618
619    /// Whether something still standing answers a range of addresses an access can land in.
620    fn reaches(&self, asked: &Reach) -> bool {
621        self.held.iter().any(|fact| reaches(fact, asked))
622    }
623
624    /// Whether one thing still standing answers both of these ranges.
625    ///
626    /// One rather than one each, for the reason [`Known::holds_both`] gives, and the reason does
627    /// not change when the ends are ranges instead of addresses.
628    fn reaches_both(&self, from: &Reach, to: &Reach) -> bool {
629        self.held.iter().any(|fact| reaches(fact, from) && reaches(fact, to))
630    }
631
632    /// Whether something would have answered it before a call came along.
633    fn covered_before(&self, asked: &Fact) -> bool {
634        self.lost.iter().any(|fact| covers(fact, asked))
635    }
636
637    /// Whether one thing still standing answers both of these.
638    ///
639    /// One rather than one each, which is the whole point of asking it this way. Two facts saying
640    /// two addresses are each inside some instance say nothing about whether it is the same
641    /// instance, and that is the only thing a derivation check wants to know.
642    fn holds_both(&self, from: &Fact, to: &Fact) -> bool {
643        self.held.iter().any(|fact| covers(fact, from) && covers(fact, to))
644    }
645
646    /// Whether one would have answered both before a call came along.
647    fn held_both_before(&self, from: &Fact, to: &Fact) -> bool {
648        self.lost.iter().any(|fact| covers(fact, from) && covers(fact, to))
649    }
650
651    /// Gives up everything, because something happened that this pass cannot see through.
652    fn forget(&mut self) {
653        self.lost.append(&mut self.held);
654    }
655}
656
657/// What holds where the walk has got to.
658///
659/// The two kinds are apart because they are killed together and answered separately: a range being
660/// inside one instance and that instance being alive are different claims, and reporting them as
661/// one number would hide which of the two a check is still being paid for.
662#[derive(Debug, Clone, Default)]
663struct Scope {
664    /// Ranges a `check_bounds` established are inside one storage instance.
665    bounds: Known,
666    /// Ranges a `check_live` established are in an instance that is alive.
667    alive: Known,
668}
669
670impl Scope {
671    /// Gives up every fact of either kind.
672    fn forget(&mut self) {
673        self.bounds.forget();
674        self.alive.forget();
675    }
676}
677
678/// Whether this instruction could do something to memory that this pass cannot account for.
679///
680/// A call is the whole of it, in every spelling, and inline assembly with it. A `tail_call` ends
681/// the block and there is nothing after it to protect, and it is here anyway so that the reason a
682/// fact survives is never that the walk did not think of something.
683///
684/// A call carrying [`Flags::NOFREE`] reaches nothing that ends a lifetime, so there is nothing for
685/// it to have done to the bytes an earlier check was passed on. `crate::nofree` is what put the
686/// flag there and what argues for it.
687///
688/// A `meta_end` and a `meta_transfer` end a lifetime by saying so, which is the plainest way for a
689/// fact to stop being true, and neither is emitted today.
690fn opaque(func: &Func, inst: Inst) -> bool {
691    match func[inst].opcode {
692        Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => {
693            !func[inst].flags.contains(Flags::NOFREE)
694        }
695        Opcode::InlineAsm | Opcode::MetaEnd | Opcode::MetaTransfer => true,
696        _ => false,
697    }
698}
699
700/// What a `check_bounds` is about, when it is one this pass can read.
701pub(crate) fn about(func: &Func, check: Inst) -> Option<Fact> {
702    let (base, offset) = addressed(func, check)?;
703    let Extra::Mem(info) = func[check].extra else { return None };
704    Some(Fact { base, offset, size: i128::from(func[info].size) })
705}
706
707/// What a `check_live` is about, when it is one this pass can read.
708///
709/// One byte, because that is the whole of what the check says: the instance holding this address
710/// is alive, and nothing about the address next door. The widening to a range that makes the fact
711/// useful is [`widened`], and it needs a bounds fact to do it.
712pub(crate) fn alive(func: &Func, check: Inst) -> Option<Fact> {
713    let (base, offset) = addressed(func, check)?;
714    Some(Fact { base, offset, size: 1 })
715}
716
717/// The address a check is about, as a base and a constant.
718///
719/// The capability has to be the `cap_of` of the check's own pointer. That is the shape
720/// `rucc-safety` emits and it is what the removal argument in the module comment needs, so a check
721/// that does not have it is not a check this pass has anything to say about.
722fn addressed(func: &Func, check: Inst) -> Option<(Value, i128)> {
723    let args = &func[func[check].args];
724    let &capability = args.first()?;
725    let &pointer = args.get(1)?;
726    if operand_of(func, capability, Opcode::CapOf, 0) != Some(pointer) {
727        return None;
728    }
729    Some(normal(func, pointer))
730}
731
732/// The two ends of a `check_deriv`, each as the single byte at it.
733///
734/// A derivation check asks whether the pointer that came out of a `ptr_add` is still in the storage
735/// instance the pointer that went in belongs to, so both ends have to be readable and both have to
736/// come out of the same value, which is what makes the two offsets comparable at all. One byte each
737/// because that is what is being asked about: not a range, but whether an address is in an instance.
738///
739/// The capability has to be the `cap_of` of the pointer that went in, for the reason [`addressed`]
740/// gives. The instance the check is about is the one that pointer belongs to, and a check naming
741/// some other capability is about some other instance.
742///
743/// The width operand is not read. It matters to the runtime only for a pointer that walked off the
744/// near end, where the check passes on the byte a stride further along instead of on the address
745/// itself, and this pass never gets that far: it discharges nothing it has not put inside a range
746/// outright.
747pub(crate) fn derives(func: &Func, check: Inst) -> Option<(Fact, Fact)> {
748    let args = &func[func[check].args];
749    let &capability = args.first()?;
750    let &from = args.get(1)?;
751    let &to = args.get(2)?;
752    if operand_of(func, capability, Opcode::CapOf, 0) != Some(from) {
753        return None;
754    }
755    let (base, start) = normal(func, from);
756    let (walked, end) = normal(func, to);
757    if base != walked {
758        return None;
759    }
760    Some((Fact { base, offset: start, size: 1 }, Fact { base, offset: end, size: 1 }))
761}
762
763/// The object a local is, when the address a check is about was computed from one.
764///
765/// This is the fact nobody had to check for, and section 7.2 puts it first of the four sources
766/// because it is where most of the win is. An `alloca` of a fixed size is one storage instance of
767/// that many bytes, said by the instruction that makes it rather than by a check that passed, so
768/// the bytes from its address to that many further along are inside one instance for the same
769/// reason a passing `check_bounds` says its own range is.
770///
771/// Only the fixed size form. The one that takes an operand is a variable length array, and how
772/// many bytes it is is a value the program works out rather than a number in the payload, where
773/// the field reads zero.
774///
775/// The fact holds everywhere in the function and no call takes it away, which is the other half of
776/// what makes it worth having. A callee cannot free a frame slot: what it could free is whatever a
777/// pointer stored in the slot points at, and that is a different instance and a different check.
778/// So this is asked separately from the facts the walk carries rather than pushed into them, since
779/// everything in there is thrown away at the first call this pass cannot see through.
780fn declared(func: &Func, base: Value) -> Option<Fact> {
781    let Def::Result { inst, .. } = func[base].def else { return None };
782    if func[inst].opcode != Opcode::Alloca || !func[func[inst].args].is_empty() {
783        return None;
784    }
785    let Extra::Mem(info) = func[inst].extra else { return None };
786    Some(Fact::whole(base, i128::from(func[info].size)))
787}
788
789/// Whether all of those bytes are inside one object an allocator made, at a place this function has
790/// already found out is not null.
791///
792/// The same shape as [`declared`] one storey up, with a marked call saying the size instead of an
793/// `alloca` and one more thing to establish. `crate::heap` has the argument for both halves: what a
794/// call to `malloc` says is an extent and never a lifetime, and it only says it where the program
795/// has looked, because a null pointer is inside no object and a check on one is a check that is
796/// meant to fail.
797///
798/// Every part has to be inside, and inside the same object, which is what asking [`covers`] with one
799/// fact and several does. Nothing is claimed when the graph was not built, which is a function this
800/// found no allocation in and so a function where the answer would have been no anyway.
801fn allocated(
802    func: &Func,
803    cfg: Option<&Cfg>,
804    checked: &mut HashMap<Value, HashSet<Block>>,
805    block: Block,
806    parts: &[&Fact],
807) -> bool {
808    let Some(first) = parts.first() else { return false };
809    let Some(whole) = heap::made(func, first.base) else { return false };
810    if !parts.iter().all(|part| covers(&whole, part)) {
811        return false;
812    }
813    let Some(cfg) = cfg else { return false };
814    checked
815        .entry(whole.base)
816        .or_insert_with(|| heap::tested(func, cfg, whole.base))
817        .contains(&block)
818}
819
820/// A lifetime fact grown from one address to the checked range it sits in.
821///
822/// The argument is in the module comment: a `check_bounds` that passed put its whole range inside
823/// one instance, so the instance this lifetime check found alive is the instance that range is in.
824/// With no range around the address the fact stays as it came, which is correct and answers only a
825/// repeat of the very same check.
826///
827/// A local is asked about first, because the object it is is the widest range there can be for an
828/// address computed from it and a wider fact answers more later checks. What that gives is a
829/// lifetime check anywhere in a local discharging every later one in the same local, up to the
830/// first call, which is the shape a function that reads several fields of a local struct has.
831fn widened(func: &Func, bounds: &Known, asked: Fact) -> Fact {
832    if let Some(local) = declared(func, asked.base).filter(|local| covers(local, &asked)) {
833        return local;
834    }
835    bounds.held.iter().find(|fact| covers(fact, &asked)).copied().unwrap_or(asked)
836}
837
838/// The value an address was computed from, and how far past it the address is.
839///
840/// A `ptr_add` over a constant is walked through, and anything else is where the answer stops. The
841/// arithmetic here is exact because it is done in `i128` over offsets that came out of the IR as
842/// sixty four bit constants, and whether it is small enough to mean anything at sixty four bits is
843/// the rule's question rather than this function's.
844pub(crate) fn normal(func: &Func, value: Value) -> (Value, i128) {
845    let mut base = value;
846    let mut offset: i128 = 0;
847    while let Some((from, step)) = walked(func, base) {
848        let Some(sum) = offset.checked_add(step) else { break };
849        base = from;
850        offset = sum;
851    }
852    (base, offset)
853}
854
855/// Every address a walk can reach, when a step it takes is a value rather than a constant.
856///
857/// This is the third of the four sources section 7.2 lists, and it is the one that needs an
858/// analysis. [`normal`] stops at the first `ptr_add` whose step it cannot read, and what it hands
859/// back is a fact about a base nobody knows the size of. Document 10's ranges do know something
860/// about the step: an index the program has already tested, or one a loop counts, is bounded even
861/// though it is not constant. So the walk carries on past the step, adding the low end of its
862/// range to the offset and the width of the range to the size.
863///
864/// What comes out is a range of addresses the access can land in, and it is a [`Reach`] rather than
865/// a [`Fact`] on purpose. Whether an object holding all of that range holds the one address the
866/// access actually uses is [`reaches`], which asks a rule with the distance left opaque, so one
867/// answer covers every value the step could take.
868///
869/// It is only ever asked with. What this returns must never be recorded as established, and the
870/// one place it could be is the push in the `check_bounds` arm, which happens only where this
871/// returned nothing or answered nothing. The reason is that the widened range is not what a check
872/// proves. A check that runs and passes proves the address the program used was inside the object,
873/// and says nothing at all about the rest of the range this function made up around it.
874fn reach(func: &Func, ranges: Option<&mut Ranges<'_>>, asked: &Fact, at: Inst) -> Option<Reach> {
875    let wide = spanned(func, ranges?, asked.base, asked.offset, asked.size, at)?;
876    // Nothing was walked past, so this is the fact that came in and asking it again is work
877    // somebody already did.
878    (wide.base != asked.base).then_some(wide)
879}
880
881/// The two ends of a derivation check, each as the range of addresses it can be at.
882///
883/// A derivation check asks whether the pointer that came out of a walk is still in the storage
884/// instance the pointer that went in belongs to. [`derives`] answers that only when both ends
885/// normalize to one base over constants, and past a step the constant reader gives up on they do
886/// not, which is why this reads the check's operands again rather than taking what that worked
887/// out. Each end becomes a range, and the two still have to be off one base or there is nothing
888/// comparable to ask about.
889///
890/// One byte each, for the reason [`derives`] gives. Nothing here claims anything about how many
891/// bytes are readable at either address.
892///
893/// The capability has to be the `cap_of` of the pointer that went in, for the reason [`addressed`]
894/// gives.
895fn spread(
896    func: &Func,
897    ranges: Option<&mut Ranges<'_>>,
898    check: Inst,
899    at: Inst,
900) -> Option<(Reach, Reach)> {
901    let ranges = ranges?;
902    let args = &func[func[check].args];
903    let &capability = args.first()?;
904    let &from = args.get(1)?;
905    let &to = args.get(2)?;
906    if operand_of(func, capability, Opcode::CapOf, 0) != Some(from) {
907        return None;
908    }
909    let (base, offset) = normal(func, from);
910    let near = spanned(func, ranges, base, offset, 1, at)?;
911    let (base, offset) = normal(func, to);
912    let far = spanned(func, ranges, base, offset, 1, at)?;
913    (near.base == far.base).then_some((near, far))
914}
915
916/// Every address a walk off `base` can reach, and how many bytes it takes when it gets there.
917///
918/// The loop is [`normal`]'s with one more thing to try. A `ptr_add` over a constant is walked
919/// through the same way, and a `ptr_add` over a value is walked through when document 10's ranges
920/// put numbers on that value: the low end of the range goes on the distance and the width of it on
921/// the slack. Anything else is where the walk stops.
922///
923/// Nothing is returned when a step is a value the ranges say nothing useful about, rather than the
924/// walk stopping there and handing back what it had. What it had would be a range off a `ptr_add`
925/// nobody knows the size of, which answers nothing, so stopping would be a longer way of saying no.
926fn spanned(
927    func: &Func,
928    ranges: &mut Ranges<'_>,
929    base: Value,
930    offset: i128,
931    size: i128,
932    at: Inst,
933) -> Option<Reach> {
934    let mut base = base;
935    let mut low = offset;
936    let mut width: i128 = 0;
937    loop {
938        // A constant step again, because past a step that needed a range there can be more of
939        // them, and the frontend leaves a field offset as a constant under an array index.
940        if let Some((from, step)) = walked(func, base) {
941            low = low.checked_add(step)?;
942            base = from;
943            continue;
944        }
945        let Some(from) = operand_of(func, base, Opcode::PtrAdd, 0) else { break };
946        let by = operand_of(func, base, Opcode::PtrAdd, 1)?;
947        let (least, most) = ranges.at_inst(by, at).signed_bounds()?;
948        low = low.checked_add(least)?;
949        width = width.checked_add(most.checked_sub(least)?)?;
950        base = from;
951    }
952    Some(Reach { base, low, width, size })
953}
954
955/// Whether any walk in this function steps by a value rather than a constant.
956///
957/// The question the ranges are built for. A function without one of these would pay for a copy of
958/// the control flow graph and never ask anything of it.
959/// Whether anything in this function says a lifetime is over.
960///
961/// Nothing emits `meta_end` today, so this is false everywhere and the frame slot rule in
962/// [`Discharge::run`] is on for every function. It is written anyway, and written over the whole
963/// function rather than along the walk, because the day something does emit one the cheap reading
964/// is the wrong one: a lifetime that ended in one arm of a branch has ended for a check after the
965/// join, and a walk down the dominator tree would not have seen it. Turning the rule off for the
966/// function is the reading that stays right when that day comes, and the finer one is a job for
967/// whoever makes `meta_end` appear.
968fn ends_a_lifetime(func: &Func) -> bool {
969    func.blocks().any(|block| func.insts(block).any(|inst| func[inst].opcode == Opcode::MetaEnd))
970}
971
972fn walks_by_a_value(func: &Func) -> bool {
973    func.blocks().any(|block| {
974        func.insts(block).any(|inst| {
975            func[inst].opcode == Opcode::PtrAdd
976                && func[func[inst].args].get(1).is_some_and(|&by| constant(func, by).is_none())
977        })
978    })
979}
980
981/// The pointer one `ptr_add` over a constant was computed from, and by how much.
982fn walked(func: &Func, value: Value) -> Option<(Value, i128)> {
983    let from = operand_of(func, value, Opcode::PtrAdd, 0)?;
984    let by = operand_of(func, value, Opcode::PtrAdd, 1)?;
985    Some((from, constant(func, by)?))
986}
987
988/// Operand `index` of the instruction that produced `value`, when that instruction is `opcode`.
989pub(crate) fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
990    let Def::Result { inst, .. } = func[value].def else { return None };
991    if func[inst].opcode != opcode {
992        return None;
993    }
994    func[func[inst].args].get(index).copied()
995}
996
997/// The value of an integer constant, read with its own sign.
998pub(crate) fn constant(func: &Func, value: Value) -> Option<i128> {
999    let Def::Result { inst, .. } = func[value].def else { return None };
1000    if func[inst].opcode != Opcode::IConst {
1001        return None;
1002    }
1003    let Extra::Imm(imm) = func[inst].extra else { return None };
1004    let ty = func[value].ty;
1005    ty.is_int().then(|| func[imm].signed(ty))
1006}
1007
1008/// Whether an established fact answers the check being asked about.
1009///
1010/// This function decides nothing. It puts the two together into the term the rule file is written
1011/// about and asks the table, which is the whole of section 7.7's split: the paragraph above worked
1012/// out that the two addresses are one value a constant apart, and whether that is enough is
1013/// somebody's proof rather than this file's opinion.
1014pub(crate) fn covers(fact: &Fact, asked: &Fact) -> bool {
1015    if fact.base != asked.base {
1016        return false;
1017    }
1018    let Some(delta) = asked.offset.checked_sub(fact.offset) else { return false };
1019    let mut question = Question::default();
1020    let at = question.opaque();
1021    let at = question.app("value.i64", &[at]);
1022    let span = question.number(fact.size);
1023    let span = question.app("iconst.i64", &[span]);
1024    let far = question.number(delta);
1025    let far = question.app("iconst.i64", &[far]);
1026    let reach = question.number(asked.size);
1027    let reach = question.app("iconst.i64", &[reach]);
1028    let term = question.app("covered.i64", &[at, span, far, reach]);
1029    match safety::TABLE.find(&question, term) {
1030        Some(found) => yes(&safety::TABLE, found.rule),
1031        None => false,
1032    }
1033}
1034
1035/// Whether an object holds every address a walk can land on.
1036///
1037/// The companion to [`covers`] for the question [`reach`] asks, and it decides nothing either. It
1038/// puts the object and the range of addresses into the term the rule file is written about and
1039/// asks the table. The distance the program actually walks is opaque in the question, which is
1040/// what makes one answer cover every value it could take.
1041fn reaches(fact: &Fact, asked: &Reach) -> bool {
1042    if fact.base != asked.base {
1043        return false;
1044    }
1045    let Some(delta) = asked.low.checked_sub(fact.offset) else { return false };
1046    let mut question = Question::default();
1047    let at = question.opaque();
1048    let at = question.app("value.i64", &[at]);
1049    let span = question.number(fact.size);
1050    let span = question.app("iconst.i64", &[span]);
1051    let delta = question.number(delta);
1052    let delta = question.app("iconst.i64", &[delta]);
1053    let width = question.number(asked.width);
1054    let width = question.app("iconst.i64", &[width]);
1055    let size = question.number(asked.size);
1056    let size = question.app("iconst.i64", &[size]);
1057    let step = question.opaque();
1058    let step = question.app("value.i64", &[step]);
1059    let term = question.app("reached.i64", &[at, span, delta, width, size, step]);
1060    match safety::TABLE.find(&question, term) {
1061        Some(found) => yes(&safety::TABLE, found.rule),
1062        None => false,
1063    }
1064}
1065
1066/// Whether the rule that fired answers yes.
1067///
1068/// A discharge rule replaces the question with a constant, and one is yes. Every rule in the file
1069/// answers that today, and reading it off the rule rather than assuming it is what keeps this
1070/// honest on the day one of them answers something else.
1071pub(crate) fn yes(table: &Table, rule: usize) -> bool {
1072    matches!(table.rules[rule].replacement, [Piece::App { .. }, Piece::Int(1)])
1073}
1074
1075/// A term built to be asked about, and nothing else.
1076///
1077/// The rules are matched against this rather than against the function, because what is being asked
1078/// about is not in the function: it is what the walk worked out about two of its instructions. So
1079/// the subject is a small arena of exactly the term being asked, built fresh for each question and
1080/// thrown away with the answer.
1081#[derive(Debug, Default)]
1082pub(crate) struct Question {
1083    held: Vec<Held>,
1084}
1085
1086/// One node of that term.
1087#[derive(Debug)]
1088enum Held {
1089    /// A number the pattern can read and a guard can be about.
1090    Int(i128),
1091    /// A head and its arguments.
1092    App(&'static str, Vec<usize>),
1093    /// Something with no structure, which is how an address the rule only names is written.
1094    Opaque,
1095}
1096
1097impl Question {
1098    /// Adds a constant and gives back where it went.
1099    ///
1100    /// Named for what it adds rather than for what it holds, because the arena also answers
1101    /// [`Subject::int`] and one name for the two would read as though building a term and asking
1102    /// about one were the same act.
1103    pub(crate) fn number(&mut self, value: i128) -> usize {
1104        self.held.push(Held::Int(value));
1105        self.held.len() - 1
1106    }
1107
1108    /// Adds an application of `head` to what is already in the arena.
1109    pub(crate) fn app(&mut self, head: &'static str, args: &[usize]) -> usize {
1110        self.held.push(Held::App(head, args.to_vec()));
1111        self.held.len() - 1
1112    }
1113
1114    /// Adds something the rule can bind and cannot look inside.
1115    pub(crate) fn opaque(&mut self) -> usize {
1116        self.held.push(Held::Opaque);
1117        self.held.len() - 1
1118    }
1119}
1120
1121impl Subject for Question {
1122    type Node = usize;
1123
1124    fn head(&self, node: usize) -> Option<(&str, usize)> {
1125        match &self.held[node] {
1126            Held::App(head, args) => Some((head, args.len())),
1127            Held::Int(_) | Held::Opaque => None,
1128        }
1129    }
1130
1131    fn arg(&self, node: usize, index: usize) -> usize {
1132        match &self.held[node] {
1133            Held::App(_, args) => args[index],
1134            // The walk only asks for an argument `head` said was there, so this is unreachable
1135            // rather than a case with an answer.
1136            Held::Int(_) | Held::Opaque => unreachable!("only an application has arguments"),
1137        }
1138    }
1139
1140    fn int(&self, node: usize) -> Option<i128> {
1141        match self.held[node] {
1142            Held::Int(value) => Some(value),
1143            Held::App(..) | Held::Opaque => None,
1144        }
1145    }
1146
1147    fn same(&self, a: usize, b: usize) -> bool {
1148        // Every node of a question is written once, so two places holding one thing are one place.
1149        a == b
1150    }
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155    use rucc_base::Interner;
1156    use rucc_ir::{
1157        AsmInfo, Block, BlockCallList, Builder, Extra, Flags, Func, Inst, InstData, IntPred,
1158        MemInfo, MemOrder, Opcode, Restrict, Signature, Type, Value,
1159    };
1160
1161    use super::{Discharge, Fact};
1162    use crate::stats::Kind;
1163    use crate::{Fuel, Pass};
1164
1165    /// A function taking a pointer, with one block, ready to have accesses put in it.
1166    fn blank() -> (Interner, Func, Block, Value) {
1167        let mut names = Interner::new();
1168        let name = names.intern("f");
1169        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR]));
1170        let block = func.create_block();
1171        let pointer = func.append_param(block, Type::PTR);
1172        (names, func, block, pointer)
1173    }
1174
1175    /// Puts `cap_of` and a `check_bounds` over `size` bytes at `pointer` into a block.
1176    ///
1177    /// The same shape `rucc-safety` emits, written out here rather than reached for, because
1178    /// `rucc-opt` is rank 9 alongside `rucc-safety` and cannot depend on it.
1179    fn check(build: &mut Builder<'_>, pointer: Value, size: u64) {
1180        let args = build.func().push_values(&[pointer]);
1181        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1182        let info = MemInfo {
1183            size,
1184            align: 1,
1185            order: MemOrder::NotAtomic,
1186            tbaa: None,
1187            restrict: Restrict::NONE,
1188        };
1189        let args = build.func().push_values(&[capability, pointer]);
1190        let extra = Extra::Mem(build.func().add_mem(info));
1191        build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1192    }
1193
1194    /// Puts `cap_of` and a `check_live` at `pointer` into a block.
1195    ///
1196    /// `rucc-safety` emits this straight after the bounds check for the same access and shares the
1197    /// one `cap_of` between the two. Sharing it is not what the pass reads, so the tests build a
1198    /// second one, which is the harder shape for it to accept.
1199    fn live(build: &mut Builder<'_>, pointer: Value) {
1200        let args = build.func().push_values(&[pointer]);
1201        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1202        let args = build.func().push_values(&[capability, pointer]);
1203        build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
1204    }
1205
1206    /// Both checks in front of one access, in the order `rucc-safety` writes them.
1207    fn access(build: &mut Builder<'_>, pointer: Value, size: u64) {
1208        check(build, pointer, size);
1209        live(build, pointer);
1210    }
1211
1212    /// A pointer `bytes` past another one.
1213    fn past(build: &mut Builder<'_>, pointer: Value, bytes: i128) -> Value {
1214        let offset = build.iconst(Type::int(64), bytes);
1215        let args = build.func().push_values(&[pointer, offset]);
1216        build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
1217    }
1218
1219    /// Puts the flag `crate::extents` writes onto every check in a function.
1220    ///
1221    /// The pass reads what the IR says, so what a test has to build is an IR that says it. Working
1222    /// out which checks deserve it is `crate::extents`, is about a module rather than a function,
1223    /// and has its own tests.
1224    fn marked(func: &mut Func) {
1225        flagged(func, Flags::STATIC);
1226    }
1227
1228    /// Puts that flag on every check in the function, the way an annotator before the pipeline
1229    /// would have.
1230    fn flagged(func: &mut Func, flag: Flags) {
1231        let insts: Vec<Inst> =
1232            func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
1233        for inst in insts {
1234            let check = matches!(
1235                func[inst].opcode,
1236                Opcode::CheckBounds | Opcode::CheckLive | Opcode::CheckDeriv
1237            );
1238            if check {
1239                func[inst].flags |= flag;
1240            }
1241        }
1242    }
1243
1244    /// How many checks are left in a function.
1245    fn checks(func: &Func) -> usize {
1246        func.blocks()
1247            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1248            .filter(|&inst| func[inst].opcode == Opcode::CheckBounds)
1249            .count()
1250    }
1251
1252    /// How many lifetime checks are left in a function.
1253    fn lives(func: &Func) -> usize {
1254        func.blocks()
1255            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1256            .filter(|&inst| func[inst].opcode == Opcode::CheckLive)
1257            .count()
1258    }
1259
1260    fn run(func: &mut Func) -> crate::Stats {
1261        Discharge.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1262    }
1263
1264    #[test]
1265    fn a_second_check_of_the_same_bytes_goes() {
1266        let (_, mut func, block, pointer) = blank();
1267        let mut build = Builder::new(&mut func, block);
1268        check(&mut build, pointer, 4);
1269        check(&mut build, pointer, 4);
1270        build.ret(&[]);
1271        let stats = run(&mut func);
1272        assert_eq!(checks(&func), 1);
1273        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1274    }
1275
1276    #[test]
1277    fn a_check_over_a_length_the_program_worked_out_is_not_this_pass_to_read() {
1278        // Section 7.4's hoisted check covers as many bytes as its loop runs times, which is a value
1279        // and not a number. Every range this pass compares is a pair of numbers, so it says so and
1280        // leaves the check alone rather than reading the payload, whose size is one element.
1281        let (_, mut func, block, pointer) = blank();
1282        let mut build = Builder::new(&mut func, block);
1283        check(&mut build, pointer, 4);
1284        let args = build.func().push_values(&[pointer]);
1285        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1286        let bytes = build.iconst(Type::int(64), 4);
1287        let info = MemInfo {
1288            size: 4,
1289            align: 1,
1290            order: MemOrder::NotAtomic,
1291            tbaa: None,
1292            restrict: Restrict::NONE,
1293        };
1294        let extra = Extra::Mem(build.func().add_mem(info));
1295        let args = build.func().push_values(&[capability, pointer, bytes]);
1296        build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1297        build.ret(&[]);
1298
1299        let stats = run(&mut func);
1300        assert_eq!(checks(&func), 2, "the second one stays");
1301        assert_eq!(stats.count(Kind::Missed, super::COMPUTED_EXTENT), 1);
1302    }
1303
1304    #[test]
1305    fn a_check_of_bytes_inside_a_checked_range_goes() {
1306        // Four bytes at offset four, inside sixteen bytes at offset zero. This is the shape the
1307        // whole pass is for: a struct whose fields are read one after another through one pointer.
1308        let (_, mut func, block, pointer) = blank();
1309        let mut build = Builder::new(&mut func, block);
1310        check(&mut build, pointer, 16);
1311        let field = past(&mut build, pointer, 4);
1312        check(&mut build, field, 4);
1313        build.ret(&[]);
1314        run(&mut func);
1315        assert_eq!(checks(&func), 1);
1316    }
1317
1318    #[test]
1319    fn a_check_of_bytes_past_the_end_of_a_checked_range_stays() {
1320        // Four bytes at offset fourteen is two bytes past the end of the sixteen that were
1321        // checked, and those two bytes are what the check is for.
1322        let (_, mut func, block, pointer) = blank();
1323        let mut build = Builder::new(&mut func, block);
1324        check(&mut build, pointer, 16);
1325        let over = past(&mut build, pointer, 14);
1326        check(&mut build, over, 4);
1327        build.ret(&[]);
1328        assert!(!run(&mut func).changed());
1329        assert_eq!(checks(&func), 2);
1330    }
1331
1332    #[test]
1333    fn a_check_of_bytes_before_a_checked_range_stays() {
1334        // The guard's `delta` is not negative, and this is why. A read four bytes below what was
1335        // checked is a read of somebody else's memory, and it is the bug the check exists for.
1336        let (_, mut func, block, pointer) = blank();
1337        let mut build = Builder::new(&mut func, block);
1338        check(&mut build, pointer, 16);
1339        let under = past(&mut build, pointer, -4);
1340        check(&mut build, under, 4);
1341        build.ret(&[]);
1342        assert!(!run(&mut func).changed());
1343        assert_eq!(checks(&func), 2);
1344    }
1345
1346    #[test]
1347    fn a_check_through_a_pointer_nothing_relates_to_the_first_stays() {
1348        let mut names = Interner::new();
1349        let name = names.intern("two");
1350        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::PTR]));
1351        let block = func.create_block();
1352        let one = func.append_param(block, Type::PTR);
1353        let other = func.append_param(block, Type::PTR);
1354        let mut build = Builder::new(&mut func, block);
1355        check(&mut build, one, 16);
1356        check(&mut build, other, 4);
1357        build.ret(&[]);
1358        assert!(!run(&mut func).changed());
1359        assert_eq!(checks(&func), 2);
1360    }
1361
1362    #[test]
1363    fn a_check_a_call_stands_between_stays_and_is_counted() {
1364        // The conservatism the module comment argues for, and the number that says what it costs.
1365        let (mut names, mut func, block, pointer) = blank();
1366        let mut build = Builder::new(&mut func, block);
1367        check(&mut build, pointer, 16);
1368        let callee = names.intern("might_free");
1369        let signature = build.func().add_signature(Signature::new());
1370        build.call(callee, signature, &[]);
1371        check(&mut build, pointer, 4);
1372        build.ret(&[]);
1373        let stats = run(&mut func);
1374        assert!(!stats.changed());
1375        assert_eq!(checks(&func), 2);
1376        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
1377    }
1378
1379    #[test]
1380    fn a_check_a_call_that_cannot_free_stands_between_goes() {
1381        // The other side of the paragraph above. The summary said this call reaches nothing that
1382        // ends a lifetime, so the range the first check established is still one range.
1383        let (mut names, mut func, block, pointer) = blank();
1384        let mut build = Builder::new(&mut func, block);
1385        check(&mut build, pointer, 16);
1386        let callee = names.intern("counts_them");
1387        let signature = build.func().add_signature(Signature::new());
1388        let call = build.call(callee, signature, &[]);
1389        check(&mut build, pointer, 4);
1390        build.ret(&[]);
1391        func[call].flags |= Flags::NOFREE;
1392        let stats = run(&mut func);
1393        assert_eq!(checks(&func), 1);
1394        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1395        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
1396    }
1397
1398    #[test]
1399    fn inline_assembly_throws_the_facts_away_whatever_it_is_flagged() {
1400        // There is no flag that would make this safe. The template is text the compiler does not
1401        // read, so nothing worked anything out about what it reaches.
1402        let (mut names, mut func, block, pointer) = blank();
1403        let mut build = Builder::new(&mut func, block);
1404        check(&mut build, pointer, 16);
1405        build.inline_asm(
1406            AsmInfo {
1407                template: names.intern("nop"),
1408                constraints: names.intern(""),
1409                clobbers: names.intern(""),
1410                targets: BlockCallList::EMPTY,
1411            },
1412            &[],
1413            &[],
1414            Flags::NONE,
1415        );
1416        check(&mut build, pointer, 4);
1417        build.ret(&[]);
1418        let stats = run(&mut func);
1419        assert!(!stats.changed());
1420        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
1421    }
1422
1423    #[test]
1424    fn a_check_that_only_one_path_covers_stays() {
1425        // The dominator tree is what makes this right. The check in the arm covers the one in the
1426        // join on one path and not on the other, and a check that goes has to be one that ran.
1427        let (_, mut func, block, pointer) = blank();
1428        let arm = func.create_block();
1429        let join = func.create_block();
1430        let mut build = Builder::new(&mut func, block);
1431        let condition = build.iconst(Type::int(32), 1);
1432        build.br_if(condition, arm, &[], join, &[]);
1433        let mut build = Builder::new(&mut func, arm);
1434        check(&mut build, pointer, 16);
1435        build.jump(join, &[]);
1436        let mut build = Builder::new(&mut func, join);
1437        check(&mut build, pointer, 4);
1438        build.ret(&[]);
1439        assert!(!run(&mut func).changed());
1440        assert_eq!(checks(&func), 2);
1441    }
1442
1443    #[test]
1444    fn a_check_a_dominating_block_covers_goes() {
1445        let (_, mut func, block, pointer) = blank();
1446        let after = func.create_block();
1447        let mut build = Builder::new(&mut func, block);
1448        check(&mut build, pointer, 16);
1449        build.jump(after, &[]);
1450        let mut build = Builder::new(&mut func, after);
1451        let field = past(&mut build, pointer, 8);
1452        check(&mut build, field, 8);
1453        build.ret(&[]);
1454        run(&mut func);
1455        assert_eq!(checks(&func), 1);
1456    }
1457
1458    #[test]
1459    fn fuel_stops_the_removing_and_not_the_looking() {
1460        let (_, mut func, block, pointer) = blank();
1461        let mut build = Builder::new(&mut func, block);
1462        check(&mut build, pointer, 4);
1463        check(&mut build, pointer, 4);
1464        check(&mut build, pointer, 4);
1465        build.ret(&[]);
1466        let mut fuel = Fuel::of(1);
1467        let stats = Discharge.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
1468        assert_eq!(checks(&func), 2);
1469        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1470        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
1471    }
1472
1473    #[test]
1474    fn a_second_lifetime_check_of_the_same_address_goes() {
1475        // The narrow fact on its own, with no range around it to widen into.
1476        let (_, mut func, block, pointer) = blank();
1477        let mut build = Builder::new(&mut func, block);
1478        live(&mut build, pointer);
1479        live(&mut build, pointer);
1480        build.ret(&[]);
1481        let stats = run(&mut func);
1482        assert_eq!(lives(&func), 1);
1483        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
1484    }
1485
1486    #[test]
1487    fn a_lifetime_check_inside_a_checked_range_goes() {
1488        // The shape the pass is for, with both halves of it. Sixteen bytes are checked and found
1489        // alive, then a field four bytes in is read, and neither check in front of it survives.
1490        let (_, mut func, block, pointer) = blank();
1491        let mut build = Builder::new(&mut func, block);
1492        access(&mut build, pointer, 16);
1493        let field = past(&mut build, pointer, 4);
1494        access(&mut build, field, 4);
1495        build.ret(&[]);
1496        let stats = run(&mut func);
1497        assert_eq!(checks(&func), 1);
1498        assert_eq!(lives(&func), 1);
1499        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1500        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
1501    }
1502
1503    #[test]
1504    fn a_lifetime_check_outside_every_checked_range_stays() {
1505        // Four bytes at offset twenty are past the sixteen that were checked, so nothing says the
1506        // address is in the instance that was found alive, and it might be in no instance at all.
1507        let (_, mut func, block, pointer) = blank();
1508        let mut build = Builder::new(&mut func, block);
1509        access(&mut build, pointer, 16);
1510        let over = past(&mut build, pointer, 20);
1511        live(&mut build, over);
1512        build.ret(&[]);
1513        assert!(!run(&mut func).changed());
1514        assert_eq!(lives(&func), 2);
1515    }
1516
1517    #[test]
1518    fn a_lifetime_check_with_no_range_around_it_does_not_widen() {
1519        // Without the bounds check the first lifetime check speaks only for its own address, so
1520        // the one four bytes along is a different question and stays.
1521        let (_, mut func, block, pointer) = blank();
1522        let mut build = Builder::new(&mut func, block);
1523        live(&mut build, pointer);
1524        let field = past(&mut build, pointer, 4);
1525        live(&mut build, field);
1526        build.ret(&[]);
1527        assert!(!run(&mut func).changed());
1528        assert_eq!(lives(&func), 2);
1529    }
1530
1531    #[test]
1532    fn a_lifetime_check_a_call_stands_between_stays_and_is_counted() {
1533        // Section 8.8's number. This is the one the summaries were written for.
1534        let (mut names, mut func, block, pointer) = blank();
1535        let mut build = Builder::new(&mut func, block);
1536        access(&mut build, pointer, 16);
1537        let callee = names.intern("might_free");
1538        let signature = build.func().add_signature(Signature::new());
1539        build.call(callee, signature, &[]);
1540        let field = past(&mut build, pointer, 4);
1541        live(&mut build, field);
1542        build.ret(&[]);
1543        let stats = run(&mut func);
1544        assert!(!stats.changed());
1545        assert_eq!(lives(&func), 2);
1546        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
1547    }
1548
1549    #[test]
1550    fn a_lifetime_check_a_call_that_cannot_free_stands_between_goes() {
1551        let (mut names, mut func, block, pointer) = blank();
1552        let mut build = Builder::new(&mut func, block);
1553        access(&mut build, pointer, 16);
1554        let callee = names.intern("counts_them");
1555        let signature = build.func().add_signature(Signature::new());
1556        let call = build.call(callee, signature, &[]);
1557        let field = past(&mut build, pointer, 4);
1558        live(&mut build, field);
1559        build.ret(&[]);
1560        func[call].flags |= Flags::NOFREE;
1561        let stats = run(&mut func);
1562        assert_eq!(lives(&func), 1);
1563        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
1564    }
1565
1566    #[test]
1567    fn ending_a_lifetime_throws_the_facts_away() {
1568        // Nothing emits `meta_end` yet, so this is the test that says what will happen when
1569        // something does, rather than a test of anything the compiler does today.
1570        let (_, mut func, block, pointer) = blank();
1571        let mut build = Builder::new(&mut func, block);
1572        access(&mut build, pointer, 16);
1573        let size = build.iconst(Type::int(64), 16);
1574        let args = build.func().push_values(&[pointer, size]);
1575        build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
1576        access(&mut build, pointer, 16);
1577        build.ret(&[]);
1578        let stats = run(&mut func);
1579        assert!(!stats.changed());
1580        assert_eq!(checks(&func), 2);
1581        assert_eq!(lives(&func), 2);
1582        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
1583        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
1584    }
1585
1586    #[test]
1587    fn fuel_runs_out_over_both_kinds_of_check() {
1588        let (_, mut func, block, pointer) = blank();
1589        let mut build = Builder::new(&mut func, block);
1590        access(&mut build, pointer, 16);
1591        access(&mut build, pointer, 4);
1592        build.ret(&[]);
1593        let mut fuel = Fuel::of(1);
1594        let stats = Discharge.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
1595        assert_eq!(checks(&func), 1);
1596        assert_eq!(lives(&func), 2);
1597        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1598        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
1599    }
1600
1601    #[test]
1602    fn a_distance_too_large_to_be_a_real_access_is_not_discharged() {
1603        // The guard's bound. The two readings of the arithmetic agree while the numbers stay
1604        // small, so a rule proved at sixty four bits is not asked about anything else. Nothing
1605        // here is wrong, it simply is not proved, and a check that is not proved to be unnecessary
1606        // stays.
1607        let huge = i128::from(u64::MAX) * 4;
1608        let fact = Fact { base: Value::new(0), offset: 0, size: huge };
1609        let asked = Fact { base: Value::new(0), offset: huge / 2, size: 4 };
1610        assert!(!super::covers(&fact, &asked));
1611    }
1612
1613    #[test]
1614    fn a_range_of_addresses_wider_than_the_rule_allows_is_not_discharged() {
1615        // The guard on `reached.i64` bounds each of the three numbers at four gigabytes, for the
1616        // reason the rule file gives: past there the compiler's `i128` reading of the guard and the
1617        // solver's sixty four bit reading part company, and a rule proved under one and run under
1618        // the other is a rule proved about arithmetic that is not happening. A step whose range is
1619        // that wide is the usual case rather than a corner, since an index nothing has bounded says
1620        // nothing about where the access lands.
1621        let base = Value::new(0);
1622        let whole = Fact::whole(base, i128::from(u64::MAX) * 4);
1623        let asked = super::Reach { base, low: 0, width: i128::from(u64::MAX), size: 4 };
1624        assert!(!super::reaches(&whole, &asked));
1625    }
1626
1627    #[test]
1628    fn a_range_of_addresses_that_ends_where_the_object_does_is_discharged() {
1629        // Sixteen bytes, a step somewhere in nought to eleven, four bytes read. The last address
1630        // the walk can reach is the last one in the object, which is inside it.
1631        let base = Value::new(0);
1632        let whole = Fact::whole(base, 16);
1633        let asked = super::Reach { base, low: 0, width: 12, size: 4 };
1634        assert!(super::reaches(&whole, &asked));
1635        let over = super::Reach { base, low: 0, width: 13, size: 4 };
1636        assert!(!super::reaches(&whole, &over), "one byte further runs off the end");
1637    }
1638
1639    #[test]
1640    fn a_walk_by_a_bounded_step_off_a_local_takes_its_derivation_check_with_it() {
1641        // The shape `derives` cannot read at all: the pointer that went in is the slot and the one
1642        // that came out is a value past it, so the two are not one base and two constants. Both
1643        // ends widen to the slot, the slot holds both ranges, and one thing holding both is what a
1644        // derivation check asks about.
1645        let (_, mut func, block, _, index) = indexed();
1646        let mut build = Builder::new(&mut func, block);
1647        let slot = local(&mut build, 16);
1648        let step = low_bits(&mut build, index, 7);
1649        let at = walk(&mut build, slot, step);
1650        deriv(&mut build, slot, at, 4);
1651        build.ret(&[]);
1652        let stats = run(&mut func);
1653        assert_eq!(derivs(&func), 0);
1654        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 1);
1655    }
1656
1657    #[test]
1658    fn a_walk_that_can_leave_the_local_keeps_its_derivation_check() {
1659        // Nought to fifteen off a slot of eight. Every step is bounded and the answer is still no,
1660        // because the question is whether the slot holds every address the walk can reach.
1661        let (_, mut func, block, _, index) = indexed();
1662        let mut build = Builder::new(&mut func, block);
1663        let slot = local(&mut build, 8);
1664        let step = low_bits(&mut build, index, 15);
1665        let at = walk(&mut build, slot, step);
1666        deriv(&mut build, slot, at, 4);
1667        build.ret(&[]);
1668        let stats = run(&mut func);
1669        assert_eq!(derivs(&func), 1);
1670        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 0);
1671    }
1672
1673    #[test]
1674    fn a_lifetime_check_a_bounded_walk_lands_inside_a_checked_range_goes() {
1675        // An access over thirty two bytes establishes the range, and the lifetime check beside it
1676        // makes that range one a check found alive. The lifetime check on the walk then goes,
1677        // because every address the walk can reach is in the range that was found alive.
1678        //
1679        // Written off a parameter rather than a slot because a slot answers the narrow question on
1680        // its own. What has to answer this one is a range a check was passed on.
1681        let (_, mut func, block, pointer, index) = indexed();
1682        let mut build = Builder::new(&mut func, block);
1683        access(&mut build, pointer, 32);
1684        let step = low_bits(&mut build, index, 7);
1685        let at = walk(&mut build, pointer, step);
1686        live(&mut build, at);
1687        build.ret(&[]);
1688        let stats = run(&mut func);
1689        assert_eq!(lives(&func), 1, "the one in front of the access stays");
1690        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 1);
1691    }
1692
1693    #[test]
1694    fn a_lifetime_check_a_bounded_walk_can_leave_the_checked_range_keeps_it() {
1695        // The same over eight bytes, under a walk that can go fifteen past the start. A range of
1696        // eight bytes does not hold an address fifteen along from where it begins.
1697        let (_, mut func, block, pointer, index) = indexed();
1698        let mut build = Builder::new(&mut func, block);
1699        access(&mut build, pointer, 8);
1700        let step = low_bits(&mut build, index, 15);
1701        let at = walk(&mut build, pointer, step);
1702        live(&mut build, at);
1703        build.ret(&[]);
1704        let stats = run(&mut func);
1705        assert_eq!(lives(&func), 2);
1706        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 0);
1707    }
1708
1709    /// A stack slot of `size` bytes, in the entry block where the verifier wants one.
1710    fn local(build: &mut Builder<'_>, size: u64) -> Value {
1711        let info = MemInfo {
1712            size,
1713            align: 8,
1714            order: MemOrder::NotAtomic,
1715            tbaa: None,
1716            restrict: Restrict::NONE,
1717        };
1718        let extra = Extra::Mem(build.func().add_mem(info));
1719        build.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
1720    }
1721
1722    /// A function taking a pointer and an index, with one block.
1723    fn indexed() -> (Interner, Func, Block, Value, Value) {
1724        let mut names = Interner::new();
1725        let name = names.intern("f");
1726        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::int(64)]));
1727        let block = func.create_block();
1728        let pointer = func.append_param(block, Type::PTR);
1729        let index = func.append_param(block, Type::int(64));
1730        (names, func, block, pointer, index)
1731    }
1732
1733    /// A pointer a value past another one.
1734    fn walk(build: &mut Builder<'_>, pointer: Value, by: Value) -> Value {
1735        let args = build.func().push_values(&[pointer, by]);
1736        build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
1737    }
1738
1739    /// The low bits of a value, which is a step the ranges can put a number on.
1740    fn low_bits(build: &mut Builder<'_>, value: Value, mask: i128) -> Value {
1741        let bits = build.iconst(Type::int(64), mask);
1742        build.binary(Opcode::And, value, bits, Flags::NONE)
1743    }
1744
1745    #[test]
1746    fn a_walk_by_a_step_the_ranges_bound_inside_a_local_goes() {
1747        // Section 7.2's third source. The step is not a constant, so the walk stops at the
1748        // `ptr_add` and the fact that comes out is about a base nobody knows the size of. What
1749        // the ranges say is that the step is somewhere in nought to seven, so the four bytes the
1750        // access wants are somewhere in nought to eleven, and all of that is inside the sixteen
1751        // the slot is.
1752        let (_, mut func, block, _, index) = indexed();
1753        let mut build = Builder::new(&mut func, block);
1754        let slot = local(&mut build, 16);
1755        let step = low_bits(&mut build, index, 7);
1756        let at = walk(&mut build, slot, step);
1757        check(&mut build, at, 4);
1758        build.ret(&[]);
1759        let stats = run(&mut func);
1760        assert_eq!(checks(&func), 0);
1761        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
1762    }
1763
1764    #[test]
1765    fn a_walk_by_a_step_the_ranges_cannot_bound_is_left_alone() {
1766        // The same function with the mask taken off. A parameter can be anything, so the range of
1767        // addresses the walk reaches is the whole of memory and no slot covers it.
1768        let (_, mut func, block, _, index) = indexed();
1769        let mut build = Builder::new(&mut func, block);
1770        let slot = local(&mut build, 16);
1771        let at = walk(&mut build, slot, index);
1772        check(&mut build, at, 4);
1773        build.ret(&[]);
1774        let stats = run(&mut func);
1775        assert_eq!(checks(&func), 1);
1776        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
1777    }
1778
1779    #[test]
1780    fn a_walk_a_bounded_step_can_take_off_the_end_of_a_local_is_left_alone() {
1781        // Nought to seven again, four bytes again, and a slot of eight this time. The step being
1782        // bounded is not the question. The question is whether every address it can reach is
1783        // inside the slot, and seven plus four is not.
1784        let (_, mut func, block, _, index) = indexed();
1785        let mut build = Builder::new(&mut func, block);
1786        let slot = local(&mut build, 8);
1787        let step = low_bits(&mut build, index, 7);
1788        let at = walk(&mut build, slot, step);
1789        check(&mut build, at, 4);
1790        build.ret(&[]);
1791        let stats = run(&mut func);
1792        assert_eq!(checks(&func), 1);
1793        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
1794    }
1795
1796    #[test]
1797    fn a_constant_step_past_a_bounded_one_is_walked_too() {
1798        // A field of an element of an array of structs, which is the shape this is for. The array
1799        // index needs a range and the field offset does not, and the walk has to get through both.
1800        let (_, mut func, block, _, index) = indexed();
1801        let mut build = Builder::new(&mut func, block);
1802        let slot = local(&mut build, 32);
1803        let step = low_bits(&mut build, index, 15);
1804        let element = walk(&mut build, slot, step);
1805        let field = past(&mut build, element, 8);
1806        check(&mut build, field, 4);
1807        build.ret(&[]);
1808        let stats = run(&mut func);
1809        assert_eq!(checks(&func), 0);
1810        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
1811    }
1812
1813    #[test]
1814    fn what_a_range_discharge_records_is_the_bytes_and_not_the_range() {
1815        // The second check is the same bytes as the first, and the first went because a made up
1816        // range around it was inside the slot. What the first one proved is that those bytes are
1817        // in the slot, so the second one goes on that rather than on the ranges being asked all
1818        // over again.
1819        let (_, mut func, block, _, index) = indexed();
1820        let mut build = Builder::new(&mut func, block);
1821        let slot = local(&mut build, 16);
1822        let step = low_bits(&mut build, index, 7);
1823        let at = walk(&mut build, slot, step);
1824        check(&mut build, at, 4);
1825        check(&mut build, at, 4);
1826        build.ret(&[]);
1827        let stats = run(&mut func);
1828        assert_eq!(checks(&func), 0);
1829        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
1830        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1831    }
1832
1833    /// A stack slot whose size the program works out, which is what a variable length array is.
1834    fn growable(build: &mut Builder<'_>, size: Value) -> Value {
1835        let info = MemInfo {
1836            size: 0,
1837            align: 8,
1838            order: MemOrder::NotAtomic,
1839            tbaa: None,
1840            restrict: Restrict::NONE,
1841        };
1842        let extra = Extra::Mem(build.func().add_mem(info));
1843        let args = build.func().push_values(&[size]);
1844        build.value(InstData { args, extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
1845    }
1846
1847    #[test]
1848    fn a_check_of_bytes_inside_a_local_goes_with_nothing_in_front_of_it() {
1849        // Section 7.2's first source. No check established this and none had to: an `alloca` of
1850        // sixteen bytes is sixteen bytes of one storage instance because that is what it makes.
1851        let (_, mut func, block, _) = blank();
1852        let mut build = Builder::new(&mut func, block);
1853        let slot = local(&mut build, 16);
1854        let field = past(&mut build, slot, 8);
1855        check(&mut build, field, 4);
1856        build.ret(&[]);
1857        let stats = run(&mut func);
1858        assert_eq!(checks(&func), 0);
1859        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
1860    }
1861
1862    #[test]
1863    fn a_check_past_the_end_of_a_local_stays() {
1864        // The slot is sixteen bytes and the access runs to twenty. Nothing about it being a local
1865        // says anything about the four bytes after it, which belong to whatever the frame puts
1866        // there next.
1867        let (_, mut func, block, _) = blank();
1868        let mut build = Builder::new(&mut func, block);
1869        let slot = local(&mut build, 16);
1870        let field = past(&mut build, slot, 16);
1871        check(&mut build, field, 4);
1872        build.ret(&[]);
1873        let stats = run(&mut func);
1874        assert_eq!(checks(&func), 1);
1875        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
1876    }
1877
1878    #[test]
1879    fn a_check_of_bytes_inside_a_local_goes_across_a_call() {
1880        // The other half of what makes the fact worth having. A callee cannot free a frame slot,
1881        // so unlike everything the walk carries this one is not thrown away at a call.
1882        let (mut names, mut func, block, _) = blank();
1883        let mut build = Builder::new(&mut func, block);
1884        let slot = local(&mut build, 16);
1885        let callee = names.intern("might_free");
1886        let signature = build.func().add_signature(Signature::new());
1887        build.call(callee, signature, &[]);
1888        check(&mut build, slot, 4);
1889        build.ret(&[]);
1890        let stats = run(&mut func);
1891        assert_eq!(checks(&func), 0);
1892        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
1893        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
1894    }
1895
1896    #[test]
1897    fn a_check_inside_a_variable_length_array_stays() {
1898        // How many bytes it is is a value the program works out, and the payload's size field
1899        // reads zero. A pass that read it anyway would discharge every check in the array.
1900        let (_, mut func, block, _) = blank();
1901        let mut build = Builder::new(&mut func, block);
1902        let bytes = build.iconst(Type::int(64), 64);
1903        let slot = growable(&mut build, bytes);
1904        check(&mut build, slot, 4);
1905        build.ret(&[]);
1906        let stats = run(&mut func);
1907        assert_eq!(checks(&func), 1);
1908        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
1909    }
1910
1911    #[test]
1912    fn a_lifetime_check_in_a_local_goes_with_nothing_in_front_of_it() {
1913        // The frame slot rule, and the point is that neither of these has a check in front of it.
1914        // A slot is alive until the function returns, so a lifetime check anywhere inside one is
1915        // asking a question the `alloca` already answered.
1916        let (_, mut func, block, _) = blank();
1917        let mut build = Builder::new(&mut func, block);
1918        let slot = local(&mut build, 16);
1919        live(&mut build, slot);
1920        let field = past(&mut build, slot, 12);
1921        live(&mut build, field);
1922        build.ret(&[]);
1923        let stats = run(&mut func);
1924        assert_eq!(lives(&func), 0);
1925        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 2);
1926    }
1927
1928    #[test]
1929    fn a_lifetime_check_past_the_end_of_a_local_stays() {
1930        // The slot answers for its own bytes and no further, so an address outside it is a
1931        // different instance and a question nothing has answered.
1932        let (_, mut func, block, _) = blank();
1933        let mut build = Builder::new(&mut func, block);
1934        let slot = local(&mut build, 16);
1935        live(&mut build, slot);
1936        let field = past(&mut build, slot, 24);
1937        live(&mut build, field);
1938        build.ret(&[]);
1939        let stats = run(&mut func);
1940        assert_eq!(lives(&func), 1);
1941        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 1);
1942    }
1943
1944    #[test]
1945    fn something_ending_a_lifetime_turns_the_frame_slot_rule_off() {
1946        // The gate, and with it the widening the frame slot rule usually hides. With a `meta_end`
1947        // anywhere in the function the slot answers nothing, so the first check stays and pays,
1948        // and what takes the second one out is the first one widened to the whole slot.
1949        let (_, mut func, block, pointer) = blank();
1950        let mut build = Builder::new(&mut func, block);
1951        let slot = local(&mut build, 16);
1952        live(&mut build, slot);
1953        let field = past(&mut build, slot, 12);
1954        live(&mut build, field);
1955        let size = build.iconst(Type::int(64), 16);
1956        let args = build.func().push_values(&[pointer, size]);
1957        build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
1958        build.ret(&[]);
1959        let stats = run(&mut func);
1960        assert_eq!(lives(&func), 1);
1961        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 0);
1962        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
1963    }
1964
1965    /// Puts `cap_of` and a `check_deriv` for a walk from `from` to `to` into a block.
1966    ///
1967    /// The stride is the width of one element, which is what `rucc-safety` passes and what the
1968    /// runtime uses for a pointer that walked off the near end. This pass does not read it.
1969    fn deriv(build: &mut Builder<'_>, from: Value, to: Value, stride: i128) {
1970        let args = build.func().push_values(&[from]);
1971        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1972        let width = build.iconst(Type::int(64), stride);
1973        let args = build.func().push_values(&[capability, from, to, width]);
1974        build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
1975    }
1976
1977    /// How many derivation checks are left in a function.
1978    fn derivs(func: &Func) -> usize {
1979        func.blocks()
1980            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1981            .filter(|&inst| func[inst].opcode == Opcode::CheckDeriv)
1982            .count()
1983    }
1984
1985    #[test]
1986    fn a_walk_inside_a_checked_range_goes() {
1987        // Sixteen bytes were checked, and the walk goes from the start of them to eight in. Both
1988        // ends are in one range, so the second address is in the instance the first belongs to.
1989        let (_, mut func, block, pointer) = blank();
1990        let mut build = Builder::new(&mut func, block);
1991        check(&mut build, pointer, 16);
1992        let field = past(&mut build, pointer, 8);
1993        deriv(&mut build, pointer, field, 4);
1994        build.ret(&[]);
1995        let stats = run(&mut func);
1996        assert_eq!(derivs(&func), 0);
1997        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 1);
1998    }
1999
2000    #[test]
2001    fn a_walk_that_leaves_the_checked_range_stays() {
2002        // Four bytes were checked and the walk goes eight past them. Nothing here says the two
2003        // addresses are in one instance, which is the whole of what the check is about.
2004        let (_, mut func, block, pointer) = blank();
2005        let mut build = Builder::new(&mut func, block);
2006        check(&mut build, pointer, 4);
2007        let field = past(&mut build, pointer, 8);
2008        deriv(&mut build, pointer, field, 4);
2009        build.ret(&[]);
2010        let stats = run(&mut func);
2011        assert_eq!(derivs(&func), 1);
2012        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
2013    }
2014
2015    #[test]
2016    fn two_ranges_holding_one_end_each_do_not_answer_a_walk() {
2017        // The case the one fact rule is written for. Both addresses have been checked, so both are
2018        // inside some instance, and nothing says it is the same one. The walk stays.
2019        let (_, mut func, block, pointer) = blank();
2020        let mut build = Builder::new(&mut func, block);
2021        check(&mut build, pointer, 4);
2022        let field = past(&mut build, pointer, 64);
2023        check(&mut build, field, 4);
2024        deriv(&mut build, pointer, field, 4);
2025        build.ret(&[]);
2026        let stats = run(&mut func);
2027        assert_eq!(derivs(&func), 1);
2028        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
2029    }
2030
2031    #[test]
2032    fn a_walk_inside_a_local_goes_with_nothing_in_front_of_it() {
2033        // The shape almost every derivation check in real code has: a field of a local struct.
2034        // `rucc-safety` emits the walk before the bounds check on what it produced, so a fact from
2035        // an earlier check is usually the wrong size for it and the local is what answers.
2036        let (_, mut func, block, _) = blank();
2037        let mut build = Builder::new(&mut func, block);
2038        let slot = local(&mut build, 16);
2039        let field = past(&mut build, slot, 8);
2040        deriv(&mut build, slot, field, 4);
2041        build.ret(&[]);
2042        let stats = run(&mut func);
2043        assert_eq!(derivs(&func), 0);
2044        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 1);
2045    }
2046
2047    #[test]
2048    fn a_walk_off_the_end_of_a_local_stays() {
2049        // Where the slot stops is where the fact stops. One past the end is the case the runtime
2050        // has slack for and this pass does not use any of it.
2051        let (_, mut func, block, _) = blank();
2052        let mut build = Builder::new(&mut func, block);
2053        let slot = local(&mut build, 16);
2054        let field = past(&mut build, slot, 16);
2055        deriv(&mut build, slot, field, 4);
2056        build.ret(&[]);
2057        let stats = run(&mut func);
2058        assert_eq!(derivs(&func), 1);
2059        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 0);
2060    }
2061
2062    #[test]
2063    fn a_walk_a_call_stands_between_stays_and_is_counted() {
2064        // The same price the other two kinds pay, reported the same way, so the cost of not
2065        // trusting a call is a number per function rather than a paragraph.
2066        let (mut names, mut func, block, pointer) = blank();
2067        let mut build = Builder::new(&mut func, block);
2068        check(&mut build, pointer, 16);
2069        let callee = names.intern("might_free");
2070        let signature = build.func().add_signature(Signature::new());
2071        build.call(callee, signature, &[]);
2072        let field = past(&mut build, pointer, 8);
2073        deriv(&mut build, pointer, field, 4);
2074        build.ret(&[]);
2075        let stats = run(&mut func);
2076        assert_eq!(derivs(&func), 1);
2077        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_DERIV), 1);
2078    }
2079
2080    #[test]
2081    fn a_check_the_module_says_is_inside_a_global_goes_with_nothing_in_front_of_it() {
2082        // The other half of section 7.2's first source. The size of a global lives on the module
2083        // and this pass is given one function, so the answer arrives as a flag `crate::extents`
2084        // wrote before the pipeline started, and all three kinds carry it.
2085        let (_, mut func, block, pointer) = blank();
2086        let mut build = Builder::new(&mut func, block);
2087        let field = past(&mut build, pointer, 8);
2088        deriv(&mut build, pointer, field, 1);
2089        access(&mut build, field, 4);
2090        build.ret(&[]);
2091        marked(&mut func);
2092        let stats = run(&mut func);
2093        assert_eq!(checks(&func), 0);
2094        assert_eq!(lives(&func), 0);
2095        assert_eq!(derivs(&func), 0);
2096        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_STATIC), 1);
2097        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_STATIC), 1);
2098        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_STATIC), 1);
2099    }
2100
2101    #[test]
2102    fn a_check_the_module_says_every_caller_hands_in_goes_with_nothing_in_front_of_it() {
2103        // Section 7.5's summaries, arriving the same way a global's extent does and for the same
2104        // reason: which object a caller passes is a fact about a different function. What the flag
2105        // says is an extent and a lifetime, because the objects `crate::params` believes are a
2106        // caller's frame slot and a global and both are alive for as long as the call runs.
2107        let (_, mut func, block, pointer) = blank();
2108        let mut build = Builder::new(&mut func, block);
2109        let field = past(&mut build, pointer, 8);
2110        deriv(&mut build, pointer, field, 1);
2111        access(&mut build, field, 4);
2112        build.ret(&[]);
2113        flagged(&mut func, Flags::HANDED);
2114        let stats = run(&mut func);
2115        assert_eq!(checks(&func), 0);
2116        assert_eq!(lives(&func), 0);
2117        assert_eq!(derivs(&func), 0);
2118        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_HANDED), 1);
2119        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_HANDED), 1);
2120        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_HANDED), 1);
2121    }
2122
2123    /// A function that allocates `size` bytes and tests the answer against null.
2124    ///
2125    /// Gives back the block where the test has passed, the block where it has not, and the pointer.
2126    /// The flag is put on by hand, because which calls deserve it is a question about a module and
2127    /// `crate::heap` is what answers it.
2128    fn allocation(size: i128) -> (Interner, Func, Block, Block, Value) {
2129        let mut names = Interner::new();
2130        let name = names.intern("f");
2131        let mut func = Func::new(name, Signature::new());
2132        let entry = func.create_block();
2133        let inside = func.create_block();
2134        let outside = func.create_block();
2135        let mut build = Builder::new(&mut func, entry);
2136        let signature = build.func().add_signature(
2137            Signature::new().with_params(&[Type::int(64)]).with_returns(&[Type::PTR]),
2138        );
2139        let bytes = build.iconst(Type::int(64), size);
2140        let call = build.call(names.intern("malloc"), signature, &[bytes]);
2141        let at = build.func();
2142        at[call].flags |= Flags::HEAP;
2143        let pointer = at[call].results().next().expect("a call that gives back a pointer");
2144        let zero = build.iconst(Type::int(64), 0);
2145        let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
2146        let condition = build.icmp(IntPred::Ne, pointer, null);
2147        build.br_if(condition, inside, &[], outside, &[]);
2148        let mut build = Builder::new(&mut func, outside);
2149        build.ret(&[]);
2150        (names, func, inside, outside, pointer)
2151    }
2152
2153    #[test]
2154    fn a_check_inside_an_allocation_the_program_tested_goes() {
2155        // The third of the objects whose extent nobody had to check for. `malloc(16)` says how
2156        // many bytes it made in the call, and the branch on null is what makes it true here.
2157        let (_, mut func, inside, _, pointer) = allocation(16);
2158        let mut build = Builder::new(&mut func, inside);
2159        let field = past(&mut build, pointer, 8);
2160        deriv(&mut build, pointer, field, 1);
2161        access(&mut build, field, 4);
2162        build.ret(&[]);
2163        let stats = run(&mut func);
2164        assert_eq!(checks(&func), 0);
2165        assert_eq!(derivs(&func), 0);
2166        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 1);
2167        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 1);
2168        // The lifetime check is the one an allocation says nothing about, because a `free` in this
2169        // same function can end it, and it is what reports a use after free.
2170        assert_eq!(lives(&func), 1);
2171    }
2172
2173    #[test]
2174    fn a_check_on_an_allocation_nobody_tested_stays() {
2175        // Down the other arm the pointer is null, a null pointer is inside no object at all, and
2176        // the check is one that is supposed to fail.
2177        let (_, mut func, _, outside, pointer) = allocation(16);
2178        let mut build = Builder::new(&mut func, outside);
2179        access(&mut build, pointer, 4);
2180        build.ret(&[]);
2181        let stats = run(&mut func);
2182        assert_eq!(checks(&func), 1);
2183        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
2184    }
2185
2186    #[test]
2187    fn a_check_past_the_end_of_an_allocation_stays() {
2188        // Four bytes at offset fourteen is two bytes past the sixteen that were asked for, and
2189        // those two bytes are what the check is for.
2190        let (_, mut func, inside, _, pointer) = allocation(16);
2191        let mut build = Builder::new(&mut func, inside);
2192        let field = past(&mut build, pointer, 14);
2193        access(&mut build, field, 4);
2194        build.ret(&[]);
2195        let stats = run(&mut func);
2196        assert_eq!(checks(&func), 1);
2197        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
2198    }
2199
2200    #[test]
2201    fn a_walk_that_leaves_an_allocation_stays() {
2202        // One end inside and the other past the end is a walk out of the object, which is what a
2203        // derivation check is there to catch, so both ends have to be inside before it goes.
2204        let (_, mut func, inside, _, pointer) = allocation(16);
2205        let mut build = Builder::new(&mut func, inside);
2206        let field = past(&mut build, pointer, 32);
2207        deriv(&mut build, pointer, field, 1);
2208        build.ret(&[]);
2209        let stats = run(&mut func);
2210        assert_eq!(derivs(&func), 1);
2211        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 0);
2212    }
2213
2214    #[test]
2215    fn a_check_inside_an_allocation_goes_across_a_call() {
2216        // The other reason a fact read off the instruction is worth having. How many bytes an
2217        // allocator made is not something a callee can change, so unlike a fact from a check that
2218        // ran this one is still there on the far side of a call.
2219        let (mut names, mut func, inside, _, pointer) = allocation(16);
2220        let mut build = Builder::new(&mut func, inside);
2221        access(&mut build, pointer, 4);
2222        let signature = build.func().add_signature(Signature::new());
2223        build.call(names.intern("g"), signature, &[]);
2224        access(&mut build, pointer, 4);
2225        build.ret(&[]);
2226        let stats = run(&mut func);
2227        assert_eq!(checks(&func), 0);
2228        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 2);
2229        // Both lifetime checks stay, and the second one is the one a `free` inside `g` would make
2230        // report.
2231        assert_eq!(lives(&func), 2);
2232    }
2233
2234    #[test]
2235    fn a_check_every_caller_hands_in_goes_across_a_call() {
2236        // The reason the flag is worth having at all. A frame slot of the caller is not something
2237        // the callee's own callees can free, so the fact does not die at a call the way a fact
2238        // from a check that ran does.
2239        let (mut names, mut func, block, pointer) = blank();
2240        let mut build = Builder::new(&mut func, block);
2241        access(&mut build, pointer, 4);
2242        let signature = build.func().add_signature(Signature::new());
2243        build.call(names.intern("g"), signature, &[]);
2244        access(&mut build, pointer, 4);
2245        build.ret(&[]);
2246        flagged(&mut func, Flags::HANDED);
2247        let stats = run(&mut func);
2248        assert_eq!(checks(&func), 0);
2249        assert_eq!(lives(&func), 0);
2250        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_HANDED), 2);
2251        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_HANDED), 2);
2252    }
2253
2254    #[test]
2255    fn a_check_inside_a_global_goes_across_a_call() {
2256        // A callee can free what a global points at and cannot free the global, which lives as
2257        // long as the program does. So this is the one fact besides a local that a call leaves
2258        // standing, and it is read off the instruction rather than out of the scope for that
2259        // reason.
2260        let (mut names, mut func, block, pointer) = blank();
2261        let mut build = Builder::new(&mut func, block);
2262        let callee = names.intern("might_free");
2263        let signature = build.func().add_signature(Signature::new());
2264        build.call(callee, signature, &[]);
2265        access(&mut build, pointer, 4);
2266        build.ret(&[]);
2267        marked(&mut func);
2268        let stats = run(&mut func);
2269        assert_eq!(checks(&func), 0);
2270        assert_eq!(lives(&func), 0);
2271        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
2272        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 0);
2273    }
2274
2275    #[test]
2276    fn a_check_the_module_marked_costs_fuel_like_any_other() {
2277        // A discharge is a discharge whatever established the fact, so `-fpass-fuel` has to stop
2278        // this one too or a bisection would step over it.
2279        let (_, mut func, block, pointer) = blank();
2280        let mut build = Builder::new(&mut func, block);
2281        access(&mut build, pointer, 4);
2282        build.ret(&[]);
2283        marked(&mut func);
2284        let stats =
2285            Discharge.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
2286        assert_eq!(checks(&func) + lives(&func), 1);
2287        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
2288    }
2289
2290    #[test]
2291    fn a_walk_off_a_pointer_the_check_does_not_name_stays() {
2292        // The capability has to be the `cap_of` of the pointer that went in. One naming something
2293        // else is asking about a different instance and is not this pass's to answer.
2294        let (_, mut func, block, pointer) = blank();
2295        let mut build = Builder::new(&mut func, block);
2296        check(&mut build, pointer, 16);
2297        let field = past(&mut build, pointer, 8);
2298        let args = build.func().push_values(&[field]);
2299        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2300        let width = build.iconst(Type::int(64), 4);
2301        let args = build.func().push_values(&[capability, pointer, field, width]);
2302        build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
2303        build.ret(&[]);
2304        let stats = run(&mut func);
2305        assert_eq!(derivs(&func), 1);
2306        assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_SHAPE_DERIV), 1);
2307    }
2308}