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 conjunct that is not about bytes
60//!
61//! A `check_bounds` tests two things, because document 06 section 6.3 put the access alignment on
62//! it rather than in a check of its own: that the bytes are inside one instance, and that the
63//! address starts where an access of that alignment may start. Everything above is about the
64//! first. A check that goes takes the second away with it, so nothing goes until something has
65//! answered it, which is `aligned` below and which reads the object the address came from and the
66//! steps taken from it. An access that assumes nothing about where it starts has nothing to
67//! answer, and a member of a packed record is exactly that.
68//!
69//! A global is settled elsewhere and arrives as [`Flags::ALIGNED`], for the reason
70//! [`Flags::STATIC`] beside it exists: how aligned a global is lives on the module and a pass is
71//! given a function. Without that the gate would cost seventeen times what it costs, which is the
72//! measurement in the changelog and is what says the flag earns its bit.
73//!
74//! What is left is a pointer this function was handed, one it loaded out of memory, and one a call
75//! gave back. It is counted rather than argued about, the same as everything else here, so
76//! `-fopt-info-missed` says what the rest of the `!aligned` fact of section 6.2.4 would be worth.
77//!
78//! # The fact nobody had to check for
79//!
80//! Section 7.2 lists four sources of a discharge and puts the frontend first, because the majority
81//! of accesses in real C are to a local or a global at a constant offset and the bounds of either
82//! are not something anybody has to find out. An `alloca` of a fixed size makes one storage
83//! instance of that many bytes and says so in its payload, so the range from its address to that
84//! many further along is inside one instance for exactly the reason a passing `check_bounds` says
85//! its own range is. When the address a check is about normalizes to such an `alloca`, that range
86//! is the fact, and the question put to the table is the same question with the same rule
87//! answering it.
88//!
89//! Two things make it worth more than a fact a check established. It is there before anything has
90//! run, so the first access to a local is discharged rather than only the second. And no call takes
91//! it away: a callee cannot free a frame slot, whatever it does to whatever the slot points at, so
92//! this fact is asked separately rather than kept in the set the walk throws away at the first call
93//! it cannot see through.
94//!
95//! Only the fixed size form. A variable length array is an `alloca` with an operand and a payload
96//! whose size field reads zero, and reading it anyway would discharge every check in the array.
97//!
98//! A global is the same fact about the other half of section 7.2's sentence, and it arrives here
99//! differently for one reason: how big a global is lives on the module and this pass is given one
100//! function. So `crate::extents` works it out over the module before the pipeline starts, asks the
101//! same rule, and writes the answer onto the check as [`Flags::STATIC`], which is what
102//! `crate::nofree` does with what a call reaches and for the same reason. What is read here is what
103//! the IR says, the same way the pass reads an opcode.
104//!
105//! It answers a lifetime check as well as a bounds check, which a local does not. What a local
106//! gives is an extent, and how long it stays alive is the block it was declared in, which is a
107//! question this pass has nothing to say about. A global has static storage duration and is alive
108//! wherever the question is asked.
109//!
110//! # The walk that stops at a step it cannot read
111//!
112//! Everything above needs the address to be a base and a constant, and an array index is not a
113//! constant. The walk stops at the first `ptr_add` whose step is a value, and what comes out is a
114//! fact about a base whose size nobody knows, which answers nothing.
115//!
116//! Section 7.2's third source is what gets past it. Document 10's ranges know something about the
117//! step even though it is not a number: an index the program has already tested against a length,
118//! or one whose low bits are all that is used, is bounded. So the walk carries on, adding the low
119//! end of the step's range to the offset and the width of the range to the size, and what it ends
120//! up with is the range of addresses the access can land in.
121//!
122//! Whether an object holding all of that range holds the one address the access actually uses is
123//! its own rule, `reached.i64`, which leaves the distance opaque so that one answer covers every
124//! value the step could take. It is a rule of its own rather than the containment rule asked about
125//! the far end of the range, and the reason is section 7.7's: turning a range of addresses into one
126//! containment question is arithmetic on the thing being proved, and a pass doing that quietly is
127//! what the split between the walk and the rule exists to stop.
128//!
129//! What the range is asked of is the list above and not a shorter one: the local an `alloca`
130//! declares, the object an allocator made where the program has tested it, and the ranges checks
131//! that already ran established. The allocation was missing from that list until tamnd/rucc#880,
132//! which is what left a loop walking an index into its own `malloc` with every check it started
133//! with however plainly the call said how many bytes it made.
134//!
135//! The range is only ever asked with and never recorded. What a check proves when it runs is that
136//! the address the program used was inside the object, and nothing at all about the rest of a
137//! range this pass made up around it. So a check discharged this way records the narrow fact, the
138//! bytes the access really wanted, which is the thing that was proved and is what a second check
139//! of the same bytes is answered by.
140//!
141//! The ranges are built only for a function that has a walk by a value in it, because they cost a
142//! copy of the control flow graph and a function without one would never ask them anything.
143//!
144//! # The lifetime half, and what it borrows from the other one
145//!
146//! A `check_live` that stays is a fact too, and a smaller one than it looks: it says the storage
147//! instance holding its own address is alive, and it says nothing about the address four bytes
148//! along, because that address might be in a different instance. On its own that fact discharges
149//! only a second lifetime check of the very same address, and the shape `rucc-safety` emits is a
150//! lifetime check per field rather than per object, so on its own it would almost never fire.
151//!
152//! What makes it fire is the bounds fact sitting next to it. A `check_bounds` that passed put its
153//! whole range inside one instance, so if the lifetime check's address is in that range, the
154//! instance that was found alive is the instance the whole range is in, and the whole range is
155//! alive. So a lifetime fact is recorded as the widest checked range containing its address, and a
156//! later lifetime check is asked about as a single byte. The question of whether that byte is in
157//! that range is the same question the bounds half asks, put to the same rule.
158//!
159//! The order the two arrive in is what makes this work rather than a coincidence to be careful
160//! about: `rucc-safety` emits the bounds check first and the lifetime check second, so the range is
161//! established by the time there is a lifetime fact to widen. A lifetime check that arrives with no
162//! range around it keeps the narrow fact, which is correct and worth little.
163//!
164//! # The derivation half, which is one question rather than two
165//!
166//! `rucc-safety` puts a `check_deriv` after every `ptr_add` off a pointer, and what it asks is not
167//! about a range at all: it asks whether the pointer that came out is still in the storage instance
168//! the pointer that went in belongs to. The runtime has some slack in it for a pointer that walked
169//! exactly off either end, and none of that slack is used here, because the case this pass answers
170//! is the one where both ends are plainly inside something.
171//!
172//! What answers it is one fact holding both ends. A `check_bounds` that passed put its whole range
173//! inside one instance, so if the address that went in and the address that came out are both in
174//! that range, the second is in the instance the first belongs to, which is the question. It has to
175//! be one fact and not one for each end: two facts saying two addresses are each inside some
176//! instance say nothing about whether it is the same instance, and that is the only thing being
177//! asked. A local is a fact of exactly this shape and is asked the same way.
178//!
179//! Both ends are asked about as a single byte, the way a lifetime check is, and for the same reason.
180//! Nothing here is claiming anything about how many bytes are readable at either address.
181//!
182//! A `check_deriv` that stays leaves no fact behind. What it establishes is that two addresses share
183//! an instance, which is not a range of bytes and does not fit in what this walk carries, and the
184//! `covered.i64` rule has nothing to say about it. Recording it would mean a second kind of fact and
185//! a second rule, and the pointer it is about nearly always gets a `check_bounds` of its own a few
186//! instructions later that establishes the range properly.
187//!
188//! # Why a call throws the facts away, and which calls do not
189//!
190//! Section 7.3 says nothing kills a bounds fact except a redefinition of the capability, which in
191//! SSA is never, and this pass is stricter than that: a call, or anything else this pass cannot see
192//! through, drops every fact it is carrying.
193//!
194//! The case is a `free` and then an allocation of something smaller at the same address. The range
195//! established before the call is no longer inside one instance after it, and what document 07
196//! leaves that to is the lifetime judgement rather than this one. Today's lifetime check is about
197//! the address rather than about the version the capability was taken at, so it would not refuse
198//! the access either, and a rate this pass reports is worth less than a hole it opens. The strict
199//! version is what is written first.
200//!
201//! A `meta_end` and a `meta_transfer` drop the facts as well. Nothing emits either one yet, so
202//! this costs nothing today and is the difference between conservative and wrong on the day the
203//! instrumentation starts ending lifetimes. `crate::nofree` treats them the same way.
204//!
205//! The two facts nobody had to check for go across a call untouched, and neither is an exception to
206//! the paragraph above because neither is in the set being thrown away. A callee cannot free a
207//! frame slot and cannot free a global, so a check the declaration answers is answered on the far
208//! side of any call at all.
209//!
210//! A call that says it reaches nothing which can free is the exception, and it is not this pass
211//! being trusting. `crate::nofree` works the answer out over the whole module before the pipeline
212//! starts and writes it onto the call site as [`Flags::NOFREE`], because the fact belongs to the
213//! callee and a pass is given one function. Reading it here is reading what the IR says, the same
214//! way the pass reads an opcode. Nothing else about a call is believed: the facts still go across
215//! an unmarked call, a call through an address, and inline assembly.
216//!
217//! What the strictness still costs is measured rather than guessed. A check that a fact would have
218//! covered if a call had not intervened is counted, so `-fopt-info-missed` says per function what
219//! is left to win.
220
221use std::collections::{HashMap, HashSet};
222
223use rucc_ir::{Block, Def, Extra, Flags, Func, Inst, Opcode, Value};
224
225use crate::range::query::Ranges;
226use crate::rules::{Piece, Subject, Table, safety};
227use crate::{Analyses, Analysis, Cfg, Fuel, Pass, Preserved, Stats, heap};
228
229/// Recorded once for each bounds check taken out.
230const REMOVED: &str = "bounds check removed, a dominating check covers the same bytes";
231
232/// Recorded once for each bounds check taken out because it was inside a local.
233const REMOVED_LOCAL: &str = "bounds check removed, its bytes are inside a local this function \
234                             declares";
235
236/// Recorded once for each bounds check taken out because it was inside a global.
237const REMOVED_STATIC: &str = "bounds check removed, its bytes are inside an object of static \
238                              storage duration";
239
240/// Recorded once for each bounds check taken out because every caller hands in the object.
241const REMOVED_HANDED: &str = "bounds check removed, its bytes are inside an object every call to \
242                              this function hands it";
243
244/// Recorded once for each bounds check taken out because an allocator made the object.
245const REMOVED_MADE: &str = "bounds check removed, its bytes are inside an object an allocator made \
246                            and this function has tested";
247
248/// Recorded once for each bounds check taken out because a range answered the step it walked by.
249const REMOVED_RANGE: &str = "bounds check removed, every address the walk can reach is inside the \
250                             object it started from";
251
252/// Recorded once for each lifetime check taken out.
253const REMOVED_LIVE: &str = "lifetime check removed, a dominating check covers the same storage";
254
255/// Recorded once for each lifetime check taken out because it was inside a global.
256const REMOVED_LIVE_STATIC: &str =
257    "lifetime check removed, its storage lives as long as the program does";
258
259/// Recorded once for each lifetime check taken out because every caller hands in the object.
260const REMOVED_LIVE_HANDED: &str = "lifetime check removed, its storage is an object every call to \
261                                   this function hands it";
262
263/// Recorded once for each lifetime check taken out because it was inside a frame slot.
264const REMOVED_LIVE_LOCAL: &str =
265    "lifetime check removed, its storage is a frame slot of this function";
266
267/// Recorded once for each lifetime check taken out because a range answered the step it walked by.
268const REMOVED_LIVE_RANGE: &str = "lifetime check removed, every address the walk can reach is in \
269                                  storage a check found alive";
270
271/// Recorded for a bounds check that would have gone if there had been fuel for it.
272const NO_FUEL: &str = "bounds check kept, the pass ran out of fuel";
273
274/// Recorded for a lifetime check that would have gone if there had been fuel for it.
275const NO_FUEL_LIVE: &str = "lifetime check kept, the pass ran out of fuel";
276
277/// Recorded once for each derivation check taken out because a range answered the step it walked by.
278const REMOVED_DERIV_RANGE: &str = "derivation check removed, every address either end can reach is \
279                                   inside one checked range";
280
281/// Recorded for a bounds check a call cost, which is the honest price of the paragraph above.
282///
283/// This one is worth reading rather than skipping. It is the number of checks that are still being
284/// paid for because `crate::nofree` could not vouch for a call, so it says per function what the
285/// rest of section 7.5's summary work would be worth before anybody writes it.
286const PAST_A_CALL: &str =
287    "bounds check kept, a call between it and the check that covers it might free";
288
289/// The same, for a lifetime check. Section 8.8 is about this number rather than the one above.
290const PAST_A_CALL_LIVE: &str =
291    "lifetime check kept, a call between it and the check that covers it might free";
292
293/// Recorded for a bounds check kept because nothing here says where the access starts.
294///
295/// The alignment conjunct of judgement J1 rides on `check_bounds`, so taking the check out takes
296/// the alignment test with it. Recorded only for a check a rule had already answered the bytes of,
297/// so the number is what the gate costs rather than how many checks have an alignment, which makes
298/// it what the `!aligned` fact of `spec/safe-memory/06-instrumentation.md` section 6.2.4 would be
299/// worth.
300const UNKNOWN_ALIGNMENT: &str =
301    "bounds check kept, nothing here says the address is aligned to what the access assumes";
302
303/// Recorded for a bounds check whose operands this pass cannot read.
304const UNKNOWN_SHAPE: &str = "bounds check left alone, its pointer is not a base and a constant";
305
306/// Recorded for a bounds check about a range the program worked out.
307const COMPUTED_EXTENT: &str =
308    "bounds check left alone, how many bytes it covers is a number only the program has";
309
310/// Recorded for a lifetime check whose operands this pass cannot read.
311const UNKNOWN_SHAPE_LIVE: &str =
312    "lifetime check left alone, its pointer is not a base and a constant";
313
314/// Recorded once for each derivation check taken out.
315const REMOVED_DERIV: &str =
316    "derivation check removed, one checked range holds both the pointer and where it walked to";
317
318/// Recorded once for each derivation check taken out because it walked inside a local.
319const REMOVED_DERIV_LOCAL: &str =
320    "derivation check removed, it walks inside a local this function declares";
321
322/// Recorded once for each derivation check taken out because it walked inside a global.
323const REMOVED_DERIV_STATIC: &str =
324    "derivation check removed, it walks inside an object of static storage duration";
325
326/// Recorded once for each derivation check taken out because every caller hands in the object.
327const REMOVED_DERIV_HANDED: &str = "derivation check removed, it walks inside an object every call \
328                                    to this function hands it";
329
330/// Recorded once for each derivation check taken out because an allocator made the object.
331const REMOVED_DERIV_MADE: &str = "derivation check removed, it walks inside an object an allocator \
332                                  made and this function has tested";
333
334/// Recorded for a derivation check that would have gone if there had been fuel for it.
335const NO_FUEL_DERIV: &str = "derivation check kept, the pass ran out of fuel";
336
337/// Recorded for a derivation check a call cost.
338const PAST_A_CALL_DERIV: &str =
339    "derivation check kept, a call between it and the range that holds both ends might free";
340
341/// Recorded for a derivation check naming a capability that is not the one it is about.
342const NOT_ITS_CAPABILITY_DERIV: &str = "derivation check left alone, the capability it names is not the one the pointer that went in \
343     carries";
344
345/// Recorded for a derivation check whose two ends are not off one value.
346const TWO_BASES_DERIV: &str =
347    "derivation check left alone, its two pointers are not built on one base";
348
349/// Recorded for a derivation check whose walk can reach past the end of the local it starts in.
350const OVER_THE_LOCAL_DERIV: &str =
351    "derivation check left alone, the walk can reach past the end of the local it starts in";
352
353/// Recorded for a derivation check on a pointer this function loaded out of memory.
354const NO_EXTENT_LOADED: &str = "derivation check left alone, nothing here says how big the object \
355                                is and the pointer to it was loaded from memory";
356
357/// Recorded for a derivation check on a pointer this function was handed.
358const NO_EXTENT_HANDED: &str = "derivation check left alone, nothing here says how big the object \
359                                is and the pointer to it was handed to this function";
360
361/// Recorded for a derivation check on a pointer into a global.
362const NO_EXTENT_GLOBAL: &str = "derivation check left alone, nothing here says how big the object \
363                                is and the pointer to it is into a global";
364
365/// Recorded for a derivation check on a pointer a call handed back.
366const NO_EXTENT_RETURNED: &str = "derivation check left alone, nothing here says how big the \
367                                  object is and the pointer to it came back from a call";
368
369/// Recorded for a derivation check on a pointer none of the shapes above describes.
370const NO_EXTENT_OTHER: &str =
371    "derivation check left alone, nothing here says how big the object its pointers are in is";
372
373/// The pass. It holds nothing, because everything it works out is about one function.
374/// Which of the places a fact comes from a run of this pass may ask.
375///
376/// Everything is asked normally and there is one pass in the pipeline. The others are here for the
377/// measurement `spec/safe-memory/13-performance.md` section 13.5 asks for and
378/// `spec/safe-memory/17-open-questions.md` question 3 is: how much each source discharges on its
379/// own, and how much the same sources discharge together. A number for a source on its own cannot
380/// be read off the remarks of a full run, because the rules are asked in an order and whichever one
381/// answers first is the one the remark names, so the second source to be asked about a check two of
382/// them could answer looks like it answered nothing.
383///
384/// The four are document 07 section 7.2's four, with the caveat the measurement found: the ranges
385/// are not a fourth kind of fact but a way of asking the other three about a subscript instead of
386/// about an address written out in the program.
387#[derive(Clone, Copy, PartialEq, Eq, Debug)]
388pub struct Sources {
389    /// How big an object is, read off whatever made it. A global's extent comes from
390    /// `crate::extents`, a local's from its `alloca`, an allocation's from the call `crate::heap`
391    /// marked. Section 7.2's first source.
392    objects: bool,
393    /// What a check that has already run established, carried down the dominator tree. Section 7.3,
394    /// and the one the literature calls redundant check elimination.
395    dominance: bool,
396    /// What every caller of this function guarantees about what it was handed, from
397    /// `crate::params`. Section 7.5.
398    summaries: bool,
399    /// The value ranges and the recurrences, which widen the one address a check names into the
400    /// range of addresses a walk can reach so that the other three can be asked about a subscript.
401    /// Section 7.4, and the half of the PICO result this pass holds. The other half is
402    /// [`crate::hoist`] and [`crate::split`], which are passes of their own and have flags of their
403    /// own.
404    ranges: bool,
405}
406
407impl Sources {
408    /// Every one of them, which is what the pipeline runs.
409    pub const ALL: Self = Self { objects: true, dominance: true, summaries: true, ranges: true };
410    /// What an object says about itself and nothing else.
411    pub const OBJECTS: Self =
412        Self { objects: true, dominance: false, summaries: false, ranges: false };
413    /// What an earlier check established and nothing else.
414    pub const DOMINANCE: Self =
415        Self { objects: false, dominance: true, summaries: false, ranges: false };
416    /// What every caller guarantees and nothing else.
417    pub const SUMMARIES: Self =
418        Self { objects: false, dominance: false, summaries: true, ranges: false };
419    /// Every fact, asked only about addresses written out in the program.
420    pub const NARROW: Self =
421        Self { objects: true, dominance: true, summaries: true, ranges: false };
422}
423
424#[derive(Debug, Clone, Copy, PartialEq, Eq)]
425pub struct Discharge {
426    /// What `-f<name>` and `-fno-<name>` reach this run by.
427    name: &'static str,
428    /// Which places it may take a fact from. See [`Sources`].
429    sources: Sources,
430}
431
432/// The pass the pipeline runs, which asks everything.
433pub static DISCHARGE: Discharge = Discharge { name: "discharge", sources: Sources::ALL };
434
435/// The same pass asking an object how big it is and nothing else.
436pub static OBJECTS: Discharge = Discharge { name: "discharge-objects", sources: Sources::OBJECTS };
437
438/// The same pass asking what an earlier check established and nothing else.
439pub static DOMINANCE: Discharge =
440    Discharge { name: "discharge-dominance", sources: Sources::DOMINANCE };
441
442/// The same pass asking what every caller guarantees and nothing else.
443pub static SUMMARIES: Discharge =
444    Discharge { name: "discharge-summaries", sources: Sources::SUMMARIES };
445
446/// The same pass asking every fact, about addresses written out in the program only.
447pub static NARROW: Discharge = Discharge { name: "discharge-narrow", sources: Sources::NARROW };
448
449/// The same pass asking everything, under a name of its own.
450///
451/// [`DISCHARGE`] already asks everything, so this looks like a duplicate and is not. A pass the level
452/// did not choose goes on the end of the pipeline, so a run of `-fno-discharge -fdischarge-objects`
453/// asks its question in a different place from where the shipped pass asks it, and the two numbers
454/// are not comparable. This one is turned on the same way as the others and lands in the same place,
455/// so the sum of the parts and the whole are measured under one arrangement. What it costs against
456/// [`DISCHARGE`] is what the position is worth, which is a number the measurement wants anyway.
457pub static EVERY: Discharge = Discharge { name: "discharge-every", sources: Sources::ALL };
458
459impl Pass for Discharge {
460    fn name(&self) -> &'static str {
461        self.name
462    }
463
464    fn describe(&self) -> &'static str {
465        "a bounds, lifetime or derivation check whose answer is already known is removed"
466    }
467
468    fn preserves(&self) -> Preserved {
469        // Instructions go and blocks do not. A check is not a terminator and removing one leaves
470        // every edge where it was. What it does not leave where it was is the liveness, because
471        // the check was reading something and now nothing is.
472        Preserved::ALL.without(Analysis::Liveness)
473    }
474
475    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
476        let mut stats = Stats::new();
477        let Some(entry) = func.entry() else { return stats };
478        let dom = an.dominators(func).clone();
479
480        // The graph is built for two reasons and neither is the common one, so a function with
481        // neither pays for no copy of it. The ranges want it when there is a walk the constant
482        // reader gives up on, and the allocation rule wants it to find where the program has tested
483        // what an allocator gave it.
484        let walks = self.sources.ranges && walks_by_a_value(func);
485        let cfg = (walks || (self.sources.objects && heap::allocates(func)))
486            .then(|| an.cfg(func).clone());
487        let mut ranges = cfg.as_ref().filter(|_| walks).map(|cfg| Ranges::new(&*func, cfg, &dom));
488
489        // One answer per allocation rather than one per check, because a function that reads twenty
490        // fields of the same object asks the same question about the same pointer twenty times.
491        let mut checked: HashMap<Value, HashSet<Block>> = HashMap::new();
492
493        // Whether anything in here says a lifetime is over. Read once over the whole function
494        // rather than carried down the walk, because what the frame slot rule needs is that no
495        // `meta_end` runs before the check on any path, and a fact carried down the dominator
496        // tree only ever says something about the paths that go through one block.
497        let ends = ends_a_lifetime(func);
498
499        // The walk is a stack rather than recursion because the dominator tree of a long chain of
500        // blocks is as deep as the function is long, and a pass is not a place to find that out.
501        // Each block carries its own copy of what holds at its start, which is what makes a fact a
502        // call killed in one arm of a branch still hold in the other.
503        let mut going: Vec<(Inst, &'static str)> = Vec::new();
504        let mut work = vec![(entry, Scope::default())];
505        while let Some((block, mut scope)) = work.pop() {
506            for inst in func.insts(block).collect::<Vec<Inst>>() {
507                if opaque(func, inst) {
508                    scope.forget();
509                    continue;
510                }
511                match func[inst].opcode {
512                    Opcode::CheckBounds => {
513                        if func[func[inst].args].len() > 2 {
514                            stats.missed(COMPUTED_EXTENT);
515                            continue;
516                        }
517                        let Some(asked) = about(func, inst) else {
518                            stats.missed(UNKNOWN_SHAPE);
519                            continue;
520                        };
521                        // The four objects whose extent is known without anybody having checked
522                        // it. A global was worked out over the module by `crate::extents` and an
523                        // object every caller hands in by `crate::params`, both of which arrive as
524                        // a flag; a local is read off its `alloca` here and an allocation off the
525                        // call `crate::heap` marked. All four are asked of the same rule as every
526                        // other fact. The reach of a walk the constant reader could not finish is
527                        // asked last, because it is the only one that costs an analysis to answer.
528                        let why = if self.sources.objects
529                            && func[inst].flags.contains(Flags::STATIC)
530                        {
531                            Some(REMOVED_STATIC)
532                        } else if self.sources.summaries && func[inst].flags.contains(Flags::HANDED)
533                        {
534                            Some(REMOVED_HANDED)
535                        } else if self.sources.objects
536                            && declared(func, asked.base)
537                                .is_some_and(|local| covers(&local, &asked))
538                        {
539                            Some(REMOVED_LOCAL)
540                        } else if self.sources.objects
541                            && allocated(func, cfg.as_ref(), &mut checked, block, &[&asked])
542                        {
543                            Some(REMOVED_MADE)
544                        } else if self.sources.dominance && scope.bounds.covers(&asked) {
545                            Some(REMOVED)
546                        } else {
547                            // The same four sources in the same order, asked of the range of
548                            // addresses the walk can reach rather than of the one address the
549                            // constant reader could name. A flag has already been read above and
550                            // reading it again would say the same thing, so what is left is the
551                            // local, the allocation and what the walk carries.
552                            reach(func, ranges.as_mut(), &asked, inst).and_then(|wide| {
553                                if self.sources.objects
554                                    && declared(func, wide.base)
555                                        .is_some_and(|local| reaches(&local, &wide))
556                                {
557                                    Some(REMOVED_RANGE)
558                                } else if self.sources.objects
559                                    && allocated_around(
560                                        func,
561                                        cfg.as_ref(),
562                                        &mut checked,
563                                        block,
564                                        &[&wide],
565                                    )
566                                {
567                                    Some(REMOVED_MADE)
568                                } else if self.sources.dominance && scope.bounds.reaches(&wide) {
569                                    Some(REMOVED_RANGE)
570                                } else {
571                                    None
572                                }
573                            })
574                        };
575                        // Asked once a rule has answered the bounds rather than in front of them
576                        // all, because a check that was staying anyway costs the gate nothing and
577                        // the number somebody reads has to be what it actually costs. A check kept
578                        // here still runs, so it still establishes what it was about.
579                        let why = why.filter(|_| {
580                            aligned(func, inst) || {
581                                stats.missed(UNKNOWN_ALIGNMENT);
582                                false
583                            }
584                        });
585                        let Some(why) = why else {
586                            if scope.bounds.covered_before(&asked) {
587                                stats.missed(PAST_A_CALL);
588                            }
589                            // A check that stays is a check that runs, and a check that runs
590                            // establishes what it was about. One that was removed establishes
591                            // nothing new: whatever covered it covers everything it would have.
592                            scope.bounds.held.push(asked);
593                            continue;
594                        };
595                        if !fuel.take() {
596                            stats.missed(NO_FUEL);
597                            scope.bounds.held.push(asked);
598                            continue;
599                        }
600                        // A check that goes normally establishes nothing new, because whatever
601                        // answered it covers everything it would have. The range is the one
602                        // exception: what answered it was a fact about a made up range around the
603                        // address, and the next check on these bytes has to ask for that range
604                        // again and may not get the same answer. So the narrow fact goes in, which
605                        // is the thing that was actually proved.
606                        if why == REMOVED_RANGE {
607                            scope.bounds.held.push(asked);
608                        }
609                        going.push((inst, why));
610                    }
611                    Opcode::CheckLive => {
612                        let Some(asked) = alive(func, inst) else {
613                            stats.missed(UNKNOWN_SHAPE_LIVE);
614                            continue;
615                        };
616                        // A global is alive as long as the program is, and a frame slot is alive
617                        // until the function returns, so both objects whose extent is known
618                        // without anybody having checked it answer this as well as a bounds
619                        // check. `ends` is what makes the second one true: where a local stops
620                        // being alive is written into the IR as `meta_end` and not read off the
621                        // shape of the source, so a function with one in it is a function this
622                        // does not claim anything about.
623                        let why = if self.sources.objects
624                            && func[inst].flags.contains(Flags::STATIC)
625                        {
626                            Some(REMOVED_LIVE_STATIC)
627                        } else if self.sources.summaries && func[inst].flags.contains(Flags::HANDED)
628                        {
629                            Some(REMOVED_LIVE_HANDED)
630                        } else if self.sources.objects
631                            && !ends
632                            && declared(func, asked.base)
633                                .is_some_and(|local| covers(&local, &asked))
634                        {
635                            Some(REMOVED_LIVE_LOCAL)
636                        } else if self.sources.dominance && scope.alive.covers(&asked) {
637                            Some(REMOVED_LIVE)
638                        } else {
639                            // A lifetime fact and not a bounds one, because what is being asked
640                            // is whether the storage is alive and a bounds check that passed says
641                            // nothing about that. The widening argument is the bounds arm's: a
642                            // range known alive that holds every address the walk can reach holds
643                            // the one it actually uses.
644                            reach(func, ranges.as_mut(), &asked, inst)
645                                .filter(|wide| {
646                                    (self.sources.objects
647                                        && !ends
648                                        && declared(func, wide.base)
649                                            .is_some_and(|local| reaches(&local, wide)))
650                                        || (self.sources.dominance && scope.alive.reaches(wide))
651                                })
652                                .map(|_| REMOVED_LIVE_RANGE)
653                        };
654                        let Some(why) = why else {
655                            if scope.alive.covered_before(&asked) {
656                                stats.missed(PAST_A_CALL_LIVE);
657                            }
658                            scope.alive.held.push(widened(func, &scope.bounds, asked));
659                            continue;
660                        };
661                        if !fuel.take() {
662                            stats.missed(NO_FUEL_LIVE);
663                            scope.alive.held.push(widened(func, &scope.bounds, asked));
664                            continue;
665                        }
666                        // The bounds arm's exception, for its reason. A range answered a made up
667                        // range around this address, so what was proved is about the address.
668                        if why == REMOVED_LIVE_RANGE {
669                            scope.alive.held.push(widened(func, &scope.bounds, asked));
670                        }
671                        going.push((inst, why));
672                    }
673                    Opcode::CheckDeriv => {
674                        let narrow = derives(func, inst);
675                        let why = narrow.and_then(|(from, to)| {
676                            if self.sources.objects && func[inst].flags.contains(Flags::STATIC) {
677                                Some(REMOVED_DERIV_STATIC)
678                            } else if self.sources.summaries
679                                && func[inst].flags.contains(Flags::HANDED)
680                            {
681                                Some(REMOVED_DERIV_HANDED)
682                            } else if self.sources.objects
683                                && declared(func, from.base).is_some_and(|local| {
684                                    covers(&local, &from) && covers(&local, &to)
685                                })
686                            {
687                                Some(REMOVED_DERIV_LOCAL)
688                            } else if self.sources.objects
689                                && allocated(func, cfg.as_ref(), &mut checked, block, &[&from, &to])
690                            {
691                                Some(REMOVED_DERIV_MADE)
692                            } else if self.sources.dominance && scope.bounds.holds_both(&from, &to)
693                            {
694                                Some(REMOVED_DERIV)
695                            } else {
696                                None
697                            }
698                        });
699                        // Asked last, and asked off the check's own operands rather than off what
700                        // `derives` worked out, because the case it is for is the one `derives`
701                        // cannot read at all: past a step the constant reader gives up on the two
702                        // ends are not one base and two constants. One thing has to hold both of
703                        // the ranges, for the same reason one thing has to hold both of the
704                        // addresses, which is that two things saying each end is inside something
705                        // say nothing about it being the same something.
706                        let why = why.or_else(|| {
707                            spread(func, ranges.as_mut(), inst, inst).and_then(|(near, far)| {
708                                if self.sources.objects
709                                    && declared(func, near.base).is_some_and(|local| {
710                                        reaches(&local, &near) && reaches(&local, &far)
711                                    })
712                                {
713                                    Some(REMOVED_DERIV_RANGE)
714                                } else if self.sources.objects
715                                    && allocated_around(
716                                        func,
717                                        cfg.as_ref(),
718                                        &mut checked,
719                                        block,
720                                        &[&near, &far],
721                                    )
722                                {
723                                    Some(REMOVED_DERIV_MADE)
724                                } else if self.sources.dominance
725                                    && scope.bounds.reaches_both(&near, &far)
726                                {
727                                    Some(REMOVED_DERIV_RANGE)
728                                } else {
729                                    None
730                                }
731                            })
732                        });
733                        let Some(why) = why else {
734                            match narrow {
735                                Some((from, to)) => {
736                                    if scope.bounds.held_both_before(&from, &to) {
737                                        stats.missed(PAST_A_CALL_DERIV);
738                                    }
739                                }
740                                None => {
741                                    stats.missed(unreadable(func, ranges.as_mut(), inst));
742                                }
743                            }
744                            continue;
745                        };
746                        if !fuel.take() {
747                            stats.missed(NO_FUEL_DERIV);
748                            continue;
749                        }
750                        going.push((inst, why));
751                    }
752                    _ => continue,
753                }
754            }
755            for child in dom.children(block) {
756                work.push((child, scope.clone()));
757            }
758        }
759
760        for (inst, why) in going {
761            func.remove_inst(inst);
762            stats.optimized(why);
763        }
764        stats
765    }
766}
767
768/// A range of bytes some check has already been passed on, or is being asked about.
769///
770/// The address is kept as the value it was computed from and the constant distance from it, rather
771/// than as the pointer itself, because that is what makes two of these comparable: the whole of
772/// what this pass knows about two addresses is that they are one value plus two constants.
773#[derive(Debug, Clone, Copy, PartialEq, Eq)]
774pub(crate) struct Fact {
775    /// The value the address was computed from.
776    pub(crate) base: Value,
777    /// How far past it the access starts.
778    pub(crate) offset: i128,
779    /// How many bytes it covers.
780    size: i128,
781}
782
783impl Fact {
784    /// The whole of an object whose extent is known, starting at its own address.
785    ///
786    /// The two sources of one of these are an `alloca` of a fixed size and a global, and what they
787    /// have in common is that the size is said by something other than a check that passed.
788    pub(crate) fn whole(base: Value, size: i128) -> Self {
789        Self { base, offset: 0, size }
790    }
791}
792
793/// A range of addresses an access can land in, and how many bytes it takes when it does.
794///
795/// What [`reach`] works out and the only thing it is used for. It is deliberately not a [`Fact`]:
796/// a fact is something that was established and may be recorded, and this is a question and may
797/// not. The address the program uses is `base` plus somewhere between `low` and `low` plus `width`
798/// further along, and what a check proves when it runs is about that one address rather than about
799/// the range this was made out of.
800#[derive(Debug, Clone, Copy)]
801struct Reach {
802    /// The value the address was computed from.
803    base: Value,
804    /// The nearest the access can start to it.
805    low: i128,
806    /// How much further than that it can start.
807    width: i128,
808    /// How many bytes it covers.
809    size: i128,
810}
811
812/// One kind of fact, and what has become of it.
813#[derive(Debug, Clone, Default)]
814struct Known {
815    /// The ranges a check has been passed on and nothing has cast doubt on since.
816    held: Vec<Fact>,
817    /// The ones a call threw away, kept only so that the cost of throwing them away is a number
818    /// somebody can read rather than a paragraph somebody has to believe.
819    lost: Vec<Fact>,
820}
821
822impl Known {
823    /// Whether something still standing answers this.
824    fn covers(&self, asked: &Fact) -> bool {
825        self.held.iter().any(|fact| covers(fact, asked))
826    }
827
828    /// Whether something still standing answers a range of addresses an access can land in.
829    fn reaches(&self, asked: &Reach) -> bool {
830        self.held.iter().any(|fact| reaches(fact, asked))
831    }
832
833    /// Whether one thing still standing answers both of these ranges.
834    ///
835    /// One rather than one each, for the reason [`Known::holds_both`] gives, and the reason does
836    /// not change when the ends are ranges instead of addresses.
837    fn reaches_both(&self, from: &Reach, to: &Reach) -> bool {
838        self.held.iter().any(|fact| reaches(fact, from) && reaches(fact, to))
839    }
840
841    /// Whether something would have answered it before a call came along.
842    fn covered_before(&self, asked: &Fact) -> bool {
843        self.lost.iter().any(|fact| covers(fact, asked))
844    }
845
846    /// Whether one thing still standing answers both of these.
847    ///
848    /// One rather than one each, which is the whole point of asking it this way. Two facts saying
849    /// two addresses are each inside some instance say nothing about whether it is the same
850    /// instance, and that is the only thing a derivation check wants to know.
851    fn holds_both(&self, from: &Fact, to: &Fact) -> bool {
852        self.held.iter().any(|fact| covers(fact, from) && covers(fact, to))
853    }
854
855    /// Whether one would have answered both before a call came along.
856    fn held_both_before(&self, from: &Fact, to: &Fact) -> bool {
857        self.lost.iter().any(|fact| covers(fact, from) && covers(fact, to))
858    }
859
860    /// Gives up everything, because something happened that this pass cannot see through.
861    fn forget(&mut self) {
862        self.lost.append(&mut self.held);
863    }
864}
865
866/// What holds where the walk has got to.
867///
868/// The two kinds are apart because they are killed together and answered separately: a range being
869/// inside one instance and that instance being alive are different claims, and reporting them as
870/// one number would hide which of the two a check is still being paid for.
871#[derive(Debug, Clone, Default)]
872struct Scope {
873    /// Ranges a `check_bounds` established are inside one storage instance.
874    bounds: Known,
875    /// Ranges a `check_live` established are in an instance that is alive.
876    alive: Known,
877}
878
879impl Scope {
880    /// Gives up every fact of either kind.
881    fn forget(&mut self) {
882        self.bounds.forget();
883        self.alive.forget();
884    }
885}
886
887/// Whether this instruction could do something to memory that this pass cannot account for.
888///
889/// A call is the whole of it, in every spelling, and inline assembly with it. A `tail_call` ends
890/// the block and there is nothing after it to protect, and it is here anyway so that the reason a
891/// fact survives is never that the walk did not think of something.
892///
893/// A call carrying [`Flags::NOFREE`] reaches nothing that ends a lifetime, so there is nothing for
894/// it to have done to the bytes an earlier check was passed on. `crate::nofree` is what put the
895/// flag there and what argues for it.
896///
897/// A `meta_end` and a `meta_transfer` end a lifetime by saying so, which is the plainest way for a
898/// fact to stop being true, and neither is emitted today.
899fn opaque(func: &Func, inst: Inst) -> bool {
900    match func[inst].opcode {
901        Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => {
902            !func[inst].flags.contains(Flags::NOFREE)
903        }
904        Opcode::InlineAsm | Opcode::MetaEnd | Opcode::MetaTransfer => true,
905        _ => false,
906    }
907}
908
909/// What a `check_bounds` is about, when it is one this pass can read.
910pub(crate) fn about(func: &Func, check: Inst) -> Option<Fact> {
911    let (base, offset) = addressed(func, check)?;
912    let Extra::Mem(info) = func[check].extra else { return None };
913    Some(Fact { base, offset, size: i128::from(func[info].size) })
914}
915
916/// What a `check_live` is about, when it is one this pass can read.
917///
918/// One byte, because that is the whole of what the check says: the instance holding this address
919/// is alive, and nothing about the address next door. The widening to a range that makes the fact
920/// useful is [`widened`], and it needs a bounds fact to do it.
921pub(crate) fn alive(func: &Func, check: Inst) -> Option<Fact> {
922    let (base, offset) = addressed(func, check)?;
923    Some(Fact { base, offset, size: 1 })
924}
925
926/// The address a check is about, as a base and a constant.
927///
928/// The capability has to be the `cap_of` of the check's own pointer. That is the shape
929/// `rucc-safety` emits and it is what the removal argument in the module comment needs, so a check
930/// that does not have it is not a check this pass has anything to say about.
931fn addressed(func: &Func, check: Inst) -> Option<(Value, i128)> {
932    let args = &func[func[check].args];
933    let &capability = args.first()?;
934    let &pointer = args.get(1)?;
935    if operand_of(func, capability, Opcode::CapOf, 0) != Some(pointer) {
936        return None;
937    }
938    Some(normal(func, pointer))
939}
940
941/// The two ends of a `check_deriv`, each as the single byte at it.
942///
943/// A derivation check asks whether the pointer that came out of a `ptr_add` is still in the storage
944/// instance the pointer that went in belongs to, so both ends have to be readable and both have to
945/// come out of the same value, which is what makes the two offsets comparable at all. One byte each
946/// because that is what is being asked about: not a range, but whether an address is in an instance.
947///
948/// The capability has to be the `cap_of` of the pointer that went in, for the reason [`addressed`]
949/// gives. The instance the check is about is the one that pointer belongs to, and a check naming
950/// some other capability is about some other instance.
951///
952/// The width operand is not read. It matters to the runtime only for a pointer that walked off the
953/// near end, where the check passes on the byte a stride further along instead of on the address
954/// itself, and this pass never gets that far: it discharges nothing it has not put inside a range
955/// outright.
956pub(crate) fn derives(func: &Func, check: Inst) -> Option<(Fact, Fact)> {
957    let args = &func[func[check].args];
958    let &capability = args.first()?;
959    let &from = args.get(1)?;
960    let &to = args.get(2)?;
961    if operand_of(func, capability, Opcode::CapOf, 0) != Some(from) {
962        return None;
963    }
964    let (base, start) = normal(func, from);
965    let (walked, end) = normal(func, to);
966    if base != walked {
967        return None;
968    }
969    Some((Fact { base, offset: start, size: 1 }, Fact { base, offset: end, size: 1 }))
970}
971
972/// The object a local is, when the address a check is about was computed from one.
973///
974/// This is the fact nobody had to check for, and section 7.2 puts it first of the four sources
975/// because it is where most of the win is. An `alloca` of a fixed size is one storage instance of
976/// that many bytes, said by the instruction that makes it rather than by a check that passed, so
977/// the bytes from its address to that many further along are inside one instance for the same
978/// reason a passing `check_bounds` says its own range is.
979///
980/// Only the fixed size form. The one that takes an operand is a variable length array, and how
981/// many bytes it is is a value the program works out rather than a number in the payload, where
982/// the field reads zero.
983///
984/// The fact holds everywhere in the function and no call takes it away, which is the other half of
985/// what makes it worth having. A callee cannot free a frame slot: what it could free is whatever a
986/// pointer stored in the slot points at, and that is a different instance and a different check.
987/// So this is asked separately from the facts the walk carries rather than pushed into them, since
988/// everything in there is thrown away at the first call this pass cannot see through.
989fn declared(func: &Func, base: Value) -> Option<Fact> {
990    let Def::Result { inst, .. } = func[base].def else { return None };
991    if func[inst].opcode != Opcode::Alloca || !func[func[inst].args].is_empty() {
992        return None;
993    }
994    let Extra::Mem(info) = func[inst].extra else { return None };
995    Some(Fact::whole(base, i128::from(func[info].size)))
996}
997
998/// The alignment an allocator promises, in bytes.
999///
1000/// C says storage an allocator hands back is aligned for any object with a fundamental alignment,
1001/// which is sixteen bytes on the targets this compiles for. Eight is claimed rather than sixteen
1002/// because the claim has to hold wherever this pass runs and the pass is given a function rather
1003/// than a target. What it costs is an access that assumes more than eight bytes, which is a
1004/// `long double` or a vector, keeping a check it could have lost.
1005const ALLOCATED: u64 = 8;
1006
1007/// How far into an expression [`divides`] reads before it gives up.
1008///
1009/// A subscript is a multiply and a constant and the answer is two steps in. The bound is here
1010/// because the walk is over an expression the program wrote and nothing about an expression stops
1011/// it from being as deep as the source file is long.
1012const DEEP: u32 = 4;
1013
1014/// Whether the address a check is about starts where the access assumes it does.
1015///
1016/// The alignment conjunct of judgement J1 rides on `check_bounds`, which document 06 section 6.3
1017/// settled, so a check that goes takes the test of it with it and something here has to have
1018/// answered it first. An access that assumes nothing about where it starts has nothing to answer,
1019/// and that is what an alignment of one is and what a member of a packed record gets.
1020///
1021/// What answers it is the object the address was computed from and the steps taken from it, which
1022/// is the same ground the bounds question walks. An `alloca` says what it is aligned to and an
1023/// allocator promises [`ALLOCATED`], and each step from there leaves whatever the step itself
1024/// divides by. So `p[i]` on an `int *` out of `malloc` is answered by the four in the subscript's
1025/// own multiply, and `(int *)(p + 1)` is not answered at all, which is row S7 and the whole reason
1026/// this is here.
1027///
1028/// A global is not read here at all. It arrives as [`Flags::ALIGNED`] from `crate::extents`, which
1029/// is given the module this is not, and the flag is the whole of what this asks about one.
1030///
1031/// A pointer this cannot read the origin of is zero, which answers nothing and keeps the check.
1032/// That is a block parameter, a pointer loaded out of memory, and one handed in.
1033/// [`UNKNOWN_ALIGNMENT`] counts them.
1034///
1035/// The two answers given before [`settles`] is asked are not arithmetic and so are not a rule's.
1036/// An access of one byte assumes nothing about where it starts, so there is nothing to prove about
1037/// it, and the flag is a fact `crate::extents` established over the whole module and wrote down.
1038fn aligned(func: &Func, check: Inst) -> bool {
1039    let Extra::Mem(info) = func[check].extra else { return false };
1040    let claim = u64::from(func[info].align);
1041    if claim <= 1 || func[check].flags.contains(Flags::ALIGNED) {
1042        return true;
1043    }
1044    let Some(&pointer) = func[func[check].args].get(1) else { return false };
1045    settles(settled(func, pointer), claim)
1046}
1047
1048/// Whether an address known to be a multiple of one number meets an access's claim.
1049///
1050/// The companion to [`covers`] for the alignment conjunct, and it decides nothing either. The walk
1051/// in [`settled`] worked out a number the address divides by, and whether that answers the access
1052/// is the rule file's to say. The address is opaque in the question, because nothing here knows
1053/// what it is and the answer is about every address the walk's number holds of.
1054///
1055/// It is worth saying what the rule catches that the comparison it replaced did not. `known` being
1056/// the larger number is not `claim` dividing it, and the two agree only because both are powers of
1057/// two. Every number that gets here is one, for the reason the head in `safety.model` writes out,
1058/// and now that reason is written somewhere a solver reads rather than only somewhere a person
1059/// does.
1060fn settles(known: u64, claim: u64) -> bool {
1061    let mut question = Question::default();
1062    let at = question.opaque();
1063    let at = question.app("value.i64", &[at]);
1064    let known = question.number(i128::from(known));
1065    let known = question.app("iconst.i64", &[known]);
1066    let claim = question.number(i128::from(claim));
1067    let claim = question.app("iconst.i64", &[claim]);
1068    let term = question.app("aligned.i64", &[at, known, claim]);
1069    match safety::TABLE.find(&question, term) {
1070        Some(found) => yes(&safety::TABLE, found.rule),
1071        None => false,
1072    }
1073}
1074
1075/// What a pointer is known to be aligned to, in bytes, or zero when nothing here says.
1076///
1077/// Every number involved is a power of two, so the greatest common divisor of two of them is the
1078/// smaller, which is why the steps are gathered with a `min` and why they start at the largest
1079/// number there is instead of at zero. Zero is the answer and not a step, since an alignment of
1080/// zero is not something an access can assume and a claim is never met by one.
1081fn settled(func: &Func, pointer: Value) -> u64 {
1082    let mut steps = u64::MAX;
1083    let mut value = pointer;
1084    loop {
1085        let Def::Result { inst, .. } = func[value].def else { return 0 };
1086        match func[inst].opcode {
1087            Opcode::Alloca => {
1088                let Extra::Mem(info) = func[inst].extra else { return 0 };
1089                return steps.min(u64::from(func[info].align));
1090            }
1091            Opcode::Call if func[inst].flags.contains(Flags::HEAP) => {
1092                return steps.min(ALLOCATED);
1093            }
1094            Opcode::PtrAdd => {
1095                let args = &func[func[inst].args];
1096                let (Some(&from), Some(&by)) = (args.first(), args.get(1)) else { return 0 };
1097                steps = steps.min(divides(func, by, DEEP));
1098                value = from;
1099            }
1100            _ => return 0,
1101        }
1102    }
1103}
1104
1105/// The largest power of two that divides a step, or one when nothing here says.
1106///
1107/// One is the answer for anything unreadable and it is the right one: every number divides by one,
1108/// so a step nobody can read leaves a pointer aligned to a byte and no more. Zero divides by
1109/// everything, which is a walk that took no step and has to leave what it started with alone.
1110fn divides(func: &Func, step: Value, depth: u32) -> u64 {
1111    if let Some(number) = constant(func, step) {
1112        let Ok(size) = u64::try_from(number.unsigned_abs()) else { return 1 };
1113        return if size == 0 { u64::MAX } else { 1 << size.trailing_zeros() };
1114    }
1115    let Def::Result { inst, .. } = func[step].def else { return 1 };
1116    let args = &func[func[inst].args];
1117    let (Some(&left), Some(&right)) = (args.first(), args.get(1)) else { return 1 };
1118    if depth == 0 {
1119        return 1;
1120    }
1121    match func[inst].opcode {
1122        // A subscript, which is an index nobody knows anything about times the element size.
1123        Opcode::Mul => {
1124            divides(func, left, depth - 1).saturating_mul(divides(func, right, depth - 1))
1125        }
1126        Opcode::Shl => match constant(func, right) {
1127            Some(by) if (0..64).contains(&by) => {
1128                divides(func, left, depth - 1).checked_shl(by as u32).unwrap_or(u64::MAX)
1129            }
1130            _ => 1,
1131        },
1132        // Two numbers added divide by whatever they both divide by, which is a field offset added
1133        // to a subscript and is how a member of an array of records comes out.
1134        Opcode::Add | Opcode::Sub => {
1135            divides(func, left, depth - 1).min(divides(func, right, depth - 1))
1136        }
1137        _ => 1,
1138    }
1139}
1140
1141/// The object an allocator made, when the address a check is about was computed from one and this
1142/// function has already found out it is not null.
1143///
1144/// The same shape as [`declared`] one storey up, with a marked call saying the size instead of an
1145/// `alloca` and one more thing to establish. `crate::heap` has the argument for both halves: what a
1146/// call to `malloc` says is an extent and never a lifetime, and it only says it where the program
1147/// has looked, because a null pointer is inside no object and a check on one is a check that is
1148/// meant to fail.
1149///
1150/// Nothing is claimed when the graph was not built, which is a function this found no allocation in
1151/// and so a function where the answer would have been no anyway.
1152fn allocation(
1153    func: &Func,
1154    cfg: Option<&Cfg>,
1155    checked: &mut HashMap<Value, HashSet<Block>>,
1156    block: Block,
1157    base: Value,
1158) -> Option<Fact> {
1159    let whole = heap::made(func, base)?;
1160    let cfg = cfg?;
1161    checked
1162        .entry(whole.base)
1163        .or_insert_with(|| heap::tested(func, cfg, whole.base))
1164        .contains(&block)
1165        .then_some(whole)
1166}
1167
1168/// Whether all of those bytes are inside one object an allocator made.
1169///
1170/// Every part has to be inside, and inside the same object, which is what asking [`covers`] with one
1171/// fact and several does.
1172fn allocated(
1173    func: &Func,
1174    cfg: Option<&Cfg>,
1175    checked: &mut HashMap<Value, HashSet<Block>>,
1176    block: Block,
1177    parts: &[&Fact],
1178) -> bool {
1179    let Some(first) = parts.first() else { return false };
1180    let Some(whole) = allocation(func, cfg, checked, block, first.base) else { return false };
1181    parts.iter().all(|part| covers(&whole, part))
1182}
1183
1184/// Whether every address a walk can reach is inside one object an allocator made.
1185///
1186/// [`allocated`] for the question [`reach`] and [`spread`] ask. The object comes from the same place
1187/// and is believed for the same reason, and what is asked of it is [`reaches`] rather than
1188/// [`covers`], so a walk by a step the ranges put numbers on can be answered by a call that says how
1189/// many bytes it made.
1190///
1191/// The wide path used to ask a local and the facts the walk carries and nothing else, so a program
1192/// that walked into its own `malloc` by an index kept its checks however plainly the size was
1193/// written. That is the first half of tamnd/rucc#880.
1194fn allocated_around(
1195    func: &Func,
1196    cfg: Option<&Cfg>,
1197    checked: &mut HashMap<Value, HashSet<Block>>,
1198    block: Block,
1199    spans: &[&Reach],
1200) -> bool {
1201    let Some(first) = spans.first() else { return false };
1202    let Some(whole) = allocation(func, cfg, checked, block, first.base) else { return false };
1203    spans.iter().all(|span| reaches(&whole, span))
1204}
1205
1206/// A lifetime fact grown from one address to the checked range it sits in.
1207///
1208/// The argument is in the module comment: a `check_bounds` that passed put its whole range inside
1209/// one instance, so the instance this lifetime check found alive is the instance that range is in.
1210/// With no range around the address the fact stays as it came, which is correct and answers only a
1211/// repeat of the very same check.
1212///
1213/// A local is asked about first, because the object it is is the widest range there can be for an
1214/// address computed from it and a wider fact answers more later checks. What that gives is a
1215/// lifetime check anywhere in a local discharging every later one in the same local, up to the
1216/// first call, which is the shape a function that reads several fields of a local struct has.
1217fn widened(func: &Func, bounds: &Known, asked: Fact) -> Fact {
1218    if let Some(local) = declared(func, asked.base).filter(|local| covers(local, &asked)) {
1219        return local;
1220    }
1221    bounds.held.iter().find(|fact| covers(fact, &asked)).copied().unwrap_or(asked)
1222}
1223
1224/// The value an address was computed from, and how far past it the address is.
1225///
1226/// A `ptr_add` over a constant is walked through, and anything else is where the answer stops. The
1227/// arithmetic here is exact because it is done in `i128` over offsets that came out of the IR as
1228/// sixty four bit constants, and whether it is small enough to mean anything at sixty four bits is
1229/// the rule's question rather than this function's.
1230pub(crate) fn normal(func: &Func, value: Value) -> (Value, i128) {
1231    let mut base = value;
1232    let mut offset: i128 = 0;
1233    while let Some((from, step)) = walked(func, base) {
1234        let Some(sum) = offset.checked_add(step) else { break };
1235        base = from;
1236        offset = sum;
1237    }
1238    (base, offset)
1239}
1240
1241/// Every address a walk can reach, when a step it takes is a value rather than a constant.
1242///
1243/// This is the third of the four sources section 7.2 lists, and it is the one that needs an
1244/// analysis. [`normal`] stops at the first `ptr_add` whose step it cannot read, and what it hands
1245/// back is a fact about a base nobody knows the size of. Document 10's ranges do know something
1246/// about the step: an index the program has already tested, or one a loop counts, is bounded even
1247/// though it is not constant. So the walk carries on past the step, adding the low end of its
1248/// range to the offset and the width of the range to the size.
1249///
1250/// What comes out is a range of addresses the access can land in, and it is a [`Reach`] rather than
1251/// a [`Fact`] on purpose. Whether an object holding all of that range holds the one address the
1252/// access actually uses is [`reaches`], which asks a rule with the distance left opaque, so one
1253/// answer covers every value the step could take.
1254///
1255/// It is only ever asked with. What this returns must never be recorded as established, and the
1256/// one place it could be is the push in the `check_bounds` arm, which happens only where this
1257/// returned nothing or answered nothing. The reason is that the widened range is not what a check
1258/// proves. A check that runs and passes proves the address the program used was inside the object,
1259/// and says nothing at all about the rest of the range this function made up around it.
1260fn reach(func: &Func, ranges: Option<&mut Ranges<'_>>, asked: &Fact, at: Inst) -> Option<Reach> {
1261    let wide = spanned(func, ranges?, asked.base, asked.offset, asked.size, at)?;
1262    // Nothing was walked past, so this is the fact that came in and asking it again is work
1263    // somebody already did.
1264    (wide.base != asked.base).then_some(wide)
1265}
1266
1267/// Which of the reasons a derivation check this pass could not read is kept for.
1268///
1269/// The census and nothing else. Whether the check goes has already been decided by the time this
1270/// runs, and what it answers is the question somebody reading `-fopt-info-missed` is actually
1271/// asking, which is what would have to be built for this pile to move.
1272///
1273/// It walks the same ground [`spread`] walks rather than being folded into it, because the two want
1274/// different things. [`spread`] wants an answer or nothing, and stopping at the first step it cannot
1275/// read is the fastest way to nothing. This wants to get as far as it can and name where it stopped,
1276/// so it runs only on checks that are staying and it is allowed to be the slower of the two.
1277///
1278/// The five that begin `nothing here says how big` are one refusal counted five ways. What is missing
1279/// in every one of them is how many bytes belong to the object, and where the pointer came from is
1280/// what says which piece of work would supply it: `__counted_by` and the type plane for a pointer out
1281/// of memory, section 7.5's summaries for one that was handed over, `crate::extents` reaching further
1282/// for a global, and the allocation summaries for one a call returned.
1283fn unreadable(func: &Func, ranges: Option<&mut Ranges<'_>>, check: Inst) -> &'static str {
1284    let args = &func[func[check].args];
1285    let (Some(&capability), Some(&from), Some(&to)) = (args.first(), args.get(1), args.get(2))
1286    else {
1287        return NO_EXTENT_OTHER;
1288    };
1289    if operand_of(func, capability, Opcode::CapOf, 0) != Some(from) {
1290        return NOT_ITS_CAPABILITY_DERIV;
1291    }
1292    // No ranges is a function with no walk in it that steps by a value, so every step here was a
1293    // constant, so the reader that gives up on two bases gave up on two bases.
1294    let Some(ranges) = ranges else { return TWO_BASES_DERIV };
1295    let (base, offset) = normal(func, from);
1296    let Some(near) = spanned(func, ranges, base, offset, 1, check) else {
1297        return NO_EXTENT_OTHER;
1298    };
1299    let (base, offset) = normal(func, to);
1300    let Some(far) = spanned(func, ranges, base, offset, 1, check) else {
1301        return NO_EXTENT_OTHER;
1302    };
1303    if near.base != far.base {
1304        return TWO_BASES_DERIV;
1305    }
1306    if declared(func, near.base).is_some() {
1307        return OVER_THE_LOCAL_DERIV;
1308    }
1309    match func[near.base].def {
1310        Def::Param { .. } => NO_EXTENT_HANDED,
1311        Def::Result { inst, .. } => match func[inst].opcode {
1312            Opcode::Load => NO_EXTENT_LOADED,
1313            Opcode::GlobalAddr => NO_EXTENT_GLOBAL,
1314            Opcode::Call | Opcode::CallIndirect => NO_EXTENT_RETURNED,
1315            _ => NO_EXTENT_OTHER,
1316        },
1317    }
1318}
1319
1320/// The two ends of a derivation check, each as the range of addresses it can be at.
1321///
1322/// A derivation check asks whether the pointer that came out of a walk is still in the storage
1323/// instance the pointer that went in belongs to. [`derives`] answers that only when both ends
1324/// normalize to one base over constants, and past a step the constant reader gives up on they do
1325/// not, which is why this reads the check's operands again rather than taking what that worked
1326/// out. Each end becomes a range, and the two still have to be off one base or there is nothing
1327/// comparable to ask about.
1328///
1329/// One byte each, for the reason [`derives`] gives. Nothing here claims anything about how many
1330/// bytes are readable at either address.
1331///
1332/// The capability has to be the `cap_of` of the pointer that went in, for the reason [`addressed`]
1333/// gives.
1334fn spread(
1335    func: &Func,
1336    ranges: Option<&mut Ranges<'_>>,
1337    check: Inst,
1338    at: Inst,
1339) -> Option<(Reach, Reach)> {
1340    let ranges = ranges?;
1341    let args = &func[func[check].args];
1342    let &capability = args.first()?;
1343    let &from = args.get(1)?;
1344    let &to = args.get(2)?;
1345    if operand_of(func, capability, Opcode::CapOf, 0) != Some(from) {
1346        return None;
1347    }
1348    let (base, offset) = normal(func, from);
1349    let near = spanned(func, ranges, base, offset, 1, at)?;
1350    let (base, offset) = normal(func, to);
1351    let far = spanned(func, ranges, base, offset, 1, at)?;
1352    (near.base == far.base).then_some((near, far))
1353}
1354
1355/// Every address a walk off `base` can reach, and how many bytes it takes when it gets there.
1356///
1357/// The loop is [`normal`]'s with one more thing to try. A `ptr_add` over a constant is walked
1358/// through the same way, and a `ptr_add` over a value is walked through when document 10's ranges
1359/// put numbers on that value: the low end of the range goes on the distance and the width of it on
1360/// the slack. Anything else is where the walk stops.
1361///
1362/// Nothing is returned when a step is a value the ranges say nothing useful about, rather than the
1363/// walk stopping there and handing back what it had. What it had would be a range off a `ptr_add`
1364/// nobody knows the size of, which answers nothing, so stopping would be a longer way of saying no.
1365fn spanned(
1366    func: &Func,
1367    ranges: &mut Ranges<'_>,
1368    base: Value,
1369    offset: i128,
1370    size: i128,
1371    at: Inst,
1372) -> Option<Reach> {
1373    let mut base = base;
1374    let mut low = offset;
1375    let mut width: i128 = 0;
1376    loop {
1377        // A constant step again, because past a step that needed a range there can be more of
1378        // them, and the frontend leaves a field offset as a constant under an array index.
1379        if let Some((from, step)) = walked(func, base) {
1380            low = low.checked_add(step)?;
1381            base = from;
1382            continue;
1383        }
1384        let Some(from) = operand_of(func, base, Opcode::PtrAdd, 0) else { break };
1385        let by = operand_of(func, base, Opcode::PtrAdd, 1)?;
1386        let (least, most) = ranges.at_inst(by, at).signed_bounds()?;
1387        low = low.checked_add(least)?;
1388        width = width.checked_add(most.checked_sub(least)?)?;
1389        base = from;
1390    }
1391    Some(Reach { base, low, width, size })
1392}
1393
1394/// Whether any walk in this function steps by a value rather than a constant.
1395///
1396/// The question the ranges are built for. A function without one of these would pay for a copy of
1397/// the control flow graph and never ask anything of it.
1398/// Whether anything in this function says a lifetime is over.
1399///
1400/// Nothing emits `meta_end` today, so this is false everywhere and the frame slot rule in
1401/// [`Discharge::run`] is on for every function. It is written anyway, and written over the whole
1402/// function rather than along the walk, because the day something does emit one the cheap reading
1403/// is the wrong one: a lifetime that ended in one arm of a branch has ended for a check after the
1404/// join, and a walk down the dominator tree would not have seen it. Turning the rule off for the
1405/// function is the reading that stays right when that day comes, and the finer one is a job for
1406/// whoever makes `meta_end` appear.
1407fn ends_a_lifetime(func: &Func) -> bool {
1408    func.blocks().any(|block| func.insts(block).any(|inst| func[inst].opcode == Opcode::MetaEnd))
1409}
1410
1411fn walks_by_a_value(func: &Func) -> bool {
1412    func.blocks().any(|block| {
1413        func.insts(block).any(|inst| {
1414            func[inst].opcode == Opcode::PtrAdd
1415                && func[func[inst].args].get(1).is_some_and(|&by| constant(func, by).is_none())
1416        })
1417    })
1418}
1419
1420/// The pointer one `ptr_add` over a constant was computed from, and by how much.
1421fn walked(func: &Func, value: Value) -> Option<(Value, i128)> {
1422    let from = operand_of(func, value, Opcode::PtrAdd, 0)?;
1423    let by = operand_of(func, value, Opcode::PtrAdd, 1)?;
1424    Some((from, constant(func, by)?))
1425}
1426
1427/// Operand `index` of the instruction that produced `value`, when that instruction is `opcode`.
1428pub(crate) fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
1429    let Def::Result { inst, .. } = func[value].def else { return None };
1430    if func[inst].opcode != opcode {
1431        return None;
1432    }
1433    func[func[inst].args].get(index).copied()
1434}
1435
1436/// The value of an integer constant, read with its own sign.
1437pub(crate) fn constant(func: &Func, value: Value) -> Option<i128> {
1438    let Def::Result { inst, .. } = func[value].def else { return None };
1439    if func[inst].opcode != Opcode::IConst {
1440        return None;
1441    }
1442    let Extra::Imm(imm) = func[inst].extra else { return None };
1443    let ty = func[value].ty;
1444    ty.is_int().then(|| func[imm].signed(ty))
1445}
1446
1447/// Whether an established fact answers the check being asked about.
1448///
1449/// This function decides nothing. It puts the two together into the term the rule file is written
1450/// about and asks the table, which is the whole of section 7.7's split: the paragraph above worked
1451/// out that the two addresses are one value a constant apart, and whether that is enough is
1452/// somebody's proof rather than this file's opinion.
1453pub(crate) fn covers(fact: &Fact, asked: &Fact) -> bool {
1454    if fact.base != asked.base {
1455        return false;
1456    }
1457    let Some(delta) = asked.offset.checked_sub(fact.offset) else { return false };
1458    let mut question = Question::default();
1459    let at = question.opaque();
1460    let at = question.app("value.i64", &[at]);
1461    let span = question.number(fact.size);
1462    let span = question.app("iconst.i64", &[span]);
1463    let far = question.number(delta);
1464    let far = question.app("iconst.i64", &[far]);
1465    let reach = question.number(asked.size);
1466    let reach = question.app("iconst.i64", &[reach]);
1467    let term = question.app("covered.i64", &[at, span, far, reach]);
1468    match safety::TABLE.find(&question, term) {
1469        Some(found) => yes(&safety::TABLE, found.rule),
1470        None => false,
1471    }
1472}
1473
1474/// Whether an object holds every address a walk can land on.
1475///
1476/// The companion to [`covers`] for the question [`reach`] asks, and it decides nothing either. It
1477/// puts the object and the range of addresses into the term the rule file is written about and
1478/// asks the table. The distance the program actually walks is opaque in the question, which is
1479/// what makes one answer cover every value it could take.
1480fn reaches(fact: &Fact, asked: &Reach) -> bool {
1481    if fact.base != asked.base {
1482        return false;
1483    }
1484    let Some(delta) = asked.low.checked_sub(fact.offset) else { return false };
1485    let mut question = Question::default();
1486    let at = question.opaque();
1487    let at = question.app("value.i64", &[at]);
1488    let span = question.number(fact.size);
1489    let span = question.app("iconst.i64", &[span]);
1490    let delta = question.number(delta);
1491    let delta = question.app("iconst.i64", &[delta]);
1492    let width = question.number(asked.width);
1493    let width = question.app("iconst.i64", &[width]);
1494    let size = question.number(asked.size);
1495    let size = question.app("iconst.i64", &[size]);
1496    let step = question.opaque();
1497    let step = question.app("value.i64", &[step]);
1498    let term = question.app("reached.i64", &[at, span, delta, width, size, step]);
1499    match safety::TABLE.find(&question, term) {
1500        Some(found) => yes(&safety::TABLE, found.rule),
1501        None => false,
1502    }
1503}
1504
1505/// Whether the rule that fired answers yes.
1506///
1507/// A discharge rule replaces the question with a constant, and one is yes. Every rule in the file
1508/// answers that today, and reading it off the rule rather than assuming it is what keeps this
1509/// honest on the day one of them answers something else.
1510pub(crate) fn yes(table: &Table, rule: usize) -> bool {
1511    matches!(table.rules[rule].replacement, [Piece::App { .. }, Piece::Int(1)])
1512}
1513
1514/// A term built to be asked about, and nothing else.
1515///
1516/// The rules are matched against this rather than against the function, because what is being asked
1517/// about is not in the function: it is what the walk worked out about two of its instructions. So
1518/// the subject is a small arena of exactly the term being asked, built fresh for each question and
1519/// thrown away with the answer.
1520#[derive(Debug, Default)]
1521pub(crate) struct Question {
1522    held: Vec<Held>,
1523}
1524
1525/// One node of that term.
1526#[derive(Debug)]
1527enum Held {
1528    /// A number the pattern can read and a guard can be about.
1529    Int(i128),
1530    /// A head and its arguments.
1531    App(&'static str, Vec<usize>),
1532    /// Something with no structure, which is how an address the rule only names is written.
1533    Opaque,
1534}
1535
1536impl Question {
1537    /// Adds a constant and gives back where it went.
1538    ///
1539    /// Named for what it adds rather than for what it holds, because the arena also answers
1540    /// [`Subject::int`] and one name for the two would read as though building a term and asking
1541    /// about one were the same act.
1542    pub(crate) fn number(&mut self, value: i128) -> usize {
1543        self.held.push(Held::Int(value));
1544        self.held.len() - 1
1545    }
1546
1547    /// Adds an application of `head` to what is already in the arena.
1548    pub(crate) fn app(&mut self, head: &'static str, args: &[usize]) -> usize {
1549        self.held.push(Held::App(head, args.to_vec()));
1550        self.held.len() - 1
1551    }
1552
1553    /// Adds something the rule can bind and cannot look inside.
1554    pub(crate) fn opaque(&mut self) -> usize {
1555        self.held.push(Held::Opaque);
1556        self.held.len() - 1
1557    }
1558}
1559
1560impl Subject for Question {
1561    type Node = usize;
1562
1563    fn head(&self, node: usize) -> Option<(&str, usize)> {
1564        match &self.held[node] {
1565            Held::App(head, args) => Some((head, args.len())),
1566            Held::Int(_) | Held::Opaque => None,
1567        }
1568    }
1569
1570    fn arg(&self, node: usize, index: usize) -> usize {
1571        match &self.held[node] {
1572            Held::App(_, args) => args[index],
1573            // The walk only asks for an argument `head` said was there, so this is unreachable
1574            // rather than a case with an answer.
1575            Held::Int(_) | Held::Opaque => unreachable!("only an application has arguments"),
1576        }
1577    }
1578
1579    fn int(&self, node: usize) -> Option<i128> {
1580        match self.held[node] {
1581            Held::Int(value) => Some(value),
1582            Held::App(..) | Held::Opaque => None,
1583        }
1584    }
1585
1586    fn same(&self, a: usize, b: usize) -> bool {
1587        // Every node of a question is written once, so two places holding one thing are one place.
1588        a == b
1589    }
1590}
1591
1592#[cfg(test)]
1593mod tests {
1594    use rucc_base::Interner;
1595    use rucc_ir::{
1596        AsmInfo, Block, BlockCallList, Builder, Extra, Flags, Func, Inst, InstData, IntPred,
1597        MemInfo, MemOrder, Opcode, Restrict, Signature, Type, Value,
1598    };
1599
1600    use super::{DISCHARGE, Fact};
1601    use crate::stats::Kind;
1602    use crate::{Fuel, Pass, pass};
1603
1604    /// A function taking a pointer, with one block, ready to have accesses put in it.
1605    fn blank() -> (Interner, Func, Block, Value) {
1606        let mut names = Interner::new();
1607        let name = names.intern("f");
1608        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR]));
1609        let block = func.create_block();
1610        let pointer = func.append_param(block, Type::PTR);
1611        (names, func, block, pointer)
1612    }
1613
1614    /// Puts `cap_of` and a `check_bounds` over `size` bytes at `pointer` into a block.
1615    ///
1616    /// The same shape `rucc-safety` emits, written out here rather than reached for, because
1617    /// `rucc-opt` is rank 9 alongside `rucc-safety` and cannot depend on it.
1618    fn check(build: &mut Builder<'_>, pointer: Value, size: u64) {
1619        let args = build.func().push_values(&[pointer]);
1620        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1621        let info = MemInfo {
1622            size,
1623            align: 1,
1624            order: MemOrder::NotAtomic,
1625            tbaa: None,
1626            owns: 0,
1627            restrict: Restrict::NONE,
1628        };
1629        let args = build.func().push_values(&[capability, pointer]);
1630        let extra = Extra::Mem(build.func().add_mem(info));
1631        build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1632    }
1633
1634    /// Puts `cap_of` and a `check_live` at `pointer` into a block.
1635    ///
1636    /// `rucc-safety` emits this straight after the bounds check for the same access and shares the
1637    /// one `cap_of` between the two. Sharing it is not what the pass reads, so the tests build a
1638    /// second one, which is the harder shape for it to accept.
1639    fn live(build: &mut Builder<'_>, pointer: Value) {
1640        let args = build.func().push_values(&[pointer]);
1641        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1642        let args = build.func().push_values(&[capability, pointer]);
1643        build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
1644    }
1645
1646    /// Both checks in front of one access, in the order `rucc-safety` writes them.
1647    fn access(build: &mut Builder<'_>, pointer: Value, size: u64) {
1648        check(build, pointer, size);
1649        live(build, pointer);
1650    }
1651
1652    /// A pointer `bytes` past another one.
1653    fn past(build: &mut Builder<'_>, pointer: Value, bytes: i128) -> Value {
1654        let offset = build.iconst(Type::int(64), bytes);
1655        let args = build.func().push_values(&[pointer, offset]);
1656        build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
1657    }
1658
1659    /// Puts the flag `crate::extents` writes onto every check in a function.
1660    ///
1661    /// The pass reads what the IR says, so what a test has to build is an IR that says it. Working
1662    /// out which checks deserve it is `crate::extents`, is about a module rather than a function,
1663    /// and has its own tests.
1664    fn marked(func: &mut Func) {
1665        flagged(func, Flags::STATIC);
1666    }
1667
1668    /// Puts that flag on every check in the function, the way an annotator before the pipeline
1669    /// would have.
1670    fn flagged(func: &mut Func, flag: Flags) {
1671        let insts: Vec<Inst> =
1672            func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
1673        for inst in insts {
1674            let check = matches!(
1675                func[inst].opcode,
1676                Opcode::CheckBounds | Opcode::CheckLive | Opcode::CheckDeriv
1677            );
1678            if check {
1679                func[inst].flags |= flag;
1680            }
1681        }
1682    }
1683
1684    /// How many checks are left in a function.
1685    fn checks(func: &Func) -> usize {
1686        func.blocks()
1687            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1688            .filter(|&inst| func[inst].opcode == Opcode::CheckBounds)
1689            .count()
1690    }
1691
1692    /// How many lifetime checks are left in a function.
1693    fn lives(func: &Func) -> usize {
1694        func.blocks()
1695            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1696            .filter(|&inst| func[inst].opcode == Opcode::CheckLive)
1697            .count()
1698    }
1699
1700    fn run(func: &mut Func) -> crate::Stats {
1701        DISCHARGE.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1702    }
1703
1704    /// The same, with one of the measurement's variants rather than the pass the pipeline runs.
1705    fn run_with(pass: &super::Discharge, func: &mut Func) -> crate::Stats {
1706        pass.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1707    }
1708
1709    #[test]
1710    fn a_run_that_may_only_ask_an_object_leaves_what_dominance_would_have_taken() {
1711        // Two checks of the same bytes on a pointer that came from outside. Nothing here says how
1712        // big the object is, so the only thing that could answer the second one is the first one
1713        // having run, and a run that may not ask that has to keep both.
1714        let (_, mut func, block, pointer) = blank();
1715        let mut build = Builder::new(&mut func, block);
1716        check(&mut build, pointer, 4);
1717        check(&mut build, pointer, 4);
1718        build.ret(&[]);
1719        let stats = run_with(&super::OBJECTS, &mut func);
1720        assert_eq!(checks(&func), 2);
1721        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 0);
1722    }
1723
1724    #[test]
1725    fn a_run_that_may_only_ask_dominance_takes_the_second_check_of_the_same_bytes() {
1726        let (_, mut func, block, pointer) = blank();
1727        let mut build = Builder::new(&mut func, block);
1728        check(&mut build, pointer, 4);
1729        check(&mut build, pointer, 4);
1730        build.ret(&[]);
1731        let stats = run_with(&super::DOMINANCE, &mut func);
1732        assert_eq!(checks(&func), 1);
1733        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
1734    }
1735
1736    #[test]
1737    fn a_run_that_may_only_ask_dominance_leaves_a_check_inside_a_local() {
1738        // The other way round. One check, nothing in front of it, and the bytes are inside an
1739        // `alloca` whose size is written on it. Only the object can answer that one.
1740        let (_, mut func, block, _) = blank();
1741        let mut build = Builder::new(&mut func, block);
1742        let slot = local(&mut build, 16);
1743        check(&mut build, slot, 4);
1744        build.ret(&[]);
1745        let stats = run_with(&super::DOMINANCE, &mut func);
1746        assert_eq!(checks(&func), 1);
1747        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
1748        assert_eq!(
1749            run_with(&super::OBJECTS, &mut func).count(Kind::Optimized, super::REMOVED_LOCAL),
1750            1
1751        );
1752    }
1753
1754    #[test]
1755    fn the_measurement_variants_answer_to_names_of_their_own() {
1756        // A run that cannot be reached by a flag is a run nobody can measure with.
1757        let names: Vec<&str> = [
1758            &DISCHARGE,
1759            &super::OBJECTS,
1760            &super::DOMINANCE,
1761            &super::SUMMARIES,
1762            &super::NARROW,
1763            &super::EVERY,
1764        ]
1765        .iter()
1766        .map(|pass| pass.name())
1767        .collect();
1768        assert_eq!(
1769            names,
1770            [
1771                "discharge",
1772                "discharge-objects",
1773                "discharge-dominance",
1774                "discharge-summaries",
1775                "discharge-narrow",
1776                "discharge-every"
1777            ]
1778        );
1779        for name in names {
1780            assert!(pass::find(name).is_some(), "`{name}` is not in the pass list");
1781        }
1782    }
1783
1784    #[test]
1785    fn a_second_check_of_the_same_bytes_goes() {
1786        let (_, mut func, block, pointer) = blank();
1787        let mut build = Builder::new(&mut func, block);
1788        check(&mut build, pointer, 4);
1789        check(&mut build, pointer, 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), 1);
1794    }
1795
1796    /// The same check over an access that assumes something about where it starts.
1797    ///
1798    /// [`check`] assumes nothing, which is the right default for the tests above it: what they are
1799    /// about is which bytes a check covers, and an access that assumes nothing has no alignment to
1800    /// answer and so reaches every rule. These are the ones about the alignment itself.
1801    fn assuming(build: &mut Builder<'_>, pointer: Value, size: u64, align: u32) {
1802        let args = build.func().push_values(&[pointer]);
1803        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1804        let info = MemInfo {
1805            size,
1806            align,
1807            order: MemOrder::NotAtomic,
1808            tbaa: None,
1809            owns: 0,
1810            restrict: Restrict::NONE,
1811        };
1812        let args = build.func().push_values(&[capability, pointer]);
1813        let extra = Extra::Mem(build.func().add_mem(info));
1814        build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1815    }
1816
1817    #[test]
1818    fn a_check_whose_alignment_nothing_here_settles_stays() {
1819        // A pointer from outside, so nothing says what it is aligned to, and a second check of the
1820        // same bytes that dominance would otherwise take. The bytes are covered and the alignment
1821        // is not, and the check tests both, so it stays. One remark and not two: the first check
1822        // was staying whatever anybody said about its alignment, and what the number is for is
1823        // what the gate costs.
1824        let (_, mut func, block, pointer) = blank();
1825        let mut build = Builder::new(&mut func, block);
1826        assuming(&mut build, pointer, 4, 4);
1827        assuming(&mut build, pointer, 4, 4);
1828        build.ret(&[]);
1829        let stats = run(&mut func);
1830        assert_eq!(checks(&func), 2);
1831        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 0);
1832        assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 1);
1833    }
1834
1835    #[test]
1836    fn a_check_inside_a_local_at_an_offset_the_local_is_aligned_through_goes() {
1837        // An eight byte aligned slot read four bytes in, which is a member of a record and the
1838        // commonest access there is. The offset leaves four of the eight, the access assumes four,
1839        // and the check goes the way it did before any of this.
1840        let (_, mut func, block, _) = blank();
1841        let mut build = Builder::new(&mut func, block);
1842        let slot = local(&mut build, 16);
1843        let field = past(&mut build, slot, 4);
1844        assuming(&mut build, field, 4, 4);
1845        build.ret(&[]);
1846        let stats = run(&mut func);
1847        assert_eq!(checks(&func), 0);
1848        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
1849        assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
1850    }
1851
1852    #[test]
1853    fn a_check_a_cast_moved_off_the_alignment_stays_however_well_its_bytes_are_covered() {
1854        // Row S7 written in IR. The bytes are inside the slot and the slot is aligned, but the
1855        // access starts one byte in and assumes four, and one byte in is where the alignment is
1856        // lost. This is the check the misaligned read needs and the one the accounting run found
1857        // going missing.
1858        let (_, mut func, block, _) = blank();
1859        let mut build = Builder::new(&mut func, block);
1860        let slot = local(&mut build, 16);
1861        let odd = past(&mut build, slot, 1);
1862        assuming(&mut build, odd, 4, 4);
1863        build.ret(&[]);
1864        let stats = run(&mut func);
1865        assert_eq!(checks(&func), 1);
1866        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
1867        assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 1);
1868    }
1869
1870    #[test]
1871    fn a_subscript_that_steps_by_the_width_it_reads_settles_its_own_alignment() {
1872        // `p[i]` on an `int *` an allocator made. Nobody knows what the index is, and nobody has
1873        // to: the step is the index times four, four divides it whatever the index turns out to
1874        // be, and the allocation it starts from is aligned to more than that.
1875        let (_, mut func, inside, _, pointer, index) = allocation(64);
1876        let mut build = Builder::new(&mut func, inside);
1877        let four = build.iconst(Type::int(64), 4);
1878        let step = build.binary(Opcode::Mul, index, four, Flags::NONE);
1879        let args = build.func().push_values(&[pointer, step]);
1880        let at = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1881        assuming(&mut build, at, 4, 4);
1882        build.ret(&[]);
1883        let stats = run(&mut func);
1884        assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
1885    }
1886
1887    #[test]
1888    fn what_answers_an_alignment_claim_is_the_rule_and_not_a_comparison() {
1889        // The four cases the rule is asked about, and the fifth is the reason it is a rule. A
1890        // number larger than the claim and not a multiple of it answers nothing, and the guard is
1891        // written so that the question never gets asked with one, because `super::settled` only
1892        // ever gives back a power of two. The last one is the same point from the other end: the
1893        // largest number there is is larger than every claim and divides nothing, and what the
1894        // walk means by it is that it took no step rather than that it found an alignment.
1895        assert!(super::settles(8, 8));
1896        assert!(super::settles(16, 8));
1897        assert!(!super::settles(4, 8));
1898        assert!(!super::settles(0, 8));
1899        assert!(!super::settles(u64::MAX, 8));
1900    }
1901
1902    #[test]
1903    fn a_step_by_something_nobody_can_read_settles_nothing() {
1904        // A step the ranges do bound, so the bytes are answered and the check was on its way out,
1905        // and a step nothing says the low bits of, so where the access starts is not answered. A
1906        // mask of seven is nought to seven and three is one of those.
1907        let (_, mut func, block, _, index) = indexed();
1908        let mut build = Builder::new(&mut func, block);
1909        let slot = local(&mut build, 16);
1910        let step = low_bits(&mut build, index, 7);
1911        let args = build.func().push_values(&[slot, step]);
1912        let at = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1913        assuming(&mut build, at, 4, 4);
1914        build.ret(&[]);
1915        let stats = run(&mut func);
1916        assert_eq!(checks(&func), 1);
1917        assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 1);
1918    }
1919
1920    #[test]
1921    fn a_check_over_a_length_the_program_worked_out_is_not_this_pass_to_read() {
1922        // Section 7.4's hoisted check covers as many bytes as its loop runs times, which is a value
1923        // and not a number. Every range this pass compares is a pair of numbers, so it says so and
1924        // leaves the check alone rather than reading the payload, whose size is one element.
1925        let (_, mut func, block, pointer) = blank();
1926        let mut build = Builder::new(&mut func, block);
1927        check(&mut build, pointer, 4);
1928        let args = build.func().push_values(&[pointer]);
1929        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1930        let bytes = build.iconst(Type::int(64), 4);
1931        let info = MemInfo {
1932            size: 4,
1933            align: 1,
1934            order: MemOrder::NotAtomic,
1935            tbaa: None,
1936            owns: 0,
1937            restrict: Restrict::NONE,
1938        };
1939        let extra = Extra::Mem(build.func().add_mem(info));
1940        let args = build.func().push_values(&[capability, pointer, bytes]);
1941        build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1942        build.ret(&[]);
1943
1944        let stats = run(&mut func);
1945        assert_eq!(checks(&func), 2, "the second one stays");
1946        assert_eq!(stats.count(Kind::Missed, super::COMPUTED_EXTENT), 1);
1947    }
1948
1949    #[test]
1950    fn a_check_of_bytes_inside_a_checked_range_goes() {
1951        // Four bytes at offset four, inside sixteen bytes at offset zero. This is the shape the
1952        // whole pass is for: a struct whose fields are read one after another through one pointer.
1953        let (_, mut func, block, pointer) = blank();
1954        let mut build = Builder::new(&mut func, block);
1955        check(&mut build, pointer, 16);
1956        let field = past(&mut build, pointer, 4);
1957        check(&mut build, field, 4);
1958        build.ret(&[]);
1959        run(&mut func);
1960        assert_eq!(checks(&func), 1);
1961    }
1962
1963    #[test]
1964    fn a_check_of_bytes_past_the_end_of_a_checked_range_stays() {
1965        // Four bytes at offset fourteen is two bytes past the end of the sixteen that were
1966        // checked, and those two bytes are what the check is for.
1967        let (_, mut func, block, pointer) = blank();
1968        let mut build = Builder::new(&mut func, block);
1969        check(&mut build, pointer, 16);
1970        let over = past(&mut build, pointer, 14);
1971        check(&mut build, over, 4);
1972        build.ret(&[]);
1973        assert!(!run(&mut func).changed());
1974        assert_eq!(checks(&func), 2);
1975    }
1976
1977    #[test]
1978    fn a_check_of_bytes_before_a_checked_range_stays() {
1979        // The guard's `delta` is not negative, and this is why. A read four bytes below what was
1980        // checked is a read of somebody else's memory, and it is the bug the check exists for.
1981        let (_, mut func, block, pointer) = blank();
1982        let mut build = Builder::new(&mut func, block);
1983        check(&mut build, pointer, 16);
1984        let under = past(&mut build, pointer, -4);
1985        check(&mut build, under, 4);
1986        build.ret(&[]);
1987        assert!(!run(&mut func).changed());
1988        assert_eq!(checks(&func), 2);
1989    }
1990
1991    #[test]
1992    fn a_check_through_a_pointer_nothing_relates_to_the_first_stays() {
1993        let mut names = Interner::new();
1994        let name = names.intern("two");
1995        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::PTR]));
1996        let block = func.create_block();
1997        let one = func.append_param(block, Type::PTR);
1998        let other = func.append_param(block, Type::PTR);
1999        let mut build = Builder::new(&mut func, block);
2000        check(&mut build, one, 16);
2001        check(&mut build, other, 4);
2002        build.ret(&[]);
2003        assert!(!run(&mut func).changed());
2004        assert_eq!(checks(&func), 2);
2005    }
2006
2007    #[test]
2008    fn a_check_a_call_stands_between_stays_and_is_counted() {
2009        // The conservatism the module comment argues for, and the number that says what it costs.
2010        let (mut names, mut func, block, pointer) = blank();
2011        let mut build = Builder::new(&mut func, block);
2012        check(&mut build, pointer, 16);
2013        let callee = names.intern("might_free");
2014        let signature = build.func().add_signature(Signature::new());
2015        build.call(callee, signature, &[]);
2016        check(&mut build, pointer, 4);
2017        build.ret(&[]);
2018        let stats = run(&mut func);
2019        assert!(!stats.changed());
2020        assert_eq!(checks(&func), 2);
2021        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
2022    }
2023
2024    #[test]
2025    fn a_check_a_call_that_cannot_free_stands_between_goes() {
2026        // The other side of the paragraph above. The summary said this call reaches nothing that
2027        // ends a lifetime, so the range the first check established is still one range.
2028        let (mut names, mut func, block, pointer) = blank();
2029        let mut build = Builder::new(&mut func, block);
2030        check(&mut build, pointer, 16);
2031        let callee = names.intern("counts_them");
2032        let signature = build.func().add_signature(Signature::new());
2033        let call = build.call(callee, signature, &[]);
2034        check(&mut build, pointer, 4);
2035        build.ret(&[]);
2036        func[call].flags |= Flags::NOFREE;
2037        let stats = run(&mut func);
2038        assert_eq!(checks(&func), 1);
2039        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2040        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
2041    }
2042
2043    #[test]
2044    fn inline_assembly_throws_the_facts_away_whatever_it_is_flagged() {
2045        // There is no flag that would make this safe. The template is text the compiler does not
2046        // read, so nothing worked anything out about what it reaches.
2047        let (mut names, mut func, block, pointer) = blank();
2048        let mut build = Builder::new(&mut func, block);
2049        check(&mut build, pointer, 16);
2050        build.inline_asm(
2051            AsmInfo {
2052                template: names.intern("nop"),
2053                constraints: names.intern(""),
2054                clobbers: names.intern(""),
2055                targets: BlockCallList::EMPTY,
2056            },
2057            &[],
2058            &[],
2059            Flags::NONE,
2060        );
2061        check(&mut build, pointer, 4);
2062        build.ret(&[]);
2063        let stats = run(&mut func);
2064        assert!(!stats.changed());
2065        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
2066    }
2067
2068    #[test]
2069    fn a_check_that_only_one_path_covers_stays() {
2070        // The dominator tree is what makes this right. The check in the arm covers the one in the
2071        // join on one path and not on the other, and a check that goes has to be one that ran.
2072        let (_, mut func, block, pointer) = blank();
2073        let arm = func.create_block();
2074        let join = func.create_block();
2075        let mut build = Builder::new(&mut func, block);
2076        let condition = build.iconst(Type::int(32), 1);
2077        build.br_if(condition, arm, &[], join, &[]);
2078        let mut build = Builder::new(&mut func, arm);
2079        check(&mut build, pointer, 16);
2080        build.jump(join, &[]);
2081        let mut build = Builder::new(&mut func, join);
2082        check(&mut build, pointer, 4);
2083        build.ret(&[]);
2084        assert!(!run(&mut func).changed());
2085        assert_eq!(checks(&func), 2);
2086    }
2087
2088    #[test]
2089    fn a_check_a_dominating_block_covers_goes() {
2090        let (_, mut func, block, pointer) = blank();
2091        let after = func.create_block();
2092        let mut build = Builder::new(&mut func, block);
2093        check(&mut build, pointer, 16);
2094        build.jump(after, &[]);
2095        let mut build = Builder::new(&mut func, after);
2096        let field = past(&mut build, pointer, 8);
2097        check(&mut build, field, 8);
2098        build.ret(&[]);
2099        run(&mut func);
2100        assert_eq!(checks(&func), 1);
2101    }
2102
2103    #[test]
2104    fn fuel_stops_the_removing_and_not_the_looking() {
2105        let (_, mut func, block, pointer) = blank();
2106        let mut build = Builder::new(&mut func, block);
2107        check(&mut build, pointer, 4);
2108        check(&mut build, pointer, 4);
2109        check(&mut build, pointer, 4);
2110        build.ret(&[]);
2111        let mut fuel = Fuel::of(1);
2112        let stats = DISCHARGE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
2113        assert_eq!(checks(&func), 2);
2114        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2115        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
2116    }
2117
2118    #[test]
2119    fn a_second_lifetime_check_of_the_same_address_goes() {
2120        // The narrow fact on its own, with no range around it to widen into.
2121        let (_, mut func, block, pointer) = blank();
2122        let mut build = Builder::new(&mut func, block);
2123        live(&mut build, pointer);
2124        live(&mut build, pointer);
2125        build.ret(&[]);
2126        let stats = run(&mut func);
2127        assert_eq!(lives(&func), 1);
2128        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
2129    }
2130
2131    #[test]
2132    fn a_lifetime_check_inside_a_checked_range_goes() {
2133        // The shape the pass is for, with both halves of it. Sixteen bytes are checked and found
2134        // alive, then a field four bytes in is read, and neither check in front of it survives.
2135        let (_, mut func, block, pointer) = blank();
2136        let mut build = Builder::new(&mut func, block);
2137        access(&mut build, pointer, 16);
2138        let field = past(&mut build, pointer, 4);
2139        access(&mut build, field, 4);
2140        build.ret(&[]);
2141        let stats = run(&mut func);
2142        assert_eq!(checks(&func), 1);
2143        assert_eq!(lives(&func), 1);
2144        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2145        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
2146    }
2147
2148    #[test]
2149    fn a_lifetime_check_outside_every_checked_range_stays() {
2150        // Four bytes at offset twenty are past the sixteen that were checked, so nothing says the
2151        // address is in the instance that was found alive, and it might be in no instance at all.
2152        let (_, mut func, block, pointer) = blank();
2153        let mut build = Builder::new(&mut func, block);
2154        access(&mut build, pointer, 16);
2155        let over = past(&mut build, pointer, 20);
2156        live(&mut build, over);
2157        build.ret(&[]);
2158        assert!(!run(&mut func).changed());
2159        assert_eq!(lives(&func), 2);
2160    }
2161
2162    #[test]
2163    fn a_lifetime_check_with_no_range_around_it_does_not_widen() {
2164        // Without the bounds check the first lifetime check speaks only for its own address, so
2165        // the one four bytes along is a different question and stays.
2166        let (_, mut func, block, pointer) = blank();
2167        let mut build = Builder::new(&mut func, block);
2168        live(&mut build, pointer);
2169        let field = past(&mut build, pointer, 4);
2170        live(&mut build, field);
2171        build.ret(&[]);
2172        assert!(!run(&mut func).changed());
2173        assert_eq!(lives(&func), 2);
2174    }
2175
2176    #[test]
2177    fn a_lifetime_check_a_call_stands_between_stays_and_is_counted() {
2178        // Section 8.8's number. This is the one the summaries were written for.
2179        let (mut names, mut func, block, pointer) = blank();
2180        let mut build = Builder::new(&mut func, block);
2181        access(&mut build, pointer, 16);
2182        let callee = names.intern("might_free");
2183        let signature = build.func().add_signature(Signature::new());
2184        build.call(callee, signature, &[]);
2185        let field = past(&mut build, pointer, 4);
2186        live(&mut build, field);
2187        build.ret(&[]);
2188        let stats = run(&mut func);
2189        assert!(!stats.changed());
2190        assert_eq!(lives(&func), 2);
2191        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
2192    }
2193
2194    #[test]
2195    fn a_lifetime_check_a_call_that_cannot_free_stands_between_goes() {
2196        let (mut names, mut func, block, pointer) = blank();
2197        let mut build = Builder::new(&mut func, block);
2198        access(&mut build, pointer, 16);
2199        let callee = names.intern("counts_them");
2200        let signature = build.func().add_signature(Signature::new());
2201        let call = build.call(callee, signature, &[]);
2202        let field = past(&mut build, pointer, 4);
2203        live(&mut build, field);
2204        build.ret(&[]);
2205        func[call].flags |= Flags::NOFREE;
2206        let stats = run(&mut func);
2207        assert_eq!(lives(&func), 1);
2208        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
2209    }
2210
2211    #[test]
2212    fn ending_a_lifetime_throws_the_facts_away() {
2213        // Nothing emits `meta_end` yet, so this is the test that says what will happen when
2214        // something does, rather than a test of anything the compiler does today.
2215        let (_, mut func, block, pointer) = blank();
2216        let mut build = Builder::new(&mut func, block);
2217        access(&mut build, pointer, 16);
2218        let size = build.iconst(Type::int(64), 16);
2219        let args = build.func().push_values(&[pointer, size]);
2220        build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
2221        access(&mut build, pointer, 16);
2222        build.ret(&[]);
2223        let stats = run(&mut func);
2224        assert!(!stats.changed());
2225        assert_eq!(checks(&func), 2);
2226        assert_eq!(lives(&func), 2);
2227        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
2228        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
2229    }
2230
2231    #[test]
2232    fn fuel_runs_out_over_both_kinds_of_check() {
2233        let (_, mut func, block, pointer) = blank();
2234        let mut build = Builder::new(&mut func, block);
2235        access(&mut build, pointer, 16);
2236        access(&mut build, pointer, 4);
2237        build.ret(&[]);
2238        let mut fuel = Fuel::of(1);
2239        let stats = DISCHARGE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
2240        assert_eq!(checks(&func), 1);
2241        assert_eq!(lives(&func), 2);
2242        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2243        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
2244    }
2245
2246    #[test]
2247    fn a_distance_too_large_to_be_a_real_access_is_not_discharged() {
2248        // The guard's bound. The two readings of the arithmetic agree while the numbers stay
2249        // small, so a rule proved at sixty four bits is not asked about anything else. Nothing
2250        // here is wrong, it simply is not proved, and a check that is not proved to be unnecessary
2251        // stays.
2252        let huge = i128::from(u64::MAX) * 4;
2253        let fact = Fact { base: Value::new(0), offset: 0, size: huge };
2254        let asked = Fact { base: Value::new(0), offset: huge / 2, size: 4 };
2255        assert!(!super::covers(&fact, &asked));
2256    }
2257
2258    #[test]
2259    fn a_range_of_addresses_wider_than_the_rule_allows_is_not_discharged() {
2260        // The guard on `reached.i64` bounds each of the three numbers at four gigabytes, for the
2261        // reason the rule file gives: past there the compiler's `i128` reading of the guard and the
2262        // solver's sixty four bit reading part company, and a rule proved under one and run under
2263        // the other is a rule proved about arithmetic that is not happening. A step whose range is
2264        // that wide is the usual case rather than a corner, since an index nothing has bounded says
2265        // nothing about where the access lands.
2266        let base = Value::new(0);
2267        let whole = Fact::whole(base, i128::from(u64::MAX) * 4);
2268        let asked = super::Reach { base, low: 0, width: i128::from(u64::MAX), size: 4 };
2269        assert!(!super::reaches(&whole, &asked));
2270    }
2271
2272    #[test]
2273    fn a_range_of_addresses_that_ends_where_the_object_does_is_discharged() {
2274        // Sixteen bytes, a step somewhere in nought to eleven, four bytes read. The last address
2275        // the walk can reach is the last one in the object, which is inside it.
2276        let base = Value::new(0);
2277        let whole = Fact::whole(base, 16);
2278        let asked = super::Reach { base, low: 0, width: 12, size: 4 };
2279        assert!(super::reaches(&whole, &asked));
2280        let over = super::Reach { base, low: 0, width: 13, size: 4 };
2281        assert!(!super::reaches(&whole, &over), "one byte further runs off the end");
2282    }
2283
2284    #[test]
2285    fn a_walk_by_a_bounded_step_off_a_local_takes_its_derivation_check_with_it() {
2286        // The shape `derives` cannot read at all: the pointer that went in is the slot and the one
2287        // that came out is a value past it, so the two are not one base and two constants. Both
2288        // ends widen to the slot, the slot holds both ranges, and one thing holding both is what a
2289        // derivation check asks about.
2290        let (_, mut func, block, _, index) = indexed();
2291        let mut build = Builder::new(&mut func, block);
2292        let slot = local(&mut build, 16);
2293        let step = low_bits(&mut build, index, 7);
2294        let at = walk(&mut build, slot, step);
2295        deriv(&mut build, slot, at, 4);
2296        build.ret(&[]);
2297        let stats = run(&mut func);
2298        assert_eq!(derivs(&func), 0);
2299        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 1);
2300    }
2301
2302    #[test]
2303    fn a_walk_that_can_leave_the_local_keeps_its_derivation_check() {
2304        // Nought to fifteen off a slot of eight. Every step is bounded and the answer is still no,
2305        // because the question is whether the slot holds every address the walk can reach.
2306        let (_, mut func, block, _, index) = indexed();
2307        let mut build = Builder::new(&mut func, block);
2308        let slot = local(&mut build, 8);
2309        let step = low_bits(&mut build, index, 15);
2310        let at = walk(&mut build, slot, step);
2311        deriv(&mut build, slot, at, 4);
2312        build.ret(&[]);
2313        let stats = run(&mut func);
2314        assert_eq!(derivs(&func), 1);
2315        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 0);
2316        assert_eq!(stats.count(Kind::Missed, super::OVER_THE_LOCAL_DERIV), 1);
2317    }
2318
2319    #[test]
2320    fn a_lifetime_check_a_bounded_walk_lands_inside_a_checked_range_goes() {
2321        // An access over thirty two bytes establishes the range, and the lifetime check beside it
2322        // makes that range one a check found alive. The lifetime check on the walk then goes,
2323        // because every address the walk can reach is in the range that was found alive.
2324        //
2325        // Written off a parameter rather than a slot because a slot answers the narrow question on
2326        // its own. What has to answer this one is a range a check was passed on.
2327        let (_, mut func, block, pointer, index) = indexed();
2328        let mut build = Builder::new(&mut func, block);
2329        access(&mut build, pointer, 32);
2330        let step = low_bits(&mut build, index, 7);
2331        let at = walk(&mut build, pointer, step);
2332        live(&mut build, at);
2333        build.ret(&[]);
2334        let stats = run(&mut func);
2335        assert_eq!(lives(&func), 1, "the one in front of the access stays");
2336        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 1);
2337    }
2338
2339    #[test]
2340    fn a_lifetime_check_a_bounded_walk_can_leave_the_checked_range_keeps_it() {
2341        // The same over eight bytes, under a walk that can go fifteen past the start. A range of
2342        // eight bytes does not hold an address fifteen along from where it begins.
2343        let (_, mut func, block, pointer, index) = indexed();
2344        let mut build = Builder::new(&mut func, block);
2345        access(&mut build, pointer, 8);
2346        let step = low_bits(&mut build, index, 15);
2347        let at = walk(&mut build, pointer, step);
2348        live(&mut build, at);
2349        build.ret(&[]);
2350        let stats = run(&mut func);
2351        assert_eq!(lives(&func), 2);
2352        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 0);
2353    }
2354
2355    /// A stack slot of `size` bytes, in the entry block where the verifier wants one.
2356    fn local(build: &mut Builder<'_>, size: u64) -> Value {
2357        let info = MemInfo {
2358            size,
2359            align: 8,
2360            order: MemOrder::NotAtomic,
2361            tbaa: None,
2362            owns: 0,
2363            restrict: Restrict::NONE,
2364        };
2365        let extra = Extra::Mem(build.func().add_mem(info));
2366        build.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
2367    }
2368
2369    /// A function taking a pointer and an index, with one block.
2370    fn indexed() -> (Interner, Func, Block, Value, Value) {
2371        let mut names = Interner::new();
2372        let name = names.intern("f");
2373        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::int(64)]));
2374        let block = func.create_block();
2375        let pointer = func.append_param(block, Type::PTR);
2376        let index = func.append_param(block, Type::int(64));
2377        (names, func, block, pointer, index)
2378    }
2379
2380    /// A pointer a value past another one.
2381    fn walk(build: &mut Builder<'_>, pointer: Value, by: Value) -> Value {
2382        let args = build.func().push_values(&[pointer, by]);
2383        build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
2384    }
2385
2386    /// The low bits of a value, which is a step the ranges can put a number on.
2387    fn low_bits(build: &mut Builder<'_>, value: Value, mask: i128) -> Value {
2388        let bits = build.iconst(Type::int(64), mask);
2389        build.binary(Opcode::And, value, bits, Flags::NONE)
2390    }
2391
2392    #[test]
2393    fn a_walk_by_a_step_the_ranges_bound_inside_a_local_goes() {
2394        // Section 7.2's third source. The step is not a constant, so the walk stops at the
2395        // `ptr_add` and the fact that comes out is about a base nobody knows the size of. What
2396        // the ranges say is that the step is somewhere in nought to seven, so the four bytes the
2397        // access wants are somewhere in nought to eleven, and all of that is inside the sixteen
2398        // the slot is.
2399        let (_, mut func, block, _, index) = indexed();
2400        let mut build = Builder::new(&mut func, block);
2401        let slot = local(&mut build, 16);
2402        let step = low_bits(&mut build, index, 7);
2403        let at = walk(&mut build, slot, step);
2404        check(&mut build, at, 4);
2405        build.ret(&[]);
2406        let stats = run(&mut func);
2407        assert_eq!(checks(&func), 0);
2408        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
2409    }
2410
2411    #[test]
2412    fn a_walk_by_a_step_the_ranges_cannot_bound_is_left_alone() {
2413        // The same function with the mask taken off. A parameter can be anything, so the range of
2414        // addresses the walk reaches is the whole of memory and no slot covers it.
2415        let (_, mut func, block, _, index) = indexed();
2416        let mut build = Builder::new(&mut func, block);
2417        let slot = local(&mut build, 16);
2418        let at = walk(&mut build, slot, index);
2419        check(&mut build, at, 4);
2420        build.ret(&[]);
2421        let stats = run(&mut func);
2422        assert_eq!(checks(&func), 1);
2423        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
2424    }
2425
2426    #[test]
2427    fn a_walk_a_bounded_step_can_take_off_the_end_of_a_local_is_left_alone() {
2428        // Nought to seven again, four bytes again, and a slot of eight this time. The step being
2429        // bounded is not the question. The question is whether every address it can reach is
2430        // inside the slot, and seven plus four is not.
2431        let (_, mut func, block, _, index) = indexed();
2432        let mut build = Builder::new(&mut func, block);
2433        let slot = local(&mut build, 8);
2434        let step = low_bits(&mut build, index, 7);
2435        let at = walk(&mut build, slot, step);
2436        check(&mut build, at, 4);
2437        build.ret(&[]);
2438        let stats = run(&mut func);
2439        assert_eq!(checks(&func), 1);
2440        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
2441    }
2442
2443    #[test]
2444    fn a_constant_step_past_a_bounded_one_is_walked_too() {
2445        // A field of an element of an array of structs, which is the shape this is for. The array
2446        // index needs a range and the field offset does not, and the walk has to get through both.
2447        let (_, mut func, block, _, index) = indexed();
2448        let mut build = Builder::new(&mut func, block);
2449        let slot = local(&mut build, 32);
2450        let step = low_bits(&mut build, index, 15);
2451        let element = walk(&mut build, slot, step);
2452        let field = past(&mut build, element, 8);
2453        check(&mut build, field, 4);
2454        build.ret(&[]);
2455        let stats = run(&mut func);
2456        assert_eq!(checks(&func), 0);
2457        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
2458    }
2459
2460    #[test]
2461    fn what_a_range_discharge_records_is_the_bytes_and_not_the_range() {
2462        // The second check is the same bytes as the first, and the first went because a made up
2463        // range around it was inside the slot. What the first one proved is that those bytes are
2464        // in the slot, so the second one goes on that rather than on the ranges being asked all
2465        // over again.
2466        let (_, mut func, block, _, index) = indexed();
2467        let mut build = Builder::new(&mut func, block);
2468        let slot = local(&mut build, 16);
2469        let step = low_bits(&mut build, index, 7);
2470        let at = walk(&mut build, slot, step);
2471        check(&mut build, at, 4);
2472        check(&mut build, at, 4);
2473        build.ret(&[]);
2474        let stats = run(&mut func);
2475        assert_eq!(checks(&func), 0);
2476        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
2477        assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2478    }
2479
2480    /// A stack slot whose size the program works out, which is what a variable length array is.
2481    fn growable(build: &mut Builder<'_>, size: Value) -> Value {
2482        let info = MemInfo {
2483            size: 0,
2484            align: 8,
2485            order: MemOrder::NotAtomic,
2486            tbaa: None,
2487            owns: 0,
2488            restrict: Restrict::NONE,
2489        };
2490        let extra = Extra::Mem(build.func().add_mem(info));
2491        let args = build.func().push_values(&[size]);
2492        build.value(InstData { args, extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
2493    }
2494
2495    #[test]
2496    fn a_check_of_bytes_inside_a_local_goes_with_nothing_in_front_of_it() {
2497        // Section 7.2's first source. No check established this and none had to: an `alloca` of
2498        // sixteen bytes is sixteen bytes of one storage instance because that is what it makes.
2499        let (_, mut func, block, _) = blank();
2500        let mut build = Builder::new(&mut func, block);
2501        let slot = local(&mut build, 16);
2502        let field = past(&mut build, slot, 8);
2503        check(&mut build, field, 4);
2504        build.ret(&[]);
2505        let stats = run(&mut func);
2506        assert_eq!(checks(&func), 0);
2507        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
2508    }
2509
2510    #[test]
2511    fn a_check_past_the_end_of_a_local_stays() {
2512        // The slot is sixteen bytes and the access runs to twenty. Nothing about it being a local
2513        // says anything about the four bytes after it, which belong to whatever the frame puts
2514        // there next.
2515        let (_, mut func, block, _) = blank();
2516        let mut build = Builder::new(&mut func, block);
2517        let slot = local(&mut build, 16);
2518        let field = past(&mut build, slot, 16);
2519        check(&mut build, field, 4);
2520        build.ret(&[]);
2521        let stats = run(&mut func);
2522        assert_eq!(checks(&func), 1);
2523        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
2524    }
2525
2526    #[test]
2527    fn a_check_of_bytes_inside_a_local_goes_across_a_call() {
2528        // The other half of what makes the fact worth having. A callee cannot free a frame slot,
2529        // so unlike everything the walk carries this one is not thrown away at a call.
2530        let (mut names, mut func, block, _) = blank();
2531        let mut build = Builder::new(&mut func, block);
2532        let slot = local(&mut build, 16);
2533        let callee = names.intern("might_free");
2534        let signature = build.func().add_signature(Signature::new());
2535        build.call(callee, signature, &[]);
2536        check(&mut build, slot, 4);
2537        build.ret(&[]);
2538        let stats = run(&mut func);
2539        assert_eq!(checks(&func), 0);
2540        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
2541        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
2542    }
2543
2544    #[test]
2545    fn a_check_inside_a_variable_length_array_stays() {
2546        // How many bytes it is is a value the program works out, and the payload's size field
2547        // reads zero. A pass that read it anyway would discharge every check in the array.
2548        let (_, mut func, block, _) = blank();
2549        let mut build = Builder::new(&mut func, block);
2550        let bytes = build.iconst(Type::int(64), 64);
2551        let slot = growable(&mut build, bytes);
2552        check(&mut build, slot, 4);
2553        build.ret(&[]);
2554        let stats = run(&mut func);
2555        assert_eq!(checks(&func), 1);
2556        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
2557    }
2558
2559    #[test]
2560    fn a_lifetime_check_in_a_local_goes_with_nothing_in_front_of_it() {
2561        // The frame slot rule, and the point is that neither of these has a check in front of it.
2562        // A slot is alive until the function returns, so a lifetime check anywhere inside one is
2563        // asking a question the `alloca` already answered.
2564        let (_, mut func, block, _) = blank();
2565        let mut build = Builder::new(&mut func, block);
2566        let slot = local(&mut build, 16);
2567        live(&mut build, slot);
2568        let field = past(&mut build, slot, 12);
2569        live(&mut build, field);
2570        build.ret(&[]);
2571        let stats = run(&mut func);
2572        assert_eq!(lives(&func), 0);
2573        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 2);
2574    }
2575
2576    #[test]
2577    fn a_lifetime_check_past_the_end_of_a_local_stays() {
2578        // The slot answers for its own bytes and no further, so an address outside it is a
2579        // different instance and a question nothing has answered.
2580        let (_, mut func, block, _) = blank();
2581        let mut build = Builder::new(&mut func, block);
2582        let slot = local(&mut build, 16);
2583        live(&mut build, slot);
2584        let field = past(&mut build, slot, 24);
2585        live(&mut build, field);
2586        build.ret(&[]);
2587        let stats = run(&mut func);
2588        assert_eq!(lives(&func), 1);
2589        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 1);
2590    }
2591
2592    #[test]
2593    fn something_ending_a_lifetime_turns_the_frame_slot_rule_off() {
2594        // The gate, and with it the widening the frame slot rule usually hides. With a `meta_end`
2595        // anywhere in the function the slot answers nothing, so the first check stays and pays,
2596        // and what takes the second one out is the first one widened to the whole slot.
2597        let (_, mut func, block, pointer) = blank();
2598        let mut build = Builder::new(&mut func, block);
2599        let slot = local(&mut build, 16);
2600        live(&mut build, slot);
2601        let field = past(&mut build, slot, 12);
2602        live(&mut build, field);
2603        let size = build.iconst(Type::int(64), 16);
2604        let args = build.func().push_values(&[pointer, size]);
2605        build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
2606        build.ret(&[]);
2607        let stats = run(&mut func);
2608        assert_eq!(lives(&func), 1);
2609        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 0);
2610        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
2611    }
2612
2613    /// Puts `cap_of` and a `check_deriv` for a walk from `from` to `to` into a block.
2614    ///
2615    /// The stride is the width of one element, which is what `rucc-safety` passes and what the
2616    /// runtime uses for a pointer that walked off the near end. This pass does not read it.
2617    fn deriv(build: &mut Builder<'_>, from: Value, to: Value, stride: i128) {
2618        let args = build.func().push_values(&[from]);
2619        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2620        let width = build.iconst(Type::int(64), stride);
2621        let args = build.func().push_values(&[capability, from, to, width]);
2622        build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
2623    }
2624
2625    /// How many derivation checks are left in a function.
2626    fn derivs(func: &Func) -> usize {
2627        func.blocks()
2628            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
2629            .filter(|&inst| func[inst].opcode == Opcode::CheckDeriv)
2630            .count()
2631    }
2632
2633    #[test]
2634    fn a_walk_inside_a_checked_range_goes() {
2635        // Sixteen bytes were checked, and the walk goes from the start of them to eight in. Both
2636        // ends are in one range, so the second address is in the instance the first belongs to.
2637        let (_, mut func, block, pointer) = blank();
2638        let mut build = Builder::new(&mut func, block);
2639        check(&mut build, pointer, 16);
2640        let field = past(&mut build, pointer, 8);
2641        deriv(&mut build, pointer, field, 4);
2642        build.ret(&[]);
2643        let stats = run(&mut func);
2644        assert_eq!(derivs(&func), 0);
2645        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 1);
2646    }
2647
2648    #[test]
2649    fn a_walk_that_leaves_the_checked_range_stays() {
2650        // Four bytes were checked and the walk goes eight past them. Nothing here says the two
2651        // addresses are in one instance, which is the whole of what the check is about.
2652        let (_, mut func, block, pointer) = blank();
2653        let mut build = Builder::new(&mut func, block);
2654        check(&mut build, pointer, 4);
2655        let field = past(&mut build, pointer, 8);
2656        deriv(&mut build, pointer, field, 4);
2657        build.ret(&[]);
2658        let stats = run(&mut func);
2659        assert_eq!(derivs(&func), 1);
2660        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
2661    }
2662
2663    #[test]
2664    fn two_ranges_holding_one_end_each_do_not_answer_a_walk() {
2665        // The case the one fact rule is written for. Both addresses have been checked, so both are
2666        // inside some instance, and nothing says it is the same one. The walk stays.
2667        let (_, mut func, block, pointer) = blank();
2668        let mut build = Builder::new(&mut func, block);
2669        check(&mut build, pointer, 4);
2670        let field = past(&mut build, pointer, 64);
2671        check(&mut build, field, 4);
2672        deriv(&mut build, pointer, field, 4);
2673        build.ret(&[]);
2674        let stats = run(&mut func);
2675        assert_eq!(derivs(&func), 1);
2676        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
2677    }
2678
2679    #[test]
2680    fn a_walk_inside_a_local_goes_with_nothing_in_front_of_it() {
2681        // The shape almost every derivation check in real code has: a field of a local struct.
2682        // `rucc-safety` emits the walk before the bounds check on what it produced, so a fact from
2683        // an earlier check is usually the wrong size for it and the local is what answers.
2684        let (_, mut func, block, _) = blank();
2685        let mut build = Builder::new(&mut func, block);
2686        let slot = local(&mut build, 16);
2687        let field = past(&mut build, slot, 8);
2688        deriv(&mut build, slot, field, 4);
2689        build.ret(&[]);
2690        let stats = run(&mut func);
2691        assert_eq!(derivs(&func), 0);
2692        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 1);
2693    }
2694
2695    #[test]
2696    fn a_walk_off_the_end_of_a_local_stays() {
2697        // Where the slot stops is where the fact stops. One past the end is the case the runtime
2698        // has slack for and this pass does not use any of it.
2699        let (_, mut func, block, _) = blank();
2700        let mut build = Builder::new(&mut func, block);
2701        let slot = local(&mut build, 16);
2702        let field = past(&mut build, slot, 16);
2703        deriv(&mut build, slot, field, 4);
2704        build.ret(&[]);
2705        let stats = run(&mut func);
2706        assert_eq!(derivs(&func), 1);
2707        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 0);
2708    }
2709
2710    #[test]
2711    fn a_walk_a_call_stands_between_stays_and_is_counted() {
2712        // The same price the other two kinds pay, reported the same way, so the cost of not
2713        // trusting a call is a number per function rather than a paragraph.
2714        let (mut names, mut func, block, pointer) = blank();
2715        let mut build = Builder::new(&mut func, block);
2716        check(&mut build, pointer, 16);
2717        let callee = names.intern("might_free");
2718        let signature = build.func().add_signature(Signature::new());
2719        build.call(callee, signature, &[]);
2720        let field = past(&mut build, pointer, 8);
2721        deriv(&mut build, pointer, field, 4);
2722        build.ret(&[]);
2723        let stats = run(&mut func);
2724        assert_eq!(derivs(&func), 1);
2725        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_DERIV), 1);
2726    }
2727
2728    #[test]
2729    fn a_check_the_module_says_is_inside_a_global_goes_with_nothing_in_front_of_it() {
2730        // The other half of section 7.2's first source. The size of a global lives on the module
2731        // and this pass is given one function, so the answer arrives as a flag `crate::extents`
2732        // wrote before the pipeline started, and all three kinds carry it.
2733        let (_, mut func, block, pointer) = blank();
2734        let mut build = Builder::new(&mut func, block);
2735        let field = past(&mut build, pointer, 8);
2736        deriv(&mut build, pointer, field, 1);
2737        access(&mut build, field, 4);
2738        build.ret(&[]);
2739        marked(&mut func);
2740        let stats = run(&mut func);
2741        assert_eq!(checks(&func), 0);
2742        assert_eq!(lives(&func), 0);
2743        assert_eq!(derivs(&func), 0);
2744        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_STATIC), 1);
2745        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_STATIC), 1);
2746        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_STATIC), 1);
2747    }
2748
2749    #[test]
2750    fn a_check_the_module_says_every_caller_hands_in_goes_with_nothing_in_front_of_it() {
2751        // Section 7.5's summaries, arriving the same way a global's extent does and for the same
2752        // reason: which object a caller passes is a fact about a different function. What the flag
2753        // says is an extent and a lifetime, because the objects `crate::params` believes are a
2754        // caller's frame slot and a global and both are alive for as long as the call runs.
2755        let (_, mut func, block, pointer) = blank();
2756        let mut build = Builder::new(&mut func, block);
2757        let field = past(&mut build, pointer, 8);
2758        deriv(&mut build, pointer, field, 1);
2759        access(&mut build, field, 4);
2760        build.ret(&[]);
2761        flagged(&mut func, Flags::HANDED);
2762        let stats = run(&mut func);
2763        assert_eq!(checks(&func), 0);
2764        assert_eq!(lives(&func), 0);
2765        assert_eq!(derivs(&func), 0);
2766        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_HANDED), 1);
2767        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_HANDED), 1);
2768        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_HANDED), 1);
2769    }
2770
2771    /// A function that takes an index, allocates `size` bytes and tests the answer against null.
2772    ///
2773    /// Gives back the block where the test has passed, the block where it has not, the pointer and
2774    /// the index. The flag is put on by hand, because which calls deserve it is a question about a
2775    /// module and `crate::heap` is what answers it.
2776    ///
2777    /// The index is there for the tests about a walk by a value. A parameter on its own is any
2778    /// number at all, so a test that wants a bounded one puts [`low_bits`] over it the same way the
2779    /// local tests do.
2780    fn allocation(size: i128) -> (Interner, Func, Block, Block, Value, Value) {
2781        let mut names = Interner::new();
2782        let name = names.intern("f");
2783        let mut func = Func::new(name, Signature::new().with_params(&[Type::int(64)]));
2784        let entry = func.create_block();
2785        let inside = func.create_block();
2786        let outside = func.create_block();
2787        let index = func.append_param(entry, Type::int(64));
2788        let mut build = Builder::new(&mut func, entry);
2789        let signature = build.func().add_signature(
2790            Signature::new().with_params(&[Type::int(64)]).with_returns(&[Type::PTR]),
2791        );
2792        let bytes = build.iconst(Type::int(64), size);
2793        let call = build.call(names.intern("malloc"), signature, &[bytes]);
2794        let at = build.func();
2795        at[call].flags |= Flags::HEAP;
2796        let pointer = at[call].results().next().expect("a call that gives back a pointer");
2797        let zero = build.iconst(Type::int(64), 0);
2798        let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
2799        let condition = build.icmp(IntPred::Ne, pointer, null);
2800        build.br_if(condition, inside, &[], outside, &[]);
2801        let mut build = Builder::new(&mut func, outside);
2802        build.ret(&[]);
2803        (names, func, inside, outside, pointer, index)
2804    }
2805
2806    #[test]
2807    fn a_check_inside_an_allocation_the_program_tested_goes() {
2808        // The third of the objects whose extent nobody had to check for. `malloc(16)` says how
2809        // many bytes it made in the call, and the branch on null is what makes it true here.
2810        let (_, mut func, inside, _, pointer, _) = allocation(16);
2811        let mut build = Builder::new(&mut func, inside);
2812        let field = past(&mut build, pointer, 8);
2813        deriv(&mut build, pointer, field, 1);
2814        access(&mut build, field, 4);
2815        build.ret(&[]);
2816        let stats = run(&mut func);
2817        assert_eq!(checks(&func), 0);
2818        assert_eq!(derivs(&func), 0);
2819        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 1);
2820        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 1);
2821        // The lifetime check is the one an allocation says nothing about, because a `free` in this
2822        // same function can end it, and it is what reports a use after free.
2823        assert_eq!(lives(&func), 1);
2824    }
2825
2826    #[test]
2827    fn a_check_on_an_allocation_nobody_tested_stays() {
2828        // Down the other arm the pointer is null, a null pointer is inside no object at all, and
2829        // the check is one that is supposed to fail.
2830        let (_, mut func, _, outside, pointer, _) = allocation(16);
2831        let mut build = Builder::new(&mut func, outside);
2832        access(&mut build, pointer, 4);
2833        build.ret(&[]);
2834        let stats = run(&mut func);
2835        assert_eq!(checks(&func), 1);
2836        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
2837    }
2838
2839    #[test]
2840    fn a_check_past_the_end_of_an_allocation_stays() {
2841        // Four bytes at offset fourteen is two bytes past the sixteen that were asked for, and
2842        // those two bytes are what the check is for.
2843        let (_, mut func, inside, _, pointer, _) = allocation(16);
2844        let mut build = Builder::new(&mut func, inside);
2845        let field = past(&mut build, pointer, 14);
2846        access(&mut build, field, 4);
2847        build.ret(&[]);
2848        let stats = run(&mut func);
2849        assert_eq!(checks(&func), 1);
2850        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
2851    }
2852
2853    #[test]
2854    fn a_walk_that_leaves_an_allocation_stays() {
2855        // One end inside and the other past the end is a walk out of the object, which is what a
2856        // derivation check is there to catch, so both ends have to be inside before it goes.
2857        let (_, mut func, inside, _, pointer, _) = allocation(16);
2858        let mut build = Builder::new(&mut func, inside);
2859        let field = past(&mut build, pointer, 32);
2860        deriv(&mut build, pointer, field, 1);
2861        build.ret(&[]);
2862        let stats = run(&mut func);
2863        assert_eq!(derivs(&func), 1);
2864        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 0);
2865    }
2866
2867    #[test]
2868    fn a_check_inside_an_allocation_goes_across_a_call() {
2869        // The other reason a fact read off the instruction is worth having. How many bytes an
2870        // allocator made is not something a callee can change, so unlike a fact from a check that
2871        // ran this one is still there on the far side of a call.
2872        let (mut names, mut func, inside, _, pointer, _) = allocation(16);
2873        let mut build = Builder::new(&mut func, inside);
2874        access(&mut build, pointer, 4);
2875        let signature = build.func().add_signature(Signature::new());
2876        build.call(names.intern("g"), signature, &[]);
2877        access(&mut build, pointer, 4);
2878        build.ret(&[]);
2879        let stats = run(&mut func);
2880        assert_eq!(checks(&func), 0);
2881        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 2);
2882        // Both lifetime checks stay, and the second one is the one a `free` inside `g` would make
2883        // report.
2884        assert_eq!(lives(&func), 2);
2885    }
2886
2887    /// A function that allocates `size` bytes and never looks at what it got back.
2888    ///
2889    /// The shape `bench/safety/a-strided-column-sum.c` has. The size on its own must not answer a
2890    /// check here, because reading through what `malloc` gave back without testing it is the bug
2891    /// this compiler is for.
2892    fn untested(size: i128) -> (Interner, Func, Block, Value, Value) {
2893        let mut names = Interner::new();
2894        let name = names.intern("f");
2895        let mut func = Func::new(name, Signature::new().with_params(&[Type::int(64)]));
2896        let block = func.create_block();
2897        let index = func.append_param(block, Type::int(64));
2898        let mut build = Builder::new(&mut func, block);
2899        let signature = build.func().add_signature(
2900            Signature::new().with_params(&[Type::int(64)]).with_returns(&[Type::PTR]),
2901        );
2902        let bytes = build.iconst(Type::int(64), size);
2903        let call = build.call(names.intern("malloc"), signature, &[bytes]);
2904        let at = build.func();
2905        at[call].flags |= Flags::HEAP;
2906        let pointer = at[call].results().next().expect("a call that gives back a pointer");
2907        (names, func, block, pointer, index)
2908    }
2909
2910    #[test]
2911    fn a_walk_by_a_step_the_ranges_bound_inside_an_allocation_goes() {
2912        // The first half of tamnd/rucc#880. The step is not a constant, so the walk stops at the
2913        // `ptr_add` and what answers the check has to be asked of the range of addresses it can
2914        // reach. That range is nought to seven plus the four bytes the access wants, all of it
2915        // inside the sixteen the call says it made, and the branch on null is what makes the
2916        // sixteen true here.
2917        let (_, mut func, inside, _, pointer, index) = allocation(16);
2918        let mut build = Builder::new(&mut func, inside);
2919        let step = low_bits(&mut build, index, 7);
2920        let at = walk(&mut build, pointer, step);
2921        check(&mut build, at, 4);
2922        build.ret(&[]);
2923        let stats = run(&mut func);
2924        assert_eq!(checks(&func), 0);
2925        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 1);
2926    }
2927
2928    #[test]
2929    fn a_walk_by_a_step_that_can_leave_an_allocation_stays() {
2930        // The same function with the mask widened. Nought to thirty one plus four bytes runs off
2931        // the end of sixteen, and the bytes past the end are what the check is for.
2932        let (_, mut func, inside, _, pointer, index) = allocation(16);
2933        let mut build = Builder::new(&mut func, inside);
2934        let step = low_bits(&mut build, index, 31);
2935        let at = walk(&mut build, pointer, step);
2936        check(&mut build, at, 4);
2937        build.ret(&[]);
2938        let stats = run(&mut func);
2939        assert_eq!(checks(&func), 1);
2940        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
2941    }
2942
2943    #[test]
2944    fn a_derivation_by_a_step_the_ranges_bound_inside_an_allocation_goes() {
2945        // The same for the derivation check, which is the one the column sum is left with. Both
2946        // ends have to be inside and inside the same object: the near end is the pointer itself and
2947        // the far end is anywhere in nought to seven past it.
2948        let (_, mut func, inside, _, pointer, index) = allocation(16);
2949        let mut build = Builder::new(&mut func, inside);
2950        let step = low_bits(&mut build, index, 7);
2951        let at = walk(&mut build, pointer, step);
2952        deriv(&mut build, pointer, at, 1);
2953        build.ret(&[]);
2954        let stats = run(&mut func);
2955        assert_eq!(derivs(&func), 0);
2956        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 1);
2957    }
2958
2959    #[test]
2960    fn a_walk_into_an_allocation_nobody_tested_stays() {
2961        // The other half of the rule, which this does not weaken. A program that walks into what
2962        // `malloc` gave back without ever looking at it is a program that reads through null when
2963        // the allocation fails, and the checks are what report it.
2964        let (_, mut func, block, pointer, index) = untested(16);
2965        let mut build = Builder::new(&mut func, block);
2966        let step = low_bits(&mut build, index, 7);
2967        let at = walk(&mut build, pointer, step);
2968        check(&mut build, at, 4);
2969        deriv(&mut build, pointer, at, 1);
2970        build.ret(&[]);
2971        let stats = run(&mut func);
2972        assert_eq!(checks(&func), 1);
2973        assert_eq!(derivs(&func), 1);
2974        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
2975        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 0);
2976    }
2977
2978    #[test]
2979    fn a_check_every_caller_hands_in_goes_across_a_call() {
2980        // The reason the flag is worth having at all. A frame slot of the caller is not something
2981        // the callee's own callees can free, so the fact does not die at a call the way a fact
2982        // from a check that ran does.
2983        let (mut names, mut func, block, pointer) = blank();
2984        let mut build = Builder::new(&mut func, block);
2985        access(&mut build, pointer, 4);
2986        let signature = build.func().add_signature(Signature::new());
2987        build.call(names.intern("g"), signature, &[]);
2988        access(&mut build, pointer, 4);
2989        build.ret(&[]);
2990        flagged(&mut func, Flags::HANDED);
2991        let stats = run(&mut func);
2992        assert_eq!(checks(&func), 0);
2993        assert_eq!(lives(&func), 0);
2994        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_HANDED), 2);
2995        assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_HANDED), 2);
2996    }
2997
2998    #[test]
2999    fn a_check_inside_a_global_goes_across_a_call() {
3000        // A callee can free what a global points at and cannot free the global, which lives as
3001        // long as the program does. So this is the one fact besides a local that a call leaves
3002        // standing, and it is read off the instruction rather than out of the scope for that
3003        // reason.
3004        let (mut names, mut func, block, pointer) = blank();
3005        let mut build = Builder::new(&mut func, block);
3006        let callee = names.intern("might_free");
3007        let signature = build.func().add_signature(Signature::new());
3008        build.call(callee, signature, &[]);
3009        access(&mut build, pointer, 4);
3010        build.ret(&[]);
3011        marked(&mut func);
3012        let stats = run(&mut func);
3013        assert_eq!(checks(&func), 0);
3014        assert_eq!(lives(&func), 0);
3015        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
3016        assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 0);
3017    }
3018
3019    #[test]
3020    fn a_check_the_module_marked_costs_fuel_like_any_other() {
3021        // A discharge is a discharge whatever established the fact, so `-fpass-fuel` has to stop
3022        // this one too or a bisection would step over it.
3023        let (_, mut func, block, pointer) = blank();
3024        let mut build = Builder::new(&mut func, block);
3025        access(&mut build, pointer, 4);
3026        build.ret(&[]);
3027        marked(&mut func);
3028        let stats =
3029            DISCHARGE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
3030        assert_eq!(checks(&func) + lives(&func), 1);
3031        assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
3032    }
3033
3034    #[test]
3035    fn a_walk_whose_two_ends_are_off_two_pointers_with_no_ranges_says_the_same() {
3036        // Nothing in this function steps by a value, so the ranges are never built and the answer
3037        // has to come out of the constant reader alone. That reader stopped for one reason, and it
3038        // is the same reason.
3039        let (_, mut func, block, pointer) = blank();
3040        let other = func.append_param(block, Type::PTR);
3041        let mut build = Builder::new(&mut func, block);
3042        let at = past(&mut build, other, 8);
3043        deriv(&mut build, pointer, at, 4);
3044        build.ret(&[]);
3045        let stats = run(&mut func);
3046        assert_eq!(derivs(&func), 1);
3047        assert_eq!(stats.count(Kind::Missed, super::TWO_BASES_DERIV), 1);
3048    }
3049
3050    #[test]
3051    fn a_walk_whose_two_ends_are_off_two_pointers_says_so() {
3052        // Nothing comparable to ask about. Both ends are readable and each is somewhere inside
3053        // something, and two facts of that shape say nothing at all about it being one something,
3054        // which is the only thing a derivation check wants to know.
3055        let (_, mut func, block, pointer, index) = indexed();
3056        let other = func.append_param(block, Type::PTR);
3057        let mut build = Builder::new(&mut func, block);
3058        let step = low_bits(&mut build, index, 7);
3059        let at = walk(&mut build, other, step);
3060        deriv(&mut build, pointer, at, 4);
3061        build.ret(&[]);
3062        let stats = run(&mut func);
3063        assert_eq!(derivs(&func), 1);
3064        assert_eq!(stats.count(Kind::Missed, super::TWO_BASES_DERIV), 1);
3065    }
3066
3067    #[test]
3068    fn a_walk_off_a_pointer_this_function_was_handed_says_so() {
3069        // The largest pile after a loaded pointer, 1321 checks on SQLite. Everything about the
3070        // shape is readable: one base, a step the ranges bound, both ends off that base. What is
3071        // missing is how many bytes belong to the object, and a pointer that arrived as a
3072        // parameter is one nothing in the function can say that about. Section 7.5's summaries are
3073        // what would.
3074        let (_, mut func, block, pointer, index) = indexed();
3075        let mut build = Builder::new(&mut func, block);
3076        let step = low_bits(&mut build, index, 7);
3077        let at = walk(&mut build, pointer, step);
3078        deriv(&mut build, pointer, at, 4);
3079        build.ret(&[]);
3080        let stats = run(&mut func);
3081        assert_eq!(derivs(&func), 1);
3082        assert_eq!(stats.count(Kind::Missed, super::NO_EXTENT_HANDED), 1);
3083    }
3084
3085    #[test]
3086    fn a_walk_off_a_pointer_this_function_loaded_says_so() {
3087        // The largest pile of the lot, 2155 checks on SQLite, and the shape is `p->field[i]`. The
3088        // extent of what a pointer in memory points at is not written down anywhere the compiler
3089        // can see today, which is what `__counted_by` and the type plane are for.
3090        let (_, mut func, block, pointer, index) = indexed();
3091        let mut build = Builder::new(&mut func, block);
3092        let info = MemInfo {
3093            size: 8,
3094            align: 8,
3095            order: MemOrder::NotAtomic,
3096            tbaa: None,
3097            owns: 0,
3098            restrict: Restrict::NONE,
3099        };
3100        let args = build.func().push_values(&[pointer]);
3101        let extra = Extra::Mem(build.func().add_mem(info));
3102        let held = build.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR);
3103        let step = low_bits(&mut build, index, 7);
3104        let at = walk(&mut build, held, step);
3105        deriv(&mut build, held, at, 4);
3106        build.ret(&[]);
3107        let stats = run(&mut func);
3108        assert_eq!(derivs(&func), 1);
3109        assert_eq!(stats.count(Kind::Missed, super::NO_EXTENT_LOADED), 1);
3110    }
3111
3112    #[test]
3113    fn a_walk_off_a_global_says_so() {
3114        // 485 checks on SQLite, and the one pile of the four where somebody does know the answer.
3115        // A global's extent is on the module, `crate::extents` reads it and writes the fact onto
3116        // every check it can settle before the pipeline starts, and it cannot settle this one
3117        // because it runs before anything has put a number on the index. See tamnd/rucc#878.
3118        let (mut names, mut func, block, _, index) = indexed();
3119        let mut build = Builder::new(&mut func, block);
3120        let extra = Extra::Symbol(names.intern("g"));
3121        let base = build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
3122        let step = low_bits(&mut build, index, 7);
3123        let at = walk(&mut build, base, step);
3124        deriv(&mut build, base, at, 4);
3125        build.ret(&[]);
3126        let stats = run(&mut func);
3127        assert_eq!(derivs(&func), 1);
3128        assert_eq!(stats.count(Kind::Missed, super::NO_EXTENT_GLOBAL), 1);
3129    }
3130
3131    #[test]
3132    fn a_walk_off_a_pointer_the_check_does_not_name_stays() {
3133        // The capability has to be the `cap_of` of the pointer that went in. One naming something
3134        // else is asking about a different instance and is not this pass's to answer.
3135        let (_, mut func, block, pointer) = blank();
3136        let mut build = Builder::new(&mut func, block);
3137        check(&mut build, pointer, 16);
3138        let field = past(&mut build, pointer, 8);
3139        let args = build.func().push_values(&[field]);
3140        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
3141        let width = build.iconst(Type::int(64), 4);
3142        let args = build.func().push_values(&[capability, pointer, field, width]);
3143        build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
3144        build.ret(&[]);
3145        let stats = run(&mut func);
3146        assert_eq!(derivs(&func), 1);
3147        assert_eq!(stats.count(Kind::Missed, super::NOT_ITS_CAPABILITY_DERIV), 1);
3148    }
3149}