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