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