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. Five kinds are the pass's business, the bounds check and the lifetime check and the
10//! initialization check and the type check in front of an access and the derivation check after a
11//! walk, because they are emitted together and taking out one of five is a fifth of a saving.
12//!
13//! The four in front of an access are four different claims and they get four fact sets. Which
14//! bytes are inside one storage instance, which of them are in an instance that is alive, which of
15//! them have been written, and which of them agree with a type. A check that passes establishes
16//! exactly one of the four, they are killed by different things, and reporting them as one number
17//! would hide which of the four a kept check is still being paid for.
18//!
19//! # The two halves
20//!
21//! Section 7.7 asks for the pass and the condition to be separate things, and they are. What is in
22//! this file is a walk: which check runs before which, which pointer was computed from which, and
23//! how far apart two addresses are. Nothing here decides whether that is enough. The condition
24//! under which a check may go is a rule in `rules/safety.rules`, a solver has to agree with it
25//! before this crate finishes building, and `crate::rules::safety` is the table it compiles into.
26//!
27//! The split is worth the trouble because the two halves fail differently. A walk that gets the
28//! context wrong is a bug of the ordinary kind, and section 14.3's differential check accounting,
29//! which runs the instrumented program with every check and again with the discharged ones gone,
30//! is what looks for it. A removal condition that is wrong is arithmetic that is off at the ends of
31//! the type. It gives the right answer on every test anybody writes and lets one access through in
32//! the one case nobody thought of, and nothing observes that until somebody exploits it.
33//!
34//! # What it establishes and what it asks
35//!
36//! Walking the dominator tree from the entry, the pass carries a set of facts. A `check_bounds`
37//! that stays is a fact, because a check that passes says the bytes it was about lie inside one
38//! storage instance, and a check that fails does not return. A fact is remembered as the pointer's
39//! base and the constant offset from it, which is what a chain of `ptr_add` over constants comes
40//! to, plus how many bytes the access covers.
41//!
42//! At the next `check_bounds`, the pointer is normalized the same way. When a fact shares its base,
43//! the distance between the two accesses is the difference of the two offsets, and that is a number
44//! this pass has rather than a claim it makes: both addresses are the same value plus a constant.
45//! The question of whether the later bytes are inside the earlier ones is then handed to the table,
46//! which answers it in sixty four bit arithmetic rather than in the offsets, and the check goes
47//! only if the answer is yes.
48//!
49//! A check whose extent is an operand is left out of all of this, in both directions. Section 7.4's
50//! hoisted check covers as many bytes as its loop runs times, and every range compared here is a
51//! pair of numbers, so such a check is neither read as a fact nor asked about. Reading its payload
52//! would be worse than skipping it, since the size there is one element of the walk rather than the
53//! range the check is about, and a fact recorded from it would be smaller than the truth in one
54//! direction and a question asked from it smaller in the other.
55//!
56//! The capability operand has to be the `cap_of` of the check's own pointer, which is the shape
57//! `rucc-safety` emits and the shape the argument needs. The check being removed asks whether its
58//! bytes are inside the instance that owns its own pointer, its pointer is inside the range the
59//! earlier check established, and that range is inside one instance, so the answer is yes. A check
60//! whose capability came from somewhere else is asking about a different instance and is left
61//! alone. Nothing is required of the earlier check's capability, because all that is used of it is
62//! that the check passed, and a check that passed put its bytes inside one instance whatever
63//! capability it named.
64//!
65//! # The conjunct that is not about bytes
66//!
67//! A `check_bounds` tests two things, because document 06 section 6.3 put the access alignment on
68//! it rather than in a check of its own: that the bytes are inside one instance, and that the
69//! address starts where an access of that alignment may start. Everything above is about the
70//! first. A check that goes takes the second away with it, so nothing goes until something has
71//! answered it, which is `aligned` below and which reads the object the address came from and the
72//! steps taken from it. An access that assumes nothing about where it starts has nothing to
73//! answer, and a member of a packed record is exactly that.
74//!
75//! A global is settled elsewhere and arrives as [`Flags::ALIGNED`], for the reason
76//! [`Flags::STATIC`] beside it exists: how aligned a global is lives on the module and a pass is
77//! given a function. Without that the gate would cost seventeen times what it costs, which is the
78//! measurement in the changelog and is what says the flag earns its bit.
79//!
80//! What is left is a pointer this function was handed, one it loaded out of memory, and one a call
81//! gave back, and for those the answer is the same one the bytes get: a check that stays is a check
82//! that runs, and a check that runs tests the alignment and refuses when it does not hold. So the
83//! first access through a pointer somebody handed in proves for nothing what every later access
84//! through the same value needs, and `Scope::aligns` carries it.
85//!
86//! That fact is easier to carry than a range and it is worth saying why, because the section below
87//! spends a page arguing about what a call does to a range. A range is about storage and storage
88//! can be freed and handed back out smaller. An alignment is about the number in the value, and
89//! nothing in a function changes the number an SSA value holds, so it crosses a call, it crosses
90//! inline assembly and it crosses a `meta_end`. Dominance is the only thing that bounds it.
91//!
92//! What is still not answered is counted rather than argued about, the same as everything else
93//! here, so `-fopt-info-missed` says what the rest of the `!aligned` fact of section 6.2.4 would be
94//! worth. On the SQLite amalgamation that row is 6046 checks at 1178 sites, down from 10729 at 1413
95//! once a check's own answer is carried, and the checks in the assembly went from 28473 to 23790.
96//!
97//! # The fact nobody had to check for
98//!
99//! Section 7.2 lists four sources of a discharge and puts the frontend first, because the majority
100//! of accesses in real C are to a local or a global at a constant offset and the bounds of either
101//! are not something anybody has to find out. An `alloca` of a fixed size makes one storage
102//! instance of that many bytes and says so in its payload, so the range from its address to that
103//! many further along is inside one instance for exactly the reason a passing `check_bounds` says
104//! its own range is. When the address a check is about normalizes to such an `alloca`, that range
105//! is the fact, and the question put to the table is the same question with the same rule
106//! answering it.
107//!
108//! Two things make it worth more than a fact a check established. It is there before anything has
109//! run, so the first access to a local is discharged rather than only the second. And no call takes
110//! it away: a callee cannot free a frame slot, whatever it does to whatever the slot points at, so
111//! this fact is asked separately rather than kept in the set the walk throws away at the first call
112//! it cannot see through.
113//!
114//! Only the fixed size form. A variable length array is an `alloca` with an operand and a payload
115//! whose size field reads zero, and reading it anyway would discharge every check in the array.
116//!
117//! A global is the same fact about the other half of section 7.2's sentence, and it arrives here
118//! differently for one reason: how big a global is lives on the module and this pass is given one
119//! function. So `crate::extents` works it out over the module before the pipeline starts, asks the
120//! same rule, and writes the answer onto the check as [`Flags::STATIC`], which is what
121//! `crate::nofree` does with what a call reaches and for the same reason. What is read here is what
122//! the IR says, the same way the pass reads an opcode.
123//!
124//! It answers a lifetime check as well as a bounds check, which a local does not. What a local
125//! gives is an extent, and how long it stays alive is the block it was declared in, which is a
126//! question this pass has nothing to say about. A global has static storage duration and is alive
127//! wherever the question is asked.
128//!
129//! # The walk that stops at a step it cannot read
130//!
131//! Everything above needs the address to be a base and a constant, and an array index is not a
132//! constant. The walk stops at the first `ptr_add` whose step is a value, and what comes out is a
133//! fact about a base whose size nobody knows, which answers nothing.
134//!
135//! Section 7.2's third source is what gets past it. Document 10's ranges know something about the
136//! step even though it is not a number: an index the program has already tested against a length,
137//! or one whose low bits are all that is used, is bounded. So the walk carries on, adding the low
138//! end of the step's range to the offset and the width of the range to the size, and what it ends
139//! up with is the range of addresses the access can land in.
140//!
141//! Whether an object holding all of that range holds the one address the access actually uses is
142//! its own rule, `reached.i64`, which leaves the distance opaque so that one answer covers every
143//! value the step could take. It is a rule of its own rather than the containment rule asked about
144//! the far end of the range, and the reason is section 7.7's: turning a range of addresses into one
145//! containment question is arithmetic on the thing being proved, and a pass doing that quietly is
146//! what the split between the walk and the rule exists to stop.
147//!
148//! What the range is asked of is the list above and not a shorter one: the local an `alloca`
149//! declares, the object an allocator made where the program has tested it, and the ranges checks
150//! that already ran established. The allocation was missing from that list until tamnd/rucc#880,
151//! which is what left a loop walking an index into its own `malloc` with every check it started
152//! with however plainly the call said how many bytes it made.
153//!
154//! The range is only ever asked with and never recorded. What a check proves when it runs is that
155//! the address the program used was inside the object, and nothing at all about the rest of a
156//! range this pass made up around it. So a check discharged this way records the narrow fact, the
157//! bytes the access really wanted, which is the thing that was proved and is what a second check
158//! of the same bytes is answered by.
159//!
160//! The ranges are built only for a function that has a walk by a value in it, because they cost a
161//! copy of the control flow graph and a function without one would never ask them anything.
162//!
163//! # The lifetime half, and what it borrows from the other one
164//!
165//! A `check_live` that stays is a fact too, and a smaller one than it looks: it says the storage
166//! instance holding its own address is alive, and it says nothing about the address four bytes
167//! along, because that address might be in a different instance. On its own that fact discharges
168//! only a second lifetime check of the very same address, and the shape `rucc-safety` emits is a
169//! lifetime check per field rather than per object, so on its own it would almost never fire.
170//!
171//! What makes it fire is the bounds fact sitting next to it. A `check_bounds` that passed put its
172//! whole range inside one instance, so if the lifetime check's address is in that range, the
173//! instance that was found alive is the instance the whole range is in, and the whole range is
174//! alive. So a lifetime fact is recorded as the widest checked range containing its address, and a
175//! later lifetime check is asked about as a single byte. The question of whether that byte is in
176//! that range is the same question the bounds half asks, put to the same rule.
177//!
178//! The order the two arrive in is what makes this work rather than a coincidence to be careful
179//! about: `rucc-safety` emits the bounds check first and the lifetime check second, so the range is
180//! established by the time there is a lifetime fact to widen. A lifetime check that arrives with no
181//! range around it keeps the narrow fact, which is correct and worth little.
182//!
183//! # The derivation half, which is one question rather than two
184//!
185//! `rucc-safety` puts a `check_deriv` after every `ptr_add` off a pointer, and what it asks is not
186//! about a range at all: it asks whether the pointer that came out is still in the storage instance
187//! the pointer that went in belongs to. The runtime has some slack in it for a pointer that walked
188//! exactly off either end, and none of that slack is used here, because the case this pass answers
189//! is the one where both ends are plainly inside something.
190//!
191//! What answers it is one fact holding both ends. A `check_bounds` that passed put its whole range
192//! inside one instance, so if the address that went in and the address that came out are both in
193//! that range, the second is in the instance the first belongs to, which is the question. It has to
194//! be one fact and not one for each end: two facts saying two addresses are each inside some
195//! instance say nothing about whether it is the same instance, and that is the only thing being
196//! asked. A local is a fact of exactly this shape and is asked the same way.
197//!
198//! Both ends are asked about as a single byte, the way a lifetime check is, and for the same reason.
199//! Nothing here is claiming anything about how many bytes are readable at either address.
200//!
201//! A `check_deriv` that stays leaves no fact behind. What it establishes is that two addresses share
202//! an instance, which is not a range of bytes and does not fit in what this walk carries, and the
203//! `covered.i64` rule has nothing to say about it. Recording it would mean a second kind of fact and
204//! a second rule, and the pointer it is about nearly always gets a `check_bounds` of its own a few
205//! instructions later that establishes the range properly.
206//!
207//! # What a call throws away, and which calls throw away nothing
208//!
209//! Section 7.3 says nothing kills a bounds fact except a redefinition of the capability, which in
210//! SSA is never. This pass is stricter than that about the lifetime half and not about the bounds
211//! half: a call, or anything else this pass cannot see through, drops every lifetime fact it is
212//! carrying, and a call keeps the bounds facts and marks them as having had one run over them.
213//!
214//! The case a call is about is a `free` and then an allocation of something smaller at the same
215//! address. The range established before the call is no longer inside one instance after it, and
216//! what document 07 leaves that to is the lifetime judgement rather than this one. The next section
217//! is the argument that the lifetime judgement is now enough, and what a mark on a fact buys.
218//!
219//! A `meta_end` and a `meta_transfer` drop both halves, and so does inline assembly. Nothing emits
220//! either of the first two yet, so most of this costs nothing today and is the difference between
221//! conservative and wrong on the day the instrumentation starts ending lifetimes. `crate::nofree`
222//! treats them the same way. Assembly is with them rather than with the calls because the argument
223//! below rests on the runtime owning the planes, and a block of assembly can write over one without
224//! the runtime having been asked.
225//!
226//! The two facts nobody had to check for go across a call untouched, and neither is an exception to
227//! the paragraph above because neither is in the set being thrown away. A callee cannot free a
228//! frame slot and cannot free a global, so a check the declaration answers is answered on the far
229//! side of any call at all.
230//!
231//! A call that says it reaches nothing which can free is the exception, and it is not this pass
232//! being trusting. `crate::nofree` works the answer out over the whole module before the pipeline
233//! starts and writes it onto the call site as [`Flags::NOFREE`], because the fact belongs to the
234//! callee and a pass is given one function. Reading it here is reading what the IR says, the same
235//! way the pass reads an opcode. Nothing else about a call is believed: the lifetime facts still go
236//! across an unmarked call, a call through an address, and inline assembly.
237//!
238//! What the strictness still costs is measured rather than guessed. A check that a fact would have
239//! covered if a call had not intervened is counted, so `-fopt-info-missed` says per function what
240//! is left to win. On the SQLite amalgamation 3.53.4 at `-O2 -fsafety=detect` that is 3978 bounds
241//! checks at 605 sites, 2679 lifetime checks at 596 sites and 2538 derivation checks at 534 sites,
242//! against 28473 bounds checks and 20423 lifetime checks that survive the whole pipeline.
243//!
244//! # Why keeping the bounds half is allowed
245//!
246//! The eighth box of tamnd/rucc#1241 asked this pass to stop throwing the bounds facts away, on the
247//! grounds that the lifetime check at the access compares a version and will refuse the case the
248//! paragraph above is about. It is done and the reason belongs here rather than in the issue,
249//! because what it turns on is what this file does.
250//!
251//! The claim is that a bounds fact may cross a call when every access that then uses it is guarded
252//! by a lifetime check that refuses once the instance has changed. Three things have to hold for
253//! that. The first two hold since the runtime started reading the version a recovery found and
254//! started moving the aux with the bytes at a copy, and the third is what `Known::since` is for.
255//!
256//! The first holds. `rucc_safety::check` emits the two checks as a pair off one capability, so the
257//! only question is whether this pass took the lifetime half out again, and there are five ways it
258//! does. `REMOVED_LIVE_STATIC` is a global, `REMOVED_LIVE_LOCAL` is a frame slot of this
259//! function, and `REMOVED_LIVE_HANDED` is an object every caller hands in, which `crate::params`
260//! only ever says of a caller's frame slot or of a global this module vouches for. None of those
261//! three can be freed by anybody, so a bounds fact about one does not go stale in the first place.
262//! `REMOVED_LIVE` comes out of `Scope::alive`, which is thrown away at the call, so it cannot
263//! fire on the far side of one. `REMOVED_LIVE_RANGE` is the frame slot rule or `Scope::alive`
264//! widened, so it is those two again. On the far side of a call the lifetime check is therefore
265//! either still standing or about an object no callee can end.
266//!
267//! The second holds and did not when this section was first written. A lifetime check that is still
268//! standing refuses a changed instance only when the version the capability carries is about the
269//! pointer the access went through, and `rucc_safe_rt::check`'s `stale` used to take the weaker
270//! reading for every recovered capability, which is every pointer a `cap_of` could not trace back to
271//! an allocator call. A recovery that walked the planes answers now, so a pointer a function was
272//! handed is covered. A pointer it loaded out of memory is covered too, and that took a second
273//! thing: the capability for one of those comes out of the aux slot beside the word, a slot holds a
274//! displacement from the pointer it was written beside rather than an address, and a `memcpy` used
275//! to move the word and leave the slot. `rucc_safe_rt::check::relocate` moves the aux across at
276//! every copy a wrapper interposes, which is what tamnd/rucc#1148 wanted. What is left uncovered is
277//! a pointer stored by code this compiler did not build, and an object a foreign writer has touched
278//! is the case `rucc_safe_rt::layout::Meta::HANDED` already stands apart.
279//!
280//! The third is a hazard the relaxation introduces rather than one it inherits, and it is what the
281//! mark is for. A lifetime fact is widened by `widened` out of the bounds facts standing at the
282//! time, so bounds facts that survive a call would otherwise widen lifetime facts established after
283//! it. A bounds fact saying a range is inside one instance, taken before a `free` and an allocation
284//! of something smaller at the same address, would then widen a lifetime check that passed on the
285//! new instance into a claim that the whole of the old range is alive, and the far end of that
286//! range is storage the new instance does not own. So `Known::since` marks where the facts a call
287//! has run over end, `widened` reads only the ones after it, and the marked ones answer a bounds
288//! check and nothing else.
289//!
290//! The derivation rule reads only the unmarked ones too, and that one is caution rather than
291//! necessity. What a `check_deriv` asks is whether two addresses share an instance, and a marked
292//! fact answers the question it was established for rather than that one. Letting it read them
293//! would still refuse every case that matters, because the access through a pointer it wrongly let
294//! through has a lifetime check of its own that the version compare refuses, but the report would
295//! arrive at the access as judgement J1 instead of at the derivation as J2, and a derivation
296//! nothing is ever read through would go unreported. So it reads the unmarked ones and the cost is
297//! counted in the `PAST_A_CALL_DERIV` row.
298//!
299//! What it comes to, on the SQLite amalgamation 3.53.4 at `-O2 -fsafety=detect`, before against
300//! after. 414 bounds checks go, which is 28887 down to 28473, and the assembly shrinks by 107
301//! kilobytes. 131 derivation checks arrive, 22754 up to 22885, and they are the other half of the
302//! paragraph above: a bounds check that is removed establishes nothing, so a check the marked fact
303//! answered no longer pushes a fact of its own, and the derivation rule was reading that. Lifetime
304//! checks do not move at all, which is the point. Net it is 283 fewer checks in the object.
305//!
306//! Why it is only 414 is worth reading, because it says where the next piece of work is and it is
307//! not here. A bounds check has to pass the alignment guard before any rule may take it out, and
308//! the guard is only asked once a rule has answered, so the row counting what it costs only counts
309//! checks something was ready to remove. That row goes from 8464 checks to 10729. Those 2265 are
310//! bounds checks a fact that crossed a call now answers and the alignment guard then keeps anyway,
311//! and they are five times the number that got out. The alignment question is `settles` and
312//! `aligned` in this file, and it is the binding constraint on the bounds half now rather than the
313//! call is.
314
315use std::collections::{HashMap, HashSet};
316
317use rucc_ir::{Block, Def, Extra, Flags, Func, Inst, Meta, Opcode, Type, Value};
318
319use crate::range::query::Ranges;
320use crate::rules::{Piece, Subject, Table, safety};
321use crate::{Analyses, Analysis, Cfg, Fuel, Pass, Preserved, Stats, copy, heap};
322
323/// Recorded once for each bounds check taken out.
324const REMOVED: &str = "bounds check removed, a dominating check covers the same bytes";
325
326/// Recorded once for each bounds check taken out because it was inside a local.
327const REMOVED_LOCAL: &str = "bounds check removed, its bytes are inside a local this function \
328 declares";
329
330/// Recorded once for each bounds check taken out because it was inside a global.
331const REMOVED_STATIC: &str = "bounds check removed, its bytes are inside an object of static \
332 storage duration";
333
334/// Recorded once for each bounds check taken out because every caller hands in the object.
335const REMOVED_HANDED: &str = "bounds check removed, its bytes are inside an object every call to \
336 this function hands it";
337
338/// Recorded once for each bounds check taken out because an allocator made the object.
339const REMOVED_MADE: &str = "bounds check removed, its bytes are inside an object an allocator made \
340 and this function has tested";
341
342/// Recorded once for each bounds check taken out because a range answered the step it walked by.
343const REMOVED_RANGE: &str = "bounds check removed, every address the walk can reach is inside the \
344 object it started from";
345
346/// Recorded for a bounds check removed by carrying its walk past the step the constant reader
347/// stopped at, all the way back to the pointer its capability names.
348const REMOVED_MIDWAY: &str = "bounds check removed, its walk was carried on to the pointer its \
349 capability names and every address it can reach is held";
350
351/// Recorded once for each lifetime check taken out.
352const REMOVED_LIVE: &str = "lifetime check removed, a dominating check covers the same storage";
353
354/// Recorded once for each lifetime check taken out because it was inside a global.
355const REMOVED_LIVE_STATIC: &str =
356 "lifetime check removed, its storage lives as long as the program does";
357
358/// Recorded once for each lifetime check taken out because every caller hands in the object.
359const REMOVED_LIVE_HANDED: &str = "lifetime check removed, its storage is an object every call to \
360 this function hands it";
361
362/// Recorded once for each lifetime check taken out because it was inside a frame slot.
363const REMOVED_LIVE_LOCAL: &str =
364 "lifetime check removed, its storage is a frame slot of this function";
365
366/// Recorded once for each lifetime check taken out because a range answered the step it walked by.
367const REMOVED_LIVE_RANGE: &str = "lifetime check removed, every address the walk can reach is in \
368 storage a check found alive";
369
370/// The same as [`REMOVED_MIDWAY`], for a lifetime check.
371const REMOVED_MIDWAY_LIVE: &str = "lifetime check removed, its walk was carried on to the pointer \
372 its capability names and every address it can reach is alive";
373
374/// Recorded once for each init check taken out because one in front of it said the same bytes had
375/// been written.
376const REMOVED_INIT: &str = "initialization check removed, a dominating check covers the same bytes";
377
378/// Recorded for an init check that would have gone if there had been fuel for it.
379const NO_FUEL_INIT: &str = "initialization check kept, the pass ran out of fuel";
380
381/// Recorded once for each init check a dominating one answered before something that could hand
382/// the storage back out ran in between.
383const PAST_A_CALL_INIT: &str = "initialization check kept, a dominating check covers its bytes and \
384 something that could end the storage ran in between";
385
386/// Recorded for an init check whose pointer this pass cannot read as a base and a constant.
387const UNKNOWN_SHAPE_INIT: &str =
388 "initialization check left alone, its pointer is not a base and a constant";
389
390/// Recorded for an init check nothing in front of it had anything to say about.
391const NOTHING_WROTE_IT: &str =
392 "initialization check kept, nothing dominating it says those bytes have been written";
393
394/// Recorded once for each type check taken out because one in front of it asked the same question
395/// of the same bytes.
396const REMOVED_TYPE: &str =
397 "type check removed, a dominating check covers the same bytes at the same type";
398
399/// Recorded for a type check that would have gone if there had been fuel for it.
400const NO_FUEL_TYPE: &str = "type check kept, the pass ran out of fuel";
401
402/// Recorded once for each type check a dominating one answered before something that could hand the
403/// storage back out ran in between.
404const PAST_A_CALL_TYPE: &str = "type check kept, a dominating check covers its bytes at its type \
405 and something that could end the storage ran in between";
406
407/// Recorded for a type check whose pointer this pass cannot read as a base and a constant, or whose
408/// payload names no plane entry to ask about.
409const UNKNOWN_SHAPE_TYPE: &str =
410 "type check left alone, its pointer is not a base and a constant or it names no type";
411
412/// Recorded for a type check nothing in front of it had anything to say about.
413const NOTHING_TYPED_IT: &str =
414 "type check kept, nothing dominating it says those bytes agree with that type";
415
416/// Recorded for a bounds check that would have gone if there had been fuel for it.
417const NO_FUEL: &str = "bounds check kept, the pass ran out of fuel";
418
419/// Recorded for a lifetime check that would have gone if there had been fuel for it.
420const NO_FUEL_LIVE: &str = "lifetime check kept, the pass ran out of fuel";
421
422/// Recorded once for each derivation check taken out because a range answered the step it walked by.
423const REMOVED_DERIV_RANGE: &str = "derivation check removed, every address either end can reach is \
424 inside one checked range";
425
426/// Recorded for a bounds check a call cost, which is the honest price of the paragraph above.
427///
428/// This one is worth reading rather than skipping. It is the number of checks that are still being
429/// paid for because `crate::nofree` could not vouch for a call, so it says per function what the
430/// rest of section 7.5's summary work would be worth before anybody writes it.
431const PAST_A_CALL: &str =
432 "bounds check kept, a call between it and the check that covers it might free";
433
434/// The same, for a lifetime check. Section 8.8 is about this number rather than the one above.
435const PAST_A_CALL_LIVE: &str =
436 "lifetime check kept, a call between it and the check that covers it might free";
437
438/// Recorded for a bounds check kept because nothing here says where the access starts.
439///
440/// The alignment conjunct of judgement J1 rides on `check_bounds`, so taking the check out takes
441/// the alignment test with it. Recorded only for a check a rule had already answered the bytes of,
442/// so the number is what the gate costs rather than how many checks have an alignment, which makes
443/// it what the `!aligned` fact of `spec/safe-memory/06-instrumentation.md` section 6.2.4 would be
444/// worth.
445///
446/// What is left in this row is the pointers no check has run on yet, since one that has is answered
447/// by [`Scope::proved`]. So it is now a count of first accesses rather than of all of them, and
448/// what would take it down further is the front end saying what a pointer is aligned to at the
449/// point it makes one.
450const UNKNOWN_ALIGNMENT: &str =
451 "bounds check kept, nothing here says the address is aligned to what the access assumes";
452
453/// Recorded for a bounds check whose address reached something saying an alignment that was short.
454///
455/// The other half of the row above, and it is separate because the two are worth different things.
456/// That one is a value nothing here says anything about, and a fact from somewhere else could take
457/// it. This one already has an answer and the answer is no, so a fact about where a pointer starts
458/// would change nothing.
459///
460/// Two shapes end up here and they are not the same, which is worth knowing before anybody reads
461/// the number as a target. One is `(int *)(p + 1)`, where the object is known and the step is a
462/// constant that lands a byte into it. That is row S7 and the check has to stay. The other is a
463/// step nobody can read, where [`divides`] answers one because every number divides by one, so the
464/// walk comes back saying a byte and means it does not know. Telling those apart is what a better
465/// [`divides`] would do and this row is where the work would show up.
466const LOST_ALIGNMENT: &str =
467 "bounds check kept, what the address was computed from says less alignment than it assumes";
468
469/// Recorded for a bounds check whose operands this pass cannot read.
470const UNKNOWN_SHAPE: &str = "bounds check left alone, its pointer is not a base and a constant";
471
472/// Recorded for a bounds check whose capability names neither its address nor the base of it.
473///
474/// The other way [`about`] gives up, and it is a different thing entirely from the row above. The
475/// capability here is readable and it names a value the address really was walked off, just not one
476/// of the two [`its_own`] accepts: `rucc_safety::origin` shares one capability down a whole
477/// derivation chain, and [`normal`] stops walking at the first step it cannot read, so a chain with
478/// a step like `i * 4` in it leaves the capability naming something further back than the base.
479/// Splitting this row into the three below it is what #1390 asked for, and the three sit in the
480/// order the pass fails at them. This one is the walk not getting back to the named pointer at all,
481/// which on the amalgamation is nothing, and the other two are the range rules refusing the walk it
482/// did get back.
483const MIDWAY_CAPABILITY: &str =
484 "bounds check left alone, its capability names a pointer further back than its base";
485
486/// Recorded for a midway bounds check where nothing at all is known about the pointer named.
487///
488/// The one that matters. [`beyond`] answered a range of addresses off the pointer the capability
489/// names, every rule was asked, and not one of them has ever heard of that pointer: it is not a
490/// local this function declared and no bounds check standing here is about it. So the range is not
491/// too wide and the walk is not wrong, there is simply no extent for the thing the capability was
492/// taken at, and no arrangement of the rules already here will produce one.
493const MIDWAY_NO_EXTENT: &str =
494 "bounds check left alone, nothing here says how far the object its capability names runs";
495
496/// Recorded for a midway bounds check whose walk reaches outside what is known about the pointer.
497///
498/// The honest refusals. Something is known about the pointer the capability names and the addresses
499/// the walk can reach are not all inside it, which is either a range that could be tighter or an
500/// access that really can go out of the object.
501const MIDWAY_OVER: &str =
502 "bounds check left alone, its walk can reach outside what is known about the object";
503
504/// Recorded for a bounds check about a range the program worked out.
505const COMPUTED_EXTENT: &str =
506 "bounds check left alone, how many bytes it covers is a number only the program has";
507
508/// Recorded for a lifetime check whose operands this pass cannot read.
509const UNKNOWN_SHAPE_LIVE: &str =
510 "lifetime check left alone, its pointer is not a base and a constant";
511
512/// The same as [`MIDWAY_CAPABILITY`], for a lifetime check.
513const MIDWAY_CAPABILITY_LIVE: &str =
514 "lifetime check left alone, its capability names a pointer further back than its base";
515
516/// The same as [`MIDWAY_NO_EXTENT`], for a lifetime check.
517const MIDWAY_NO_EXTENT_LIVE: &str =
518 "lifetime check left alone, nothing here says how far the object its capability names runs";
519
520/// The same as [`MIDWAY_OVER`], for a lifetime check.
521const MIDWAY_OVER_LIVE: &str =
522 "lifetime check left alone, its walk can reach outside what is known about the object";
523
524/// Recorded once for each derivation check taken out.
525const REMOVED_DERIV: &str =
526 "derivation check removed, one checked range holds both the pointer and where it walked to";
527
528/// Recorded once for each derivation check taken out because it walked inside a local.
529const REMOVED_DERIV_LOCAL: &str =
530 "derivation check removed, it walks inside a local this function declares";
531
532/// Recorded once for each derivation check taken out because it walked inside a global.
533const REMOVED_DERIV_STATIC: &str =
534 "derivation check removed, it walks inside an object of static storage duration";
535
536/// Recorded once for each derivation check taken out because every caller hands in the object.
537const REMOVED_DERIV_HANDED: &str = "derivation check removed, it walks inside an object every call \
538 to this function hands it";
539
540/// Recorded once for each derivation check taken out because an allocator made the object.
541const REMOVED_DERIV_MADE: &str = "derivation check removed, it walks inside an object an allocator \
542 made and this function has tested";
543
544/// Recorded for a derivation check that would have gone if there had been fuel for it.
545const NO_FUEL_DERIV: &str = "derivation check kept, the pass ran out of fuel";
546
547/// Recorded for a derivation check a call cost.
548const PAST_A_CALL_DERIV: &str =
549 "derivation check kept, a call between it and the range that holds both ends might free";
550
551/// Recorded for a derivation check naming a capability that is not the one it is about.
552const NOT_ITS_CAPABILITY_DERIV: &str = "derivation check left alone, the capability it names is not the one the pointer that went in \
553 carries";
554
555/// Recorded for a derivation check whose two ends are not off one value.
556const TWO_BASES_DERIV: &str =
557 "derivation check left alone, its two pointers are not built on one base";
558
559/// Recorded for a derivation check whose walk can reach past the end of the local it starts in.
560const OVER_THE_LOCAL_DERIV: &str =
561 "derivation check left alone, the walk can reach past the end of the local it starts in";
562
563/// Recorded for a derivation check on a pointer this function loaded out of memory.
564const NO_EXTENT_LOADED: &str = "derivation check left alone, nothing here says how big the object \
565 is and the pointer to it was loaded from memory";
566
567/// Recorded for a derivation check on a pointer this function was handed.
568const NO_EXTENT_HANDED: &str = "derivation check left alone, nothing here says how big the object \
569 is and the pointer to it was handed to this function";
570
571/// Recorded for a derivation check on a pointer into a global.
572const NO_EXTENT_GLOBAL: &str = "derivation check left alone, nothing here says how big the object \
573 is and the pointer to it is into a global";
574
575/// Recorded for a derivation check on a pointer a call handed back.
576const NO_EXTENT_RETURNED: &str = "derivation check left alone, nothing here says how big the \
577 object is and the pointer to it came back from a call";
578
579/// Recorded for a derivation check on a pointer none of the shapes above describes.
580const NO_EXTENT_OTHER: &str =
581 "derivation check left alone, nothing here says how big the object its pointers are in is";
582
583/// The pass. It holds nothing, because everything it works out is about one function.
584/// Which of the places a fact comes from a run of this pass may ask.
585///
586/// Everything is asked normally and there is one pass in the pipeline. The others are here for the
587/// measurement `spec/safe-memory/13-performance.md` section 13.5 asks for and
588/// `spec/safe-memory/17-open-questions.md` question 3 is: how much each source discharges on its
589/// own, and how much the same sources discharge together. A number for a source on its own cannot
590/// be read off the remarks of a full run, because the rules are asked in an order and whichever one
591/// answers first is the one the remark names, so the second source to be asked about a check two of
592/// them could answer looks like it answered nothing.
593///
594/// The four are document 07 section 7.2's four, with the caveat the measurement found: the ranges
595/// are not a fourth kind of fact but a way of asking the other three about a subscript instead of
596/// about an address written out in the program.
597#[derive(Clone, Copy, PartialEq, Eq, Debug)]
598pub struct Sources {
599 /// How big an object is, read off whatever made it. A global's extent comes from
600 /// `crate::extents`, a local's from its `alloca`, an allocation's from the call `crate::heap`
601 /// marked. Section 7.2's first source.
602 objects: bool,
603 /// What a check that has already run established, carried down the dominator tree. Section 7.3,
604 /// and the one the literature calls redundant check elimination.
605 dominance: bool,
606 /// What every caller of this function guarantees about what it was handed, from
607 /// `crate::params`. Section 7.5.
608 summaries: bool,
609 /// The value ranges and the recurrences, which widen the one address a check names into the
610 /// range of addresses a walk can reach so that the other three can be asked about a subscript.
611 /// Section 7.4, and the half of the PICO result this pass holds. The other half is
612 /// [`crate::hoist`] and [`crate::split`], which are passes of their own and have flags of their
613 /// own.
614 ranges: bool,
615}
616
617impl Sources {
618 /// Every one of them, which is what the pipeline runs.
619 pub const ALL: Self = Self { objects: true, dominance: true, summaries: true, ranges: true };
620 /// What an object says about itself and nothing else.
621 pub const OBJECTS: Self =
622 Self { objects: true, dominance: false, summaries: false, ranges: false };
623 /// What an earlier check established and nothing else.
624 pub const DOMINANCE: Self =
625 Self { objects: false, dominance: true, summaries: false, ranges: false };
626 /// What every caller guarantees and nothing else.
627 pub const SUMMARIES: Self =
628 Self { objects: false, dominance: false, summaries: true, ranges: false };
629 /// Every fact, asked only about addresses written out in the program.
630 pub const NARROW: Self =
631 Self { objects: true, dominance: true, summaries: true, ranges: false };
632}
633
634#[derive(Debug, Clone, Copy, PartialEq, Eq)]
635pub struct Discharge {
636 /// What `-f<name>` and `-fno-<name>` reach this run by.
637 name: &'static str,
638 /// Which places it may take a fact from. See [`Sources`].
639 sources: Sources,
640}
641
642/// The pass the pipeline runs, which asks everything.
643pub static DISCHARGE: Discharge = Discharge { name: "discharge", sources: Sources::ALL };
644
645/// The same pass asking an object how big it is and nothing else.
646pub static OBJECTS: Discharge = Discharge { name: "discharge-objects", sources: Sources::OBJECTS };
647
648/// The same pass asking what an earlier check established and nothing else.
649pub static DOMINANCE: Discharge =
650 Discharge { name: "discharge-dominance", sources: Sources::DOMINANCE };
651
652/// The same pass asking what every caller guarantees and nothing else.
653pub static SUMMARIES: Discharge =
654 Discharge { name: "discharge-summaries", sources: Sources::SUMMARIES };
655
656/// The same pass asking every fact, about addresses written out in the program only.
657pub static NARROW: Discharge = Discharge { name: "discharge-narrow", sources: Sources::NARROW };
658
659/// The same pass asking everything, under a name of its own.
660///
661/// [`DISCHARGE`] already asks everything, so this looks like a duplicate and is not. A pass the level
662/// did not choose goes on the end of the pipeline, so a run of `-fno-discharge -fdischarge-objects`
663/// asks its question in a different place from where the shipped pass asks it, and the two numbers
664/// are not comparable. This one is turned on the same way as the others and lands in the same place,
665/// so the sum of the parts and the whole are measured under one arrangement. What it costs against
666/// [`DISCHARGE`] is what the position is worth, which is a number the measurement wants anyway.
667pub static EVERY: Discharge = Discharge { name: "discharge-every", sources: Sources::ALL };
668
669impl Pass for Discharge {
670 fn name(&self) -> &'static str {
671 self.name
672 }
673
674 fn describe(&self) -> &'static str {
675 "a bounds, lifetime or derivation check whose answer is already known is removed"
676 }
677
678 fn preserves(&self) -> Preserved {
679 // Instructions go and blocks do not. A check is not a terminator and removing one leaves
680 // every edge where it was. What it does not leave where it was is the liveness, because
681 // the check was reading something and now nothing is.
682 Preserved::ALL.without(Analysis::Liveness)
683 }
684
685 fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
686 let mut stats = Stats::new();
687 let Some(entry) = func.entry() else { return stats };
688 let dom = an.dominators(func);
689
690 // The graph is built for two reasons and neither is the common one, so a function with
691 // neither pays for no copy of it. The ranges want it when there is a walk the constant
692 // reader gives up on, and the allocation rule wants it to find where the program has tested
693 // what an allocator gave it.
694 let walks = self.sources.ranges && walks_by_a_value(func);
695 let cfg = (walks
696 || joins_a_pointer(func, entry)
697 || (self.sources.objects && heap::allocates(func)))
698 .then(|| an.cfg(func));
699 let mut ranges = cfg.filter(|_| walks).map(|cfg| Ranges::new(&*func, cfg, dom));
700
701 // One answer per allocation rather than one per check, because a function that reads twenty
702 // fields of the same object asks the same question about the same pointer twenty times.
703 let mut checked: HashMap<Value, HashSet<Block>> = HashMap::new();
704
705 // Whether anything in here says a lifetime is over. Read once over the whole function
706 // rather than carried down the walk, because what the frame slot rule needs is that no
707 // `meta_end` runs before the check on any path, and a fact carried down the dominator
708 // tree only ever says something about the paths that go through one block.
709 let ends = ends_a_lifetime(func);
710
711 // The walk is a stack rather than recursion because the dominator tree of a long chain of
712 // blocks is as deep as the function is long, and a pass is not a place to find that out.
713 // Each block carries its own copy of what holds at its start, which is what makes a fact a
714 // call killed in one arm of a branch still hold in the other.
715 let mut going: Vec<(Inst, &'static str)> = Vec::new();
716 let mut work = vec![(entry, Scope::default())];
717 while let Some((block, mut scope)) = work.pop() {
718 for inst in func.insts(block).collect::<Vec<Inst>>() {
719 match opaque(func, inst) {
720 Some(Opaque::Called) => {
721 scope.called();
722 continue;
723 }
724 Some(Opaque::Everything) => {
725 scope.forget();
726 continue;
727 }
728 None => {}
729 }
730 match func[inst].opcode {
731 Opcode::CheckBounds => {
732 if func[func[inst].args].len() > 2 {
733 stats.missed(COMPUTED_EXTENT);
734 scope.proved(func, inst);
735 continue;
736 }
737 let Some(asked) = about(func, inst) else {
738 // The constant reader could not name the address, so the rules that
739 // take one address never run. The range reader can, past the step the
740 // other one stopped at, and only when the base it lands on is the
741 // pointer the capability names.
742 let mid = midway(func, inst);
743 let size = match func[inst].extra {
744 Extra::Mem(info) => i128::from(func[info].size),
745 _ => 0,
746 };
747 let span =
748 mid.then(|| beyond(func, ranges.as_mut(), inst, size)).flatten();
749 let wide = span
750 .filter(|wide| {
751 (self.sources.objects
752 && declared(func, wide.base)
753 .is_some_and(|local| reaches(&local, wide)))
754 || (self.sources.objects
755 && allocated_around(
756 func,
757 cfg,
758 &mut checked,
759 block,
760 &[wide],
761 ))
762 || (self.sources.dominance && scope.bounds.reaches(wide))
763 })
764 .filter(|_| match aligned(func, cfg, &scope.aligns, inst) {
765 Alignment::Answered => true,
766 Alignment::Unknown => {
767 stats.missed(UNKNOWN_ALIGNMENT);
768 false
769 }
770 Alignment::Lost => {
771 stats.missed(LOST_ALIGNMENT);
772 false
773 }
774 });
775 if wide.is_some() {
776 if fuel.take() {
777 going.push((inst, REMOVED_MIDWAY));
778 } else {
779 stats.missed(NO_FUEL);
780 scope.proved(func, inst);
781 }
782 continue;
783 }
784 stats.missed(if mid {
785 why_midway(func, &scope.bounds, span, false)
786 } else {
787 UNKNOWN_SHAPE
788 });
789 scope.proved(func, inst);
790 continue;
791 };
792 // The four objects whose extent is known without anybody having checked
793 // it. A global was worked out over the module by `crate::extents` and an
794 // object every caller hands in by `crate::params`, both of which arrive as
795 // a flag; a local is read off its `alloca` here and an allocation off the
796 // call `crate::heap` marked. All four are asked of the same rule as every
797 // other fact. The reach of a walk the constant reader could not finish is
798 // asked last, because it is the only one that costs an analysis to answer.
799 let why = if self.sources.objects
800 && func[inst].flags.contains(Flags::STATIC)
801 {
802 Some(REMOVED_STATIC)
803 } else if self.sources.summaries && func[inst].flags.contains(Flags::HANDED)
804 {
805 Some(REMOVED_HANDED)
806 } else if self.sources.objects
807 && declared(func, asked.base)
808 .is_some_and(|local| covers(&local, &asked))
809 {
810 Some(REMOVED_LOCAL)
811 } else if self.sources.objects
812 && allocated(func, cfg, &mut checked, block, &[&asked])
813 {
814 Some(REMOVED_MADE)
815 } else if self.sources.dominance && scope.bounds.covers(&asked) {
816 Some(REMOVED)
817 } else {
818 // The same four sources in the same order, asked of the range of
819 // addresses the walk can reach rather than of the one address the
820 // constant reader could name. A flag has already been read above and
821 // reading it again would say the same thing, so what is left is the
822 // local, the allocation and what the walk carries.
823 reach(func, ranges.as_mut(), &asked, inst).and_then(|wide| {
824 if self.sources.objects
825 && declared(func, wide.base)
826 .is_some_and(|local| reaches(&local, &wide))
827 {
828 Some(REMOVED_RANGE)
829 } else if self.sources.objects
830 && allocated_around(func, cfg, &mut checked, block, &[&wide])
831 {
832 Some(REMOVED_MADE)
833 } else if self.sources.dominance && scope.bounds.reaches(&wide) {
834 Some(REMOVED_RANGE)
835 } else {
836 None
837 }
838 })
839 };
840 // Asked once a rule has answered the bounds rather than in front of them
841 // all, because a check that was staying anyway costs the gate nothing and
842 // the number somebody reads has to be what it actually costs. A check kept
843 // here still runs, so it still establishes what it was about.
844 let why = why.filter(|_| match aligned(func, cfg, &scope.aligns, inst) {
845 Alignment::Answered => true,
846 Alignment::Unknown => {
847 stats.missed(UNKNOWN_ALIGNMENT);
848 false
849 }
850 Alignment::Lost => {
851 stats.missed(LOST_ALIGNMENT);
852 false
853 }
854 });
855 let Some(why) = why else {
856 if scope.bounds.covered_before(&asked) {
857 stats.missed(PAST_A_CALL);
858 }
859 // A check that stays is a check that runs, and a check that runs
860 // establishes what it was about. One that was removed establishes
861 // nothing new: whatever covered it covers everything it would have.
862 // Both halves of what it was about, since the alignment conjunct rides
863 // on this check and the gate above may well be the thing that kept it.
864 scope.bounds.held.push(asked);
865 scope.proved(func, inst);
866 continue;
867 };
868 if !fuel.take() {
869 stats.missed(NO_FUEL);
870 scope.bounds.held.push(asked);
871 scope.proved(func, inst);
872 continue;
873 }
874 // A check that goes normally establishes nothing new, because whatever
875 // answered it covers everything it would have. The range is the one
876 // exception: what answered it was a fact about a made up range around the
877 // address, and the next check on these bytes has to ask for that range
878 // again and may not get the same answer. So the narrow fact goes in, which
879 // is the thing that was actually proved.
880 if why == REMOVED_RANGE {
881 scope.bounds.held.push(asked);
882 }
883 going.push((inst, why));
884 }
885 Opcode::CheckLive => {
886 let Some(asked) = alive(func, inst) else {
887 // The bounds arm's paragraph, and the same two rules this arm already
888 // asks of a range, which is a local that holds everything the walk can
889 // reach and a lifetime fact that does.
890 let mid = midway(func, inst);
891 let span =
892 mid.then(|| beyond(func, ranges.as_mut(), inst, 1)).flatten();
893 let wide = span.filter(|wide| {
894 (self.sources.objects
895 && !ends
896 && declared(func, wide.base)
897 .is_some_and(|local| reaches(&local, wide)))
898 || (self.sources.dominance && scope.alive.reaches(wide))
899 });
900 if wide.is_some() {
901 if fuel.take() {
902 going.push((inst, REMOVED_MIDWAY_LIVE));
903 } else {
904 stats.missed(NO_FUEL_LIVE);
905 }
906 continue;
907 }
908 stats.missed(if mid {
909 why_midway(func, &scope.alive, span, true)
910 } else {
911 UNKNOWN_SHAPE_LIVE
912 });
913 continue;
914 };
915 // A global is alive as long as the program is, and a frame slot is alive
916 // until the function returns, so both objects whose extent is known
917 // without anybody having checked it answer this as well as a bounds
918 // check. `ends` is what makes the second one true: where a local stops
919 // being alive is written into the IR as `meta_end` and not read off the
920 // shape of the source, so a function with one in it is a function this
921 // does not claim anything about.
922 let why = if self.sources.objects
923 && func[inst].flags.contains(Flags::STATIC)
924 {
925 Some(REMOVED_LIVE_STATIC)
926 } else if self.sources.summaries && func[inst].flags.contains(Flags::HANDED)
927 {
928 Some(REMOVED_LIVE_HANDED)
929 } else if self.sources.objects
930 && !ends
931 && declared(func, asked.base)
932 .is_some_and(|local| covers(&local, &asked))
933 {
934 Some(REMOVED_LIVE_LOCAL)
935 } else if self.sources.dominance && scope.alive.covers(&asked) {
936 Some(REMOVED_LIVE)
937 } else {
938 // A lifetime fact and not a bounds one, because what is being asked
939 // is whether the storage is alive and a bounds check that passed says
940 // nothing about that. The widening argument is the bounds arm's: a
941 // range known alive that holds every address the walk can reach holds
942 // the one it actually uses.
943 reach(func, ranges.as_mut(), &asked, inst)
944 .filter(|wide| {
945 (self.sources.objects
946 && !ends
947 && declared(func, wide.base)
948 .is_some_and(|local| reaches(&local, wide)))
949 || (self.sources.dominance && scope.alive.reaches(wide))
950 })
951 .map(|_| REMOVED_LIVE_RANGE)
952 };
953 let Some(why) = why else {
954 if scope.alive.covered_before(&asked) {
955 stats.missed(PAST_A_CALL_LIVE);
956 }
957 scope.alive.held.push(widened(func, &scope.bounds, asked));
958 continue;
959 };
960 if !fuel.take() {
961 stats.missed(NO_FUEL_LIVE);
962 scope.alive.held.push(widened(func, &scope.bounds, asked));
963 continue;
964 }
965 // The bounds arm's exception, for its reason. A range answered a made up
966 // range around this address, so what was proved is about the address.
967 if why == REMOVED_LIVE_RANGE {
968 scope.alive.held.push(widened(func, &scope.bounds, asked));
969 }
970 going.push((inst, why));
971 }
972 Opcode::CheckDeriv => {
973 let narrow = derives(func, inst);
974 let why = narrow.and_then(|(from, to)| {
975 if self.sources.objects && func[inst].flags.contains(Flags::STATIC) {
976 Some(REMOVED_DERIV_STATIC)
977 } else if self.sources.summaries
978 && func[inst].flags.contains(Flags::HANDED)
979 {
980 Some(REMOVED_DERIV_HANDED)
981 } else if self.sources.objects
982 && declared(func, from.base).is_some_and(|local| {
983 covers(&local, &from) && covers(&local, &to)
984 })
985 {
986 Some(REMOVED_DERIV_LOCAL)
987 } else if self.sources.objects
988 && allocated(func, cfg, &mut checked, block, &[&from, &to])
989 {
990 Some(REMOVED_DERIV_MADE)
991 } else if self.sources.dominance && scope.bounds.holds_both(&from, &to)
992 {
993 Some(REMOVED_DERIV)
994 } else {
995 None
996 }
997 });
998 // Asked last, and asked off the check's own operands rather than off what
999 // `derives` worked out, because the case it is for is the one `derives`
1000 // cannot read at all: past a step the constant reader gives up on the two
1001 // ends are not one base and two constants. One thing has to hold both of
1002 // the ranges, for the same reason one thing has to hold both of the
1003 // addresses, which is that two things saying each end is inside something
1004 // say nothing about it being the same something.
1005 let why = why.or_else(|| {
1006 spread(func, ranges.as_mut(), inst, inst).and_then(|(near, far)| {
1007 if self.sources.objects
1008 && declared(func, near.base).is_some_and(|local| {
1009 reaches(&local, &near) && reaches(&local, &far)
1010 })
1011 {
1012 Some(REMOVED_DERIV_RANGE)
1013 } else if self.sources.objects
1014 && allocated_around(
1015 func,
1016 cfg,
1017 &mut checked,
1018 block,
1019 &[&near, &far],
1020 )
1021 {
1022 Some(REMOVED_DERIV_MADE)
1023 } else if self.sources.dominance
1024 && scope.bounds.reaches_both(&near, &far)
1025 {
1026 Some(REMOVED_DERIV_RANGE)
1027 } else {
1028 None
1029 }
1030 })
1031 });
1032 let Some(why) = why else {
1033 match narrow {
1034 Some((from, to)) => {
1035 if scope.bounds.held_both_before(&from, &to) {
1036 stats.missed(PAST_A_CALL_DERIV);
1037 }
1038 }
1039 None => {
1040 stats.missed(unreadable(func, ranges.as_mut(), inst));
1041 }
1042 }
1043 continue;
1044 };
1045 if !fuel.take() {
1046 stats.missed(NO_FUEL_DERIV);
1047 continue;
1048 }
1049 going.push((inst, why));
1050 }
1051 Opcode::CheckInit => {
1052 let Some(asked) = about(func, inst) else {
1053 stats.missed(UNKNOWN_SHAPE_INIT);
1054 continue;
1055 };
1056 if !(self.sources.dominance && scope.written.covers(&asked)) {
1057 stats.missed(if scope.written.covered_before(&asked) {
1058 PAST_A_CALL_INIT
1059 } else {
1060 NOTHING_WROTE_IT
1061 });
1062 scope.written.held.push(asked);
1063 continue;
1064 }
1065 if !fuel.take() {
1066 stats.missed(NO_FUEL_INIT);
1067 scope.written.held.push(asked);
1068 continue;
1069 }
1070 going.push((inst, REMOVED_INIT));
1071 }
1072 Opcode::CheckType => {
1073 let Some((node, asked)) = holding(func, inst) else {
1074 stats.missed(UNKNOWN_SHAPE_TYPE);
1075 continue;
1076 };
1077 let held = scope.typed.entry(node).or_default();
1078 if !(self.sources.dominance && held.covers(&asked)) {
1079 stats.missed(if held.covered_before(&asked) {
1080 PAST_A_CALL_TYPE
1081 } else {
1082 NOTHING_TYPED_IT
1083 });
1084 held.held.push(asked);
1085 continue;
1086 }
1087 if !fuel.take() {
1088 stats.missed(NO_FUEL_TYPE);
1089 held.held.push(asked);
1090 continue;
1091 }
1092 going.push((inst, REMOVED_TYPE));
1093 }
1094 // The two ways bytes that were written stop counting as written without a call
1095 // being involved. A `meta_begin` is a lifetime starting, which is the storage
1096 // becoming fresh again, and a `meta_init_copy` carries whatever the source said
1097 // about its own bytes, which for an uninitialized source is that the
1098 // destination is uninitialized too. Neither says anything about bounds or about
1099 // lifetime, so neither goes through `opaque`.
1100 Opcode::MetaBegin | Opcode::MetaInitCopy => {
1101 scope.written.forget();
1102 // Only the first of the two touches the type plane. A lifetime starting is
1103 // storage nobody has stored through yet, which holds no type, and a copy of
1104 // the init plane moves init entries and nothing else.
1105 if func[inst].opcode == Opcode::MetaBegin {
1106 scope.retyped(None);
1107 }
1108 }
1109 // The type plane's copy, which carries whatever the source said and this pass
1110 // has no idea what that was. It says nothing about any other plane.
1111 Opcode::MetaTypeCopy => {
1112 scope.retyped(None);
1113 }
1114 // A store's judgement, which is the one plane write that names a type.
1115 // `Scope::retyped` is the argument for keeping its own entry's facts.
1116 Opcode::MetaType => {
1117 let node = match func[inst].extra {
1118 Extra::Node(node) => Some(node),
1119 _ => None,
1120 };
1121 scope.retyped(node);
1122 }
1123 _ => continue,
1124 }
1125 }
1126 for child in dom.children(block) {
1127 work.push((child, scope.clone()));
1128 }
1129 }
1130
1131 for (inst, why) in going {
1132 func.remove_inst(inst);
1133 stats.optimized(why);
1134 }
1135 stats
1136 }
1137}
1138
1139/// A range of bytes some check has already been passed on, or is being asked about.
1140///
1141/// The address is kept as the value it was computed from and the constant distance from it, rather
1142/// than as the pointer itself, because that is what makes two of these comparable: the whole of
1143/// what this pass knows about two addresses is that they are one value plus two constants.
1144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1145pub(crate) struct Fact {
1146 /// The value the address was computed from.
1147 pub(crate) base: Value,
1148 /// How far past it the access starts.
1149 pub(crate) offset: i128,
1150 /// How many bytes it covers.
1151 size: i128,
1152}
1153
1154impl Fact {
1155 /// The whole of an object whose extent is known, starting at its own address.
1156 ///
1157 /// The two sources of one of these are an `alloca` of a fixed size and a global, and what they
1158 /// have in common is that the size is said by something other than a check that passed.
1159 pub(crate) fn whole(base: Value, size: i128) -> Self {
1160 Self { base, offset: 0, size }
1161 }
1162
1163 /// A range of bytes named by where it starts and how far it runs.
1164 ///
1165 /// The general form of [`Fact::whole`], for a caller that has both ends of a range in hand
1166 /// rather than an object. `crate::dead_plane` is the one, and what it has is a plane write
1167 /// rather than an access, which is a different thing to be about and the same thing to ask.
1168 pub(crate) fn range(base: Value, offset: i128, size: i128) -> Self {
1169 Self { base, offset, size }
1170 }
1171}
1172
1173/// A range of addresses an access can land in, and how many bytes it takes when it does.
1174///
1175/// What [`reach`] works out and the only thing it is used for. It is deliberately not a [`Fact`]:
1176/// a fact is something that was established and may be recorded, and this is a question and may
1177/// not. The address the program uses is `base` plus somewhere between `low` and `low` plus `width`
1178/// further along, and what a check proves when it runs is about that one address rather than about
1179/// the range this was made out of.
1180#[derive(Debug, Clone, Copy)]
1181struct Reach {
1182 /// The value the address was computed from.
1183 base: Value,
1184 /// The nearest the access can start to it.
1185 low: i128,
1186 /// How much further than that it can start.
1187 width: i128,
1188 /// How many bytes it covers.
1189 size: i128,
1190}
1191
1192/// One kind of fact, and what has become of it.
1193#[derive(Debug, Clone, Default)]
1194struct Known {
1195 /// The ranges a check has been passed on and nothing has cast doubt on since.
1196 held: Vec<Fact>,
1197 /// The ones a call threw away, kept only so that the cost of throwing them away is a number
1198 /// somebody can read rather than a paragraph somebody has to believe.
1199 lost: Vec<Fact>,
1200 /// Where in [`Known::held`] the facts established since the last call begin.
1201 ///
1202 /// Everything in front of that index is a fact that was true when it was established and has
1203 /// had a call run over it since. For the bounds half those facts still answer a bounds check,
1204 /// which is what [`Known::crossed`] is about, and there are two questions they may not answer.
1205 /// An index rather than a flag on each fact because nothing ever takes one out of the middle:
1206 /// the vector is pushed and emptied and never anything else, so the ones from before the call
1207 /// are exactly the ones in front of a mark.
1208 since: usize,
1209}
1210
1211impl Known {
1212 /// Whether something still standing answers this.
1213 fn covers(&self, asked: &Fact) -> bool {
1214 self.held.iter().any(|fact| covers(fact, asked))
1215 }
1216
1217 /// Whether something still standing answers a range of addresses an access can land in.
1218 fn reaches(&self, asked: &Reach) -> bool {
1219 self.held.iter().any(|fact| reaches(fact, asked))
1220 }
1221
1222 /// The facts no call has run over, which is the only kind two of the rules may read.
1223 fn fresh(&self) -> &[Fact] {
1224 let from = self.since.min(self.held.len());
1225 &self.held[from..]
1226 }
1227
1228 /// The facts a call has run over, which is what the cost of not reading them is counted from.
1229 fn stale(&self) -> impl Iterator<Item = &Fact> {
1230 let upto = self.since.min(self.held.len());
1231 self.held[..upto].iter().chain(self.lost.iter())
1232 }
1233
1234 /// Whether one thing still standing answers both of these ranges.
1235 ///
1236 /// One rather than one each, for the reason [`Known::holds_both`] gives, and the reason does
1237 /// not change when the ends are ranges instead of addresses.
1238 fn reaches_both(&self, from: &Reach, to: &Reach) -> bool {
1239 self.fresh().iter().any(|fact| reaches(fact, from) && reaches(fact, to))
1240 }
1241
1242 /// Whether something would have answered it before a call came along.
1243 fn covered_before(&self, asked: &Fact) -> bool {
1244 self.stale().any(|fact| covers(fact, asked))
1245 }
1246
1247 /// Whether one thing still standing answers both of these.
1248 ///
1249 /// One rather than one each, which is the whole point of asking it this way. Two facts saying
1250 /// two addresses are each inside some instance say nothing about whether it is the same
1251 /// instance, and that is the only thing a derivation check wants to know.
1252 fn holds_both(&self, from: &Fact, to: &Fact) -> bool {
1253 self.fresh().iter().any(|fact| covers(fact, from) && covers(fact, to))
1254 }
1255
1256 /// Whether one would have answered both before a call came along.
1257 fn held_both_before(&self, from: &Fact, to: &Fact) -> bool {
1258 self.stale().any(|fact| covers(fact, from) && covers(fact, to))
1259 }
1260
1261 /// Gives up everything, because something happened that this pass cannot see through.
1262 fn forget(&mut self) {
1263 self.lost.append(&mut self.held);
1264 self.since = 0;
1265 }
1266
1267 /// Keeps everything and marks it as having had a call run over it.
1268 ///
1269 /// The bounds half only, and the module comment's section on what keeping it takes is the whole
1270 /// argument for why that is allowed. In one line: the lifetime check beside the access is still
1271 /// there, it compares the version the capability carries against the plane's, and a range that
1272 /// was inside one instance is inside that instance still or is about to be refused.
1273 fn crossed(&mut self) {
1274 self.since = self.held.len();
1275 }
1276}
1277
1278/// What holds where the walk has got to.
1279///
1280/// The two kinds are apart because they are killed together and answered separately: a range being
1281/// inside one instance and that instance being alive are different claims, and reporting them as
1282/// one number would hide which of the two a check is still being paid for.
1283#[derive(Debug, Clone, Default)]
1284struct Scope {
1285 /// Ranges a `check_bounds` established are inside one storage instance.
1286 bounds: Known,
1287 /// Ranges a `check_live` established are in an instance that is alive.
1288 alive: Known,
1289 /// Ranges a `check_init` established have had every byte written.
1290 ///
1291 /// Kept apart from the other two for the reason those two are kept apart from each other. A
1292 /// range being inside one instance, that instance being alive, and the bytes in it having been
1293 /// written are three claims, and a check that passes establishes exactly one of them.
1294 written: Known,
1295 /// Ranges a `check_type` established agree with a type, one set of ranges per plane entry.
1296 ///
1297 /// A fact here is not quite what the check is named after. What a `check_type` that passes
1298 /// establishes is that the plane over those bytes is compatible with the entry it asked with,
1299 /// which is the plane holding that entry or the plane holding the untyped one, and this pass
1300 /// cannot tell which. That is enough to answer a later check asking with the same entry and it
1301 /// is not enough to answer one asking with any other, so the entry is the key rather than a
1302 /// field, and a lookup that misses is the honest answer for every other type.
1303 typed: HashMap<Meta, Known>,
1304 /// What a `check_bounds` that ran proved about where the address it was about starts.
1305 ///
1306 /// Nothing here is ever given up, and that is the difference between this and the other two.
1307 /// They are facts about storage, and storage can be freed and handed back out, which is the
1308 /// whole of what a call does to them. This is a fact about the number a value holds, and
1309 /// nothing in a function changes the number an SSA value holds. So it survives a call, it
1310 /// survives inline assembly and it survives a `meta_end`, and dominance is the only thing that
1311 /// bounds it, which the walk already handles by giving each child its own copy.
1312 aligns: HashMap<Value, u64>,
1313}
1314
1315impl Scope {
1316 /// Gives up every fact of either kind. The alignment facts are not one of the two, and
1317 /// [`Scope::aligns`] says why they are not given up here or anywhere else.
1318 fn forget(&mut self) {
1319 self.bounds.forget();
1320 self.alive.forget();
1321 self.written.forget();
1322 self.retyped(None);
1323 }
1324
1325 /// Gives up the type facts that a plane write over an unknown range has made unsafe to keep.
1326 ///
1327 /// A `meta_type` naming entry `E` puts `E` over the bytes it covers and leaves every other byte
1328 /// where it was, so a range this pass recorded as agreeing with `E` agrees with `E` still,
1329 /// wherever the write landed. No such argument holds for any other entry, and where the write
1330 /// landed is the thing this pass does not know, so every other entry's facts go. A caller with
1331 /// no entry to spare, which is a copy or a lifetime starting or anything opaque, passes `None`
1332 /// and loses the lot.
1333 fn retyped(&mut self, kept: Option<Meta>) {
1334 for (&node, facts) in &mut self.typed {
1335 if Some(node) != kept {
1336 facts.forget();
1337 }
1338 }
1339 }
1340
1341 /// Records what a `check_bounds` that is staying proves about where its address starts.
1342 ///
1343 /// The check runs the alignment conjunct, which is `addr & (align - 1) != 0` in the runtime's
1344 /// `bounds`, and refuses when it does not hold. So on any path past the check the address is a
1345 /// multiple of what the access assumed, and the first access through a pointer somebody handed
1346 /// in proves for nothing what every later access through the same value needs.
1347 ///
1348 /// Only for a check that stays. One that is removed does not run and proves nothing, and it
1349 /// needs nothing either, since [`aligned`] answered it before it was allowed to go.
1350 fn proved(&mut self, func: &Func, check: Inst) {
1351 let Extra::Mem(info) = func[check].extra else { return };
1352 let claim = u64::from(func[info].align);
1353 let Some(&pointer) = func[func[check].args].get(1) else { return };
1354 if claim > 1 {
1355 let held = self.aligns.entry(pointer).or_default();
1356 *held = (*held).max(claim);
1357 }
1358 }
1359
1360 /// Gives up the lifetime facts and the initialization ones and the type ones, and keeps the
1361 /// bounds ones, marked as a call having run over them.
1362 ///
1363 /// The initialization facts and the type facts go with the lifetime ones rather than with the
1364 /// bounds. There is a version of the bounds argument that would keep them, since storage handed
1365 /// back out and handed over again comes with a capability whose version no longer matches and
1366 /// the lifetime check beside the access is what notices, but it rests on that check still being
1367 /// there, which is true only because this pass gives up the lifetime facts at the same point.
1368 /// Resting one rule on another rule's conservatism is worth a measurement before it is worth
1369 /// writing. tamnd/rucc#1617.
1370 fn called(&mut self) {
1371 self.bounds.crossed();
1372 self.alive.forget();
1373 self.written.forget();
1374 self.retyped(None);
1375 }
1376}
1377
1378/// What an instruction the pass cannot see through does to the facts the walk is carrying.
1379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1380enum Opaque {
1381 /// A call that might free. The lifetime facts go and the bounds facts stay, marked.
1382 Called,
1383 /// Everything else, which gives up both halves.
1384 Everything,
1385}
1386
1387/// Whether this instruction could do something to memory that this pass cannot account for, and
1388/// what that means for what the walk is carrying.
1389///
1390/// A call is most of it, in every spelling, and inline assembly with it. A `tail_call` ends the
1391/// block and there is nothing after it to protect, and it is here anyway so that the reason a fact
1392/// survives is never that the walk did not think of something.
1393///
1394/// A call carrying [`Flags::NOFREE`] reaches nothing that ends a lifetime, so there is nothing for
1395/// it to have done to the bytes an earlier check was passed on. `crate::nofree` is what put the
1396/// flag there and what argues for it.
1397///
1398/// A `meta_end` and a `meta_transfer` end a lifetime by saying so, which is the plainest way for a
1399/// fact to stop being true, and neither is emitted today. Inline assembly is with them rather than
1400/// with the calls, because the argument for keeping the bounds half rests on the lifetime check at
1401/// the access reading a plane the runtime wrote, and a block of assembly is the one thing in the
1402/// IR that can write over a plane without the runtime having been asked.
1403fn opaque(func: &Func, inst: Inst) -> Option<Opaque> {
1404 match func[inst].opcode {
1405 Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => {
1406 (!func[inst].flags.contains(Flags::NOFREE)).then_some(Opaque::Called)
1407 }
1408 Opcode::InlineAsm | Opcode::MetaEnd | Opcode::MetaTransfer => Some(Opaque::Everything),
1409 _ => None,
1410 }
1411}
1412
1413/// What a `check_bounds` is about, when it is one this pass can read.
1414pub(crate) fn about(func: &Func, check: Inst) -> Option<Fact> {
1415 let (base, offset, whole) = addressed(func, check)?;
1416 let Extra::Mem(info) = func[check].extra else { return None };
1417 hull(base, offset, i128::from(func[info].size), whole)
1418}
1419
1420/// What a `check_type` is about, when it is one this pass can read: which plane entry it asks with
1421/// and which bytes it asks about.
1422///
1423/// The entry comes out of the access payload's `tbaa` field, where `rucc_safety::ask` puts it after
1424/// translating the aliasing node the front end named into the plane's vocabulary. So it is a plane
1425/// entry rather than a node in the aliasing tree, and two checks carrying the same one are asking
1426/// the same question.
1427fn holding(func: &Func, check: Inst) -> Option<(Meta, Fact)> {
1428 let Extra::Mem(info) = func[check].extra else { return None };
1429 let node = func[info].tbaa?;
1430 Some((node, about(func, check)?))
1431}
1432
1433/// What a `check_live` is about, when it is one this pass can read.
1434///
1435/// One byte, because that is the whole of what the check says: the instance holding this address
1436/// is alive, and nothing about the address next door. The widening to a range that makes the fact
1437/// useful is `widened`, and it needs a bounds fact to do it.
1438pub(crate) fn alive(func: &Func, check: Inst) -> Option<Fact> {
1439 let (base, offset, whole) = addressed(func, check)?;
1440 hull(base, offset, 1, whole)
1441}
1442
1443/// The address a check is about, as a base and a constant, and whether the capability names the
1444/// base rather than the address.
1445///
1446/// The capability has to be one this pass can tie to the address, which [`its_own`] is, so a check
1447/// that does not have it is not a check this pass has anything to say about.
1448fn addressed(func: &Func, check: Inst) -> Option<(Value, i128, bool)> {
1449 let args = &func[func[check].args];
1450 let &capability = args.first()?;
1451 let &pointer = args.get(1)?;
1452 let (base, offset) = normal(func, pointer);
1453 let named = named_by(func, capability)?;
1454 its_own(named, pointer, base).map(|whole| (base, offset, whole))
1455}
1456
1457/// Whether a check's capability is about the address the check names or about the pointer that
1458/// address was worked out from, and which of the two it is.
1459///
1460/// Both are shapes `rucc-safety` emits. The first is what it used to emit everywhere, a `cap_of` in
1461/// front of each check naming the check's own pointer, and the second is what
1462/// `rucc_safety::origin` emits now, one capability taken where the object came from and shared by
1463/// every address walked off it. A check naming anything else is about some other instance and
1464/// nothing here is entitled to read it.
1465/// Whether a check this pass could not read names a capability the address really did come off.
1466///
1467/// Asked only where [`about`] has already answered nothing, so it is never on the path of a check
1468/// that goes, and it exists to tell one kind of miss from the rest. `rucc_safety::origin` shares one
1469/// capability down a whole derivation chain and [`normal`] stops walking at the first step it cannot
1470/// read, so a chain with a step like `i * 4` in it leaves the capability naming something further
1471/// back than the base and [`its_own`] refuses it. That is a miss something could be done about. A
1472/// capability naming a pointer the address was never walked off is a different instance and there is
1473/// nothing to do about one, so it stays in the row it was already in.
1474///
1475/// The walk here goes through a step of any kind, which is what makes it a different walk from
1476/// [`normal`], and it reads nothing but the chain, so it says the two are related and not by how
1477/// much.
1478fn midway(func: &Func, check: Inst) -> bool {
1479 let args = &func[func[check].args];
1480 let (Some(&capability), Some(&pointer)) = (args.first(), args.get(1)) else { return false };
1481 let Some(named) = named_by(func, capability) else { return false };
1482 let (base, _) = normal(func, pointer);
1483 let mut value = base;
1484 loop {
1485 if value == named {
1486 return true;
1487 }
1488 let Def::Result { inst, .. } = func[value].def else { return false };
1489 if func[inst].opcode != Opcode::PtrAdd {
1490 return false;
1491 }
1492 let Some(&from) = func[func[inst].args].first() else { return false };
1493 value = from;
1494 }
1495}
1496
1497/// Every address a check [`its_own`] refused can land in, when its capability names the far end.
1498///
1499/// The piece [`about`] cannot supply. That one reads an address as a base and a constant, and a
1500/// chain with a step nobody can read has no such reading, so it answers nothing and the rules never
1501/// run. [`spanned`] has no such trouble, because a step nobody can read is exactly what it asks the
1502/// ranges about, and it walks the whole chain rather than stopping at the first one. So the walk is
1503/// carried on from where the constant reader gave up, and what comes back is a range of addresses
1504/// off a base further back.
1505///
1506/// The base it lands on has to be the value the capability names, and that is the whole of what
1507/// makes this sound rather than a widening. A fact answers a range by [`reaches`], which already
1508/// insists on the same base, so what a rule says yes to is that every address this walk can reach is
1509/// inside something already known about that base. The capability naming that base is what says the
1510/// instance the rules are talking about is the instance this check is about.
1511/// Which of the three midway rows a check that got this far belongs in.
1512///
1513/// Told apart by whether anything in the pass has an extent for the base, rather than by whether a
1514/// rule said yes, because a rule saying no covers both "I have never heard of this object" and "I
1515/// have heard of it and the walk leaves it" and those are completely different pieces of work. The
1516/// first is the great majority and it is not fixable by anything in this pass.
1517fn why_midway(func: &Func, known: &Known, wide: Option<Reach>, live: bool) -> &'static str {
1518 let Some(wide) = wide else {
1519 return if live { MIDWAY_CAPABILITY_LIVE } else { MIDWAY_CAPABILITY };
1520 };
1521 let heard =
1522 declared(func, wide.base).is_some() || known.held.iter().any(|fact| fact.base == wide.base);
1523 match (heard, live) {
1524 (false, false) => MIDWAY_NO_EXTENT,
1525 (false, true) => MIDWAY_NO_EXTENT_LIVE,
1526 (true, false) => MIDWAY_OVER,
1527 (true, true) => MIDWAY_OVER_LIVE,
1528 }
1529}
1530
1531fn beyond(func: &Func, ranges: Option<&mut Ranges<'_>>, check: Inst, size: i128) -> Option<Reach> {
1532 let args = &func[func[check].args];
1533 let &capability = args.first()?;
1534 let &pointer = args.get(1)?;
1535 let named = named_by(func, capability)?;
1536 let (base, offset) = normal(func, pointer);
1537 let wide = spanned(func, ranges?, base, offset, size, check, Some(named))?;
1538 (wide.base == named).then_some(wide)
1539}
1540
1541fn its_own(named: Value, pointer: Value, base: Value) -> Option<bool> {
1542 if named == pointer {
1543 return Some(false);
1544 }
1545 (named == base).then_some(true)
1546}
1547
1548/// The bytes a check says belong to one instance.
1549///
1550/// Which is the access and nothing else when the capability was taken at the address, and the
1551/// access together with everything between it and the base when the capability was taken at the
1552/// base. The second is not a widening this pass made up. A capability names the instance its own
1553/// pointer is in, so the base is in that instance by the meaning of the operand, the access is in
1554/// it because that is what the check asks, and an instance is a run of bytes, so everything between
1555/// the two is in it as well.
1556///
1557/// That is what makes reading the second shape sound, and it has to be the fact rather than a note
1558/// on the side, because a fact is the thing both the asking and the recording go through. Asking
1559/// with it means whatever answers holds the base too, so the instance the answer is about is the
1560/// instance the capability names. Recording it after a check that stays is recording what the check
1561/// proves, and it is more than the narrow one, which is the whole reason a capability taken at the
1562/// base is worth having here.
1563fn hull(base: Value, offset: i128, size: i128, whole: bool) -> Option<Fact> {
1564 if !whole {
1565 return Some(Fact { base, offset, size });
1566 }
1567 let low = offset.min(0);
1568 let high = offset.checked_add(size)?.max(1);
1569 Some(Fact { base, offset: low, size: high.checked_sub(low)? })
1570}
1571
1572/// The two ends of a `check_deriv`, each as the single byte at it.
1573///
1574/// A derivation check asks whether the pointer that came out of a `ptr_add` is still in the storage
1575/// instance the pointer that went in belongs to, so both ends have to be readable and both have to
1576/// come out of the same value, which is what makes the two offsets comparable at all. One byte each
1577/// because that is what is being asked about: not a range, but whether an address is in an instance.
1578///
1579/// The capability has to be about the pointer that went in, for the reason [`addressed`] gives. The
1580/// instance the check is about is the one that pointer belongs to, and a check naming some other
1581/// capability is about some other instance.
1582///
1583/// The width operand is not read. It matters to the runtime only for a pointer that walked off the
1584/// near end, where the check passes on the byte a stride further along instead of on the address
1585/// itself, and this pass never gets that far: it discharges nothing it has not put inside a range
1586/// outright.
1587pub(crate) fn derives(func: &Func, check: Inst) -> Option<(Fact, Fact)> {
1588 let args = &func[func[check].args];
1589 let &capability = args.first()?;
1590 let &from = args.get(1)?;
1591 let &to = args.get(2)?;
1592 let (base, start) = normal(func, from);
1593 let named = named_by(func, capability)?;
1594 let whole = its_own(named, from, base)?;
1595 let (walked, end) = normal(func, to);
1596 if base != walked {
1597 return None;
1598 }
1599 // One fact has to hold both ends, so widening the near one to reach the base is what carries
1600 // the base into whatever answers, which is what [`hull`] is for. The far end is left as it is,
1601 // since the one fact that holds the pair holds it.
1602 Some((hull(base, start, 1, whole)?, Fact { base, offset: end, size: 1 }))
1603}
1604
1605/// The object a local is, when the address a check is about was computed from one.
1606///
1607/// This is the fact nobody had to check for, and section 7.2 puts it first of the four sources
1608/// because it is where most of the win is. An `alloca` of a fixed size is one storage instance of
1609/// that many bytes, said by the instruction that makes it rather than by a check that passed, so
1610/// the bytes from its address to that many further along are inside one instance for the same
1611/// reason a passing `check_bounds` says its own range is.
1612///
1613/// Only the fixed size form. The one that takes an operand is a variable length array, and how
1614/// many bytes it is is a value the program works out rather than a number in the payload, where
1615/// the field reads zero.
1616///
1617/// The fact holds everywhere in the function and no call takes it away, which is the other half of
1618/// what makes it worth having. A callee cannot free a frame slot: what it could free is whatever a
1619/// pointer stored in the slot points at, and that is a different instance and a different check.
1620/// So this is asked separately from the facts the walk carries rather than pushed into them, since
1621/// everything in there is thrown away at the first call this pass cannot see through.
1622fn declared(func: &Func, base: Value) -> Option<Fact> {
1623 let Def::Result { inst, .. } = func[base].def else { return None };
1624 if func[inst].opcode != Opcode::Alloca || !func[func[inst].args].is_empty() {
1625 return None;
1626 }
1627 let Extra::Mem(info) = func[inst].extra else { return None };
1628 Some(Fact::whole(base, i128::from(func[info].size)))
1629}
1630
1631/// The alignment an allocator promises, in bytes.
1632///
1633/// C says storage an allocator hands back is aligned for any object with a fundamental alignment,
1634/// which is sixteen bytes on the targets this compiles for. Eight is claimed rather than sixteen
1635/// because the claim has to hold wherever this pass runs and the pass is given a function rather
1636/// than a target. What it costs is an access that assumes more than eight bytes, which is a
1637/// `long double` or a vector, keeping a check it could have lost.
1638const ALLOCATED: u64 = 8;
1639
1640/// How far into an expression [`divides`] reads before it gives up.
1641///
1642/// A subscript is a multiply and a constant and the answer is two steps in. The bound is here
1643/// because the walk is over an expression the program wrote and nothing about an expression stops
1644/// it from being as deep as the source file is long.
1645const DEEP: u32 = 4;
1646
1647/// Whether the address a check is about starts where the access assumes it does.
1648///
1649/// The alignment conjunct of judgement J1 rides on `check_bounds`, which document 06 section 6.3
1650/// settled, so a check that goes takes the test of it with it and something here has to have
1651/// answered it first. An access that assumes nothing about where it starts has nothing to answer,
1652/// and that is what an alignment of one is and what a member of a packed record gets.
1653///
1654/// What answers it is the object the address was computed from and the steps taken from it, which
1655/// is the same ground the bounds question walks. An `alloca` says what it is aligned to and an
1656/// allocator promises [`ALLOCATED`], and each step from there leaves whatever the step itself
1657/// divides by. So `p[i]` on an `int *` out of `malloc` is answered by the four in the subscript's
1658/// own multiply, and `(int *)(p + 1)` is not answered at all, which is row S7 and the whole reason
1659/// this is here.
1660///
1661/// A global is not read here at all. It arrives as [`Flags::ALIGNED`] from `crate::extents`, which
1662/// is given the module this is not, and the flag is the whole of what this asks about one.
1663///
1664/// A pointer whose origin this cannot read is answered by a check that already ran on it, which is
1665/// [`Scope::proved`], or by `!aligned(a)` written on the value, which is the side table
1666/// `crate::params` fills from the call sites. Between them those are the only things that answer a
1667/// block parameter, a pointer loaded out of memory or one handed in. What is left after that is
1668/// zero, which answers nothing and keeps the check, and [`UNKNOWN_ALIGNMENT`] counts them.
1669///
1670/// The two answers given before [`settles`] is asked are not arithmetic and so are not a rule's.
1671/// An access of one byte assumes nothing about where it starts, so there is nothing to prove about
1672/// it, and the flag is a fact `crate::extents` established over the whole module and wrote down.
1673///
1674/// The two ways of saying no are told apart because they are worth different things to a reader.
1675/// [`Alignment::Unknown`] is a value nothing here says anything about and a better fact could take.
1676/// [`Alignment::Lost`] is an address this followed all the way back to an object it knows the
1677/// alignment of, where the steps taken from it landed somewhere the access may not start, and no
1678/// fact answers one of those because a check that stays is what the conjunct is for.
1679fn aligned(func: &Func, cfg: Option<&Cfg>, aligns: &HashMap<Value, u64>, check: Inst) -> Alignment {
1680 let Extra::Mem(info) = func[check].extra else { return Alignment::Unknown };
1681 let claim = u64::from(func[info].align);
1682 if claim <= 1 || func[check].flags.contains(Flags::ALIGNED) {
1683 return Alignment::Answered;
1684 }
1685 let Some(&pointer) = func[func[check].args].get(1) else { return Alignment::Unknown };
1686 let known = settled(func, cfg, aligns, pointer);
1687 if settles(known, claim) {
1688 Alignment::Answered
1689 } else if known == 0 {
1690 Alignment::Unknown
1691 } else {
1692 Alignment::Lost
1693 }
1694}
1695
1696/// What [`aligned`] found out about where an access starts.
1697#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1698enum Alignment {
1699 /// The address starts where the access assumes, so the check may go as far as this conjunct is
1700 /// concerned.
1701 Answered,
1702 /// Nothing here says where the address starts.
1703 Unknown,
1704 /// This knows where the address started and knows the steps took it off, which is row S7.
1705 Lost,
1706}
1707
1708/// Whether an address known to be a multiple of one number meets an access's claim.
1709///
1710/// The companion to [`covers`] for the alignment conjunct, and it decides nothing either. The walk
1711/// in [`settled`] worked out a number the address divides by, and whether that answers the access
1712/// is the rule file's to say. The address is opaque in the question, because nothing here knows
1713/// what it is and the answer is about every address the walk's number holds of.
1714///
1715/// It is worth saying what the rule catches that the comparison it replaced did not. `known` being
1716/// the larger number is not `claim` dividing it, and the two agree only because both are powers of
1717/// two. Every number that gets here is one, for the reason the head in `safety.model` writes out,
1718/// and now that reason is written somewhere a solver reads rather than only somewhere a person
1719/// does.
1720fn settles(known: u64, claim: u64) -> bool {
1721 let mut question = Question::default();
1722 let at = question.opaque();
1723 let at = question.app("value.i64", &[at]);
1724 let known = question.number(i128::from(known));
1725 let known = question.app("iconst.i64", &[known]);
1726 let claim = question.number(i128::from(claim));
1727 let claim = question.app("iconst.i64", &[claim]);
1728 let term = question.app("aligned.i64", &[at, known, claim]);
1729 match safety::TABLE.find(&question, term) {
1730 Some(found) => yes(&safety::TABLE, found.rule),
1731 None => false,
1732 }
1733}
1734
1735/// What a pointer is known to be aligned to, in bytes, or zero when nothing here says.
1736///
1737/// Every number involved is a power of two, so the greatest common divisor of two of them is the
1738/// smaller, which is why the steps are gathered with a `min` and why they start at the largest
1739/// number there is instead of at zero. Zero is the answer and not a step, since an alignment of
1740/// zero is not something an access can assume and a claim is never met by one.
1741///
1742/// `crate::params` calls this on an argument at a call site, with no graph and with `aligns`
1743/// carrying what the round before worked out about the caller's own parameters. Sharing the walk
1744/// is the point of doing it that way: what a fact says about a parameter is then exactly what the
1745/// callee would have worked out for itself if the value had not crossed a boundary.
1746pub(crate) fn settled(
1747 func: &Func,
1748 cfg: Option<&Cfg>,
1749 aligns: &HashMap<Value, u64>,
1750 pointer: Value,
1751) -> u64 {
1752 let mut budget = JOINS;
1753 joined(func, cfg, aligns, pointer, &mut Vec::new(), &mut budget)
1754}
1755
1756/// How many block parameters [`settled`] will walk through before answering nothing.
1757///
1758/// The cut in [`joined`] keeps the walk from going round for ever, and this keeps it from going
1759/// wide for ever. A chain of joins each of which has two predecessors is a walk that doubles at
1760/// every step, and a function with forty of those in a row is not a function worth an answer.
1761const JOINS: u32 = 256;
1762
1763/// [`settled`] with the two things a walk through a join needs, the values it is already inside of
1764/// and what is left of its budget.
1765///
1766/// A block parameter holds whatever its predecessors hand in, so it is aligned to at least the
1767/// least of what those are aligned to. That is arithmetic over values this function already has
1768/// and it needs nothing written on the function, which is why it is here rather than in the side
1769/// table tamnd/rucc#1385 is otherwise about. The side table is read at the top of the loop, beside
1770/// the answers the checks in this function gave.
1771///
1772/// The care it needs is a parameter that reaches itself round a loop, and the cut is that a value
1773/// the walk is already inside of contributes only the steps taken to get back to it. That lands on
1774/// the fixpoint rather than above it, which is worth the sentence because getting it wrong here
1775/// removes a check that should stay. Every step along a path is folded in as a divisor by the
1776/// `min` below, going round the loop a second time applies those same divisors again, and a
1777/// minimum does not move when you take it twice. So the answer after one lap is the answer after
1778/// any number of them, and there is nothing a second lap could lower that the first did not.
1779fn joined(
1780 func: &Func,
1781 cfg: Option<&Cfg>,
1782 aligns: &HashMap<Value, u64>,
1783 pointer: Value,
1784 inside: &mut Vec<Value>,
1785 budget: &mut u32,
1786) -> u64 {
1787 let mut steps = u64::MAX;
1788 let mut value = pointer;
1789 loop {
1790 // Asked in front of the shape, because the point of both is the values the shape gives up
1791 // on: a pointer handed in, a pointer read out of a field, a block parameter. A check that
1792 // ran on one of those is as good an answer as an `alloca`, and `!aligned(a)` is the same
1793 // answer about the same value worked out from outside the function, which is what
1794 // `crate::params` writes and what section 6.2.4 of
1795 // `spec/safe-memory/06-instrumentation.md` has the fact for.
1796 //
1797 // Whichever of the two says more is the one to take. A fact is a promise and never a
1798 // denial, so two promises about one value are both true and the larger is no less true
1799 // than the smaller.
1800 let proved = aligns.get(&value).copied();
1801 let written = func.facts(value).align.map(u64::from);
1802 if let Some(known) = proved.max(written) {
1803 return steps.min(known);
1804 }
1805 let inst = match func[value].def {
1806 Def::Result { inst, .. } => inst,
1807 // A function's parameter with no fact on it. Nothing inside the function says anything
1808 // about one, so this is where the row goes that the side table above is for, and it is
1809 // still the larger half of it.
1810 Def::Param { block, index } => {
1811 let Some(cfg) = cfg else { return 0 };
1812 if func.entry() == Some(block) {
1813 return 0;
1814 }
1815 // The cut, and the only place a walk answers with the steps alone. Everywhere
1816 // else running out of things to look at is nothing known, which is zero.
1817 if inside.contains(&value) {
1818 return steps;
1819 }
1820 if *budget == 0 {
1821 return 0;
1822 }
1823 *budget -= 1;
1824 inside.push(value);
1825 let least = handed(func, cfg, aligns, block, index, inside, budget);
1826 inside.pop();
1827 return steps.min(least);
1828 }
1829 };
1830 match func[inst].opcode {
1831 Opcode::Alloca => {
1832 let Extra::Mem(info) = func[inst].extra else { return 0 };
1833 return steps.min(u64::from(func[info].align));
1834 }
1835 Opcode::Call if func[inst].flags.contains(Flags::HEAP) => {
1836 return steps.min(ALLOCATED);
1837 }
1838 Opcode::PtrAdd => {
1839 let args = &func[func[inst].args];
1840 let (Some(&from), Some(&by)) = (args.first(), args.get(1)) else { return 0 };
1841 steps = steps.min(divides(func, by, DEEP));
1842 value = from;
1843 }
1844 _ => return 0,
1845 }
1846 }
1847}
1848
1849/// The least alignment any predecessor hands to one parameter of a block.
1850///
1851/// Zero for a block nothing reaches and for an edge whose arguments do not run that far, since
1852/// either is a function this does not understand and claiming an alignment for one would be
1853/// claiming it out of nothing. A predecessor whose terminator is missing is the same case.
1854fn handed(
1855 func: &Func,
1856 cfg: &Cfg,
1857 aligns: &HashMap<Value, u64>,
1858 block: Block,
1859 index: u32,
1860 inside: &mut Vec<Value>,
1861 budget: &mut u32,
1862) -> u64 {
1863 let preds = cfg.predecessors(block);
1864 if preds.is_empty() {
1865 return 0;
1866 }
1867 let mut least = u64::MAX;
1868 for &pred in preds {
1869 let Some(term) = func.terminator(pred) else { return 0 };
1870 let Some(&came) = copy::edge_args(func, term, block).get(index as usize) else {
1871 return 0;
1872 };
1873 least = least.min(joined(func, Some(cfg), aligns, came, inside, budget));
1874 if least == 0 {
1875 break;
1876 }
1877 }
1878 least
1879}
1880
1881/// The largest power of two that divides a step, or one when nothing here says.
1882///
1883/// One is the answer for anything unreadable and it is the right one: every number divides by one,
1884/// so a step nobody can read leaves a pointer aligned to a byte and no more. Zero divides by
1885/// everything, which is a walk that took no step and has to leave what it started with alone.
1886fn divides(func: &Func, step: Value, depth: u32) -> u64 {
1887 if let Some(number) = constant(func, step) {
1888 let Ok(size) = u64::try_from(number.unsigned_abs()) else { return 1 };
1889 return if size == 0 { u64::MAX } else { 1 << size.trailing_zeros() };
1890 }
1891 let Def::Result { inst, .. } = func[step].def else { return 1 };
1892 let args = &func[func[inst].args];
1893 let (Some(&left), Some(&right)) = (args.first(), args.get(1)) else { return 1 };
1894 if depth == 0 {
1895 return 1;
1896 }
1897 match func[inst].opcode {
1898 // A subscript, which is an index nobody knows anything about times the element size.
1899 Opcode::Mul => {
1900 divides(func, left, depth - 1).saturating_mul(divides(func, right, depth - 1))
1901 }
1902 Opcode::Shl => match constant(func, right) {
1903 Some(by) if (0..64).contains(&by) => {
1904 divides(func, left, depth - 1).checked_shl(by as u32).unwrap_or(u64::MAX)
1905 }
1906 _ => 1,
1907 },
1908 // Two numbers added divide by whatever they both divide by, which is a field offset added
1909 // to a subscript and is how a member of an array of records comes out.
1910 Opcode::Add | Opcode::Sub => {
1911 divides(func, left, depth - 1).min(divides(func, right, depth - 1))
1912 }
1913 _ => 1,
1914 }
1915}
1916
1917/// The object an allocator made, when the address a check is about was computed from one and this
1918/// function has already found out it is not null.
1919///
1920/// The same shape as [`declared`] one storey up, with a marked call saying the size instead of an
1921/// `alloca` and one more thing to establish. `crate::heap` has the argument for both halves: what a
1922/// call to `malloc` says is an extent and never a lifetime, and it only says it where the program
1923/// has looked, because a null pointer is inside no object and a check on one is a check that is
1924/// meant to fail.
1925///
1926/// Nothing is claimed when the graph was not built, which is a function this found no allocation in
1927/// and so a function where the answer would have been no anyway.
1928fn allocation(
1929 func: &Func,
1930 cfg: Option<&Cfg>,
1931 checked: &mut HashMap<Value, HashSet<Block>>,
1932 block: Block,
1933 base: Value,
1934) -> Option<Fact> {
1935 let whole = heap::made(func, base)?;
1936 let cfg = cfg?;
1937 checked
1938 .entry(whole.base)
1939 .or_insert_with(|| heap::tested(func, cfg, whole.base))
1940 .contains(&block)
1941 .then_some(whole)
1942}
1943
1944/// Whether all of those bytes are inside one object an allocator made.
1945///
1946/// Every part has to be inside, and inside the same object, which is what asking [`covers`] with one
1947/// fact and several does.
1948fn allocated(
1949 func: &Func,
1950 cfg: Option<&Cfg>,
1951 checked: &mut HashMap<Value, HashSet<Block>>,
1952 block: Block,
1953 parts: &[&Fact],
1954) -> bool {
1955 let Some(first) = parts.first() else { return false };
1956 let Some(whole) = allocation(func, cfg, checked, block, first.base) else { return false };
1957 parts.iter().all(|part| covers(&whole, part))
1958}
1959
1960/// Whether every address a walk can reach is inside one object an allocator made.
1961///
1962/// [`allocated`] for the question [`reach`] and [`spread`] ask. The object comes from the same place
1963/// and is believed for the same reason, and what is asked of it is [`reaches`] rather than
1964/// [`covers`], so a walk by a step the ranges put numbers on can be answered by a call that says how
1965/// many bytes it made.
1966///
1967/// The wide path used to ask a local and the facts the walk carries and nothing else, so a program
1968/// that walked into its own `malloc` by an index kept its checks however plainly the size was
1969/// written. That is the first half of tamnd/rucc#880.
1970fn allocated_around(
1971 func: &Func,
1972 cfg: Option<&Cfg>,
1973 checked: &mut HashMap<Value, HashSet<Block>>,
1974 block: Block,
1975 spans: &[&Reach],
1976) -> bool {
1977 let Some(first) = spans.first() else { return false };
1978 let Some(whole) = allocation(func, cfg, checked, block, first.base) else { return false };
1979 spans.iter().all(|span| reaches(&whole, span))
1980}
1981
1982/// A lifetime fact grown from one address to the checked range it sits in.
1983///
1984/// The argument is in the module comment: a `check_bounds` that passed put its whole range inside
1985/// one instance, so the instance this lifetime check found alive is the instance that range is in.
1986/// With no range around the address the fact stays as it came, which is correct and answers only a
1987/// repeat of the very same check.
1988///
1989/// A local is asked about first, because the object it is is the widest range there can be for an
1990/// address computed from it and a wider fact answers more later checks. What that gives is a
1991/// lifetime check anywhere in a local discharging every later one in the same local, up to the
1992/// first call, which is the shape a function that reads several fields of a local struct has.
1993///
1994/// Only the bounds facts no call has run over, which is the third of the three things the module
1995/// comment's section on keeping the bounds half says have to hold. A range that was inside one
1996/// instance before a `free` and an allocation of something smaller at the same address is not
1997/// inside one instance after it, so widening a lifetime check that passed on the new instance by
1998/// that range would claim the whole of the old one is alive. The fact is still good enough to
1999/// answer a bounds check, because the lifetime check beside that access refuses the case, and it is
2000/// not good enough to be the reason a lifetime check goes away.
2001fn widened(func: &Func, bounds: &Known, asked: Fact) -> Fact {
2002 if let Some(local) = declared(func, asked.base).filter(|local| covers(local, &asked)) {
2003 return local;
2004 }
2005 bounds.fresh().iter().find(|fact| covers(fact, &asked)).copied().unwrap_or(asked)
2006}
2007
2008/// The value an address was computed from, and how far past it the address is.
2009///
2010/// A `ptr_add` over a constant is walked through, and anything else is where the answer stops. The
2011/// arithmetic here is exact because it is done in `i128` over offsets that came out of the IR as
2012/// sixty four bit constants, and whether it is small enough to mean anything at sixty four bits is
2013/// the rule's question rather than this function's.
2014pub(crate) fn normal(func: &Func, value: Value) -> (Value, i128) {
2015 let mut base = value;
2016 let mut offset: i128 = 0;
2017 while let Some((from, step)) = walked(func, base) {
2018 let Some(sum) = offset.checked_add(step) else { break };
2019 base = from;
2020 offset = sum;
2021 }
2022 (base, offset)
2023}
2024
2025/// Every address a walk can reach, when a step it takes is a value rather than a constant.
2026///
2027/// This is the third of the four sources section 7.2 lists, and it is the one that needs an
2028/// analysis. [`normal`] stops at the first `ptr_add` whose step it cannot read, and what it hands
2029/// back is a fact about a base nobody knows the size of. Document 10's ranges do know something
2030/// about the step: an index the program has already tested, or one a loop counts, is bounded even
2031/// though it is not constant. So the walk carries on past the step, adding the low end of its
2032/// range to the offset and the width of the range to the size.
2033///
2034/// What comes out is a range of addresses the access can land in, and it is a [`Reach`] rather than
2035/// a [`Fact`] on purpose. Whether an object holding all of that range holds the one address the
2036/// access actually uses is [`reaches`], which asks a rule with the distance left opaque, so one
2037/// answer covers every value the step could take.
2038///
2039/// It is only ever asked with. What this returns must never be recorded as established, and the
2040/// one place it could be is the push in the `check_bounds` arm, which happens only where this
2041/// returned nothing or answered nothing. The reason is that the widened range is not what a check
2042/// proves. A check that runs and passes proves the address the program used was inside the object,
2043/// and says nothing at all about the rest of the range this function made up around it.
2044fn reach(func: &Func, ranges: Option<&mut Ranges<'_>>, asked: &Fact, at: Inst) -> Option<Reach> {
2045 let wide = spanned(func, ranges?, asked.base, asked.offset, asked.size, at, None)?;
2046 // Nothing was walked past, so this is the fact that came in and asking it again is work
2047 // somebody already did.
2048 (wide.base != asked.base).then_some(wide)
2049}
2050
2051/// Which of the reasons a derivation check this pass could not read is kept for.
2052///
2053/// The census and nothing else. Whether the check goes has already been decided by the time this
2054/// runs, and what it answers is the question somebody reading `-fopt-info-missed` is actually
2055/// asking, which is what would have to be built for this pile to move.
2056///
2057/// It walks the same ground [`spread`] walks rather than being folded into it, because the two want
2058/// different things. [`spread`] wants an answer or nothing, and stopping at the first step it cannot
2059/// read is the fastest way to nothing. This wants to get as far as it can and name where it stopped,
2060/// so it runs only on checks that are staying and it is allowed to be the slower of the two.
2061///
2062/// The five that begin `nothing here says how big` are one refusal counted five ways. What is missing
2063/// in every one of them is how many bytes belong to the object, and where the pointer came from is
2064/// what says which piece of work would supply it: `__counted_by` and the type plane for a pointer out
2065/// of memory, section 7.5's summaries for one that was handed over, `crate::extents` reaching further
2066/// for a global, and the allocation summaries for one a call returned.
2067fn unreadable(func: &Func, ranges: Option<&mut Ranges<'_>>, check: Inst) -> &'static str {
2068 let args = &func[func[check].args];
2069 let (Some(&capability), Some(&from), Some(&to)) = (args.first(), args.get(1), args.get(2))
2070 else {
2071 return NO_EXTENT_OTHER;
2072 };
2073 if named_by(func, capability) != Some(from) {
2074 return NOT_ITS_CAPABILITY_DERIV;
2075 }
2076 // No ranges is a function with no walk in it that steps by a value, so every step here was a
2077 // constant, so the reader that gives up on two bases gave up on two bases.
2078 let Some(ranges) = ranges else { return TWO_BASES_DERIV };
2079 let (base, offset) = normal(func, from);
2080 let Some(near) = spanned(func, ranges, base, offset, 1, check, None) else {
2081 return NO_EXTENT_OTHER;
2082 };
2083 let (base, offset) = normal(func, to);
2084 let Some(far) = spanned(func, ranges, base, offset, 1, check, None) else {
2085 return NO_EXTENT_OTHER;
2086 };
2087 if near.base != far.base {
2088 return TWO_BASES_DERIV;
2089 }
2090 if declared(func, near.base).is_some() {
2091 return OVER_THE_LOCAL_DERIV;
2092 }
2093 match func[near.base].def {
2094 Def::Param { .. } => NO_EXTENT_HANDED,
2095 Def::Result { inst, .. } => match func[inst].opcode {
2096 Opcode::Load => NO_EXTENT_LOADED,
2097 Opcode::GlobalAddr => NO_EXTENT_GLOBAL,
2098 Opcode::Call | Opcode::CallIndirect => NO_EXTENT_RETURNED,
2099 _ => NO_EXTENT_OTHER,
2100 },
2101 }
2102}
2103
2104/// The two ends of a derivation check, each as the range of addresses it can be at.
2105///
2106/// A derivation check asks whether the pointer that came out of a walk is still in the storage
2107/// instance the pointer that went in belongs to. [`derives`] answers that only when both ends
2108/// normalize to one base over constants, and past a step the constant reader gives up on they do
2109/// not, which is why this reads the check's operands again rather than taking what that worked
2110/// out. Each end becomes a range, and the two still have to be off one base or there is nothing
2111/// comparable to ask about.
2112///
2113/// One byte each, for the reason [`derives`] gives. Nothing here claims anything about how many
2114/// bytes are readable at either address.
2115///
2116/// The capability has to be the `cap_of` of the pointer that went in, for the reason [`addressed`]
2117/// gives. Only that one, where [`derives`] reads a capability taken at the base the address was
2118/// worked out from as well: a range this reached by walking past a step comes back off a base of
2119/// its own, which is not the base the capability names, so there is nothing to widen towards.
2120fn spread(
2121 func: &Func,
2122 ranges: Option<&mut Ranges<'_>>,
2123 check: Inst,
2124 at: Inst,
2125) -> Option<(Reach, Reach)> {
2126 let ranges = ranges?;
2127 let args = &func[func[check].args];
2128 let &capability = args.first()?;
2129 let &from = args.get(1)?;
2130 let &to = args.get(2)?;
2131 if named_by(func, capability) != Some(from) {
2132 return None;
2133 }
2134 let (base, offset) = normal(func, from);
2135 let near = spanned(func, ranges, base, offset, 1, at, None)?;
2136 let (base, offset) = normal(func, to);
2137 let far = spanned(func, ranges, base, offset, 1, at, None)?;
2138 (near.base == far.base).then_some((near, far))
2139}
2140
2141/// Every address a walk off `base` can reach, and how many bytes it takes when it gets there.
2142///
2143/// The loop is [`normal`]'s with one more thing to try. A `ptr_add` over a constant is walked
2144/// through the same way, and a `ptr_add` over a value is walked through when document 10's ranges
2145/// put numbers on that value: the low end of the range goes on the distance and the width of it on
2146/// the slack. Anything else is where the walk stops.
2147///
2148/// A caller with a base in mind passes it as `stop` and the walk ends there rather than carrying on
2149/// past it, which matters because the pointer a capability was taken at is very often a walk off
2150/// something further back.
2151///
2152/// Nothing is returned when a step is a value the ranges say nothing useful about, rather than the
2153/// walk stopping there and handing back what it had. What it had would be a range off a `ptr_add`
2154/// nobody knows the size of, which answers nothing, so stopping would be a longer way of saying no.
2155fn spanned(
2156 func: &Func,
2157 ranges: &mut Ranges<'_>,
2158 base: Value,
2159 offset: i128,
2160 size: i128,
2161 at: Inst,
2162 stop: Option<Value>,
2163) -> Option<Reach> {
2164 let mut base = base;
2165 let mut low = offset;
2166 let mut width: i128 = 0;
2167 loop {
2168 // Where a caller has a base in mind, the walk is over when it gets there. Without this it
2169 // carries on past, because a pointer somebody took a capability at is often a walk off
2170 // something else, and a range off a base further back is a range about a base the caller
2171 // was not asking about.
2172 if stop == Some(base) {
2173 break;
2174 }
2175 // A constant step again, because past a step that needed a range there can be more of
2176 // them, and the frontend leaves a field offset as a constant under an array index.
2177 if let Some((from, step)) = walked(func, base) {
2178 low = low.checked_add(step)?;
2179 base = from;
2180 continue;
2181 }
2182 let Some(from) = operand_of(func, base, Opcode::PtrAdd, 0) else { break };
2183 let by = operand_of(func, base, Opcode::PtrAdd, 1)?;
2184 let (least, most) = ranges.at_inst(by, at).signed_bounds()?;
2185 low = low.checked_add(least)?;
2186 width = width.checked_add(most.checked_sub(least)?)?;
2187 base = from;
2188 }
2189 Some(Reach { base, low, width, size })
2190}
2191
2192/// Whether any walk in this function steps by a value rather than a constant.
2193///
2194/// The question the ranges are built for. A function without one of these would pay for a copy of
2195/// the control flow graph and never ask anything of it.
2196/// Whether anything in this function says a lifetime is over.
2197///
2198/// Nothing emits `meta_end` today, so this is false everywhere and the frame slot rule in
2199/// [`Discharge::run`] is on for every function. It is written anyway, and written over the whole
2200/// function rather than along the walk, because the day something does emit one the cheap reading
2201/// is the wrong one: a lifetime that ended in one arm of a branch has ended for a check after the
2202/// join, and a walk down the dominator tree would not have seen it. Turning the rule off for the
2203/// function is the reading that stays right when that day comes, and the finer one is a job for
2204/// whoever makes `meta_end` appear.
2205fn ends_a_lifetime(func: &Func) -> bool {
2206 func.blocks().any(|block| func.insts(block).any(|inst| func[inst].opcode == Opcode::MetaEnd))
2207}
2208
2209fn walks_by_a_value(func: &Func) -> bool {
2210 func.blocks().any(|block| {
2211 func.insts(block).any(|inst| {
2212 func[inst].opcode == Opcode::PtrAdd
2213 && func[func[inst].args].get(1).is_some_and(|&by| constant(func, by).is_none())
2214 })
2215 })
2216}
2217
2218/// Whether the control flow joins a pointer anywhere, which is what the alignment walk needs it for.
2219///
2220/// A block parameter of pointer type outside the entry is a pointer that came in one way on one
2221/// path and another way on another, and [`joined`] answers what it is aligned to by asking the
2222/// predecessors. Asked rather than always building the graph because the comment above says a
2223/// function that wants none of it should pay for none of it, and a function without a join has
2224/// nothing here to ask about.
2225fn joins_a_pointer(func: &Func, entry: Block) -> bool {
2226 func.blocks().any(|block| {
2227 block != entry && func[block].params.iter().any(|¶m| func[param].ty == Type::PTR)
2228 })
2229}
2230
2231/// The pointer one `ptr_add` over a constant was computed from, and by how much.
2232fn walked(func: &Func, value: Value) -> Option<(Value, i128)> {
2233 let from = operand_of(func, value, Opcode::PtrAdd, 0)?;
2234 let by = operand_of(func, value, Opcode::PtrAdd, 1)?;
2235 Some((from, constant(func, by)?))
2236}
2237
2238/// Operand `index` of the instruction that produced `value`, when that instruction is `opcode`.
2239pub(crate) fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
2240 let Def::Result { inst, .. } = func[value].def else { return None };
2241 if func[inst].opcode != opcode {
2242 return None;
2243 }
2244 func[func[inst].args].get(index).copied()
2245}
2246
2247/// The pointer a capability is about, whichever producer made it.
2248///
2249/// [`Opcode::capability_names`] is the fact and this is the lookup over a value. Asked instead of
2250/// `operand_of(func, capability, Opcode::CapOf, 0)`, which was the same question while `cap_of` was
2251/// the only producer `rucc-safety` emitted and became a narrower one when tamnd/rucc#1241 started
2252/// emitting the cheap ones. A rule here cares which pointer a capability describes and not how the
2253/// capability was arrived at, so asking for the opcode by name would have meant a check through a
2254/// pointer read out of memory quietly stopped being dischargeable on the day that read got cheaper.
2255pub(crate) fn named_by(func: &Func, capability: Value) -> Option<Value> {
2256 let Def::Result { inst, .. } = func[capability].def else { return None };
2257 let at = func[inst].opcode.capability_names()?;
2258 func[func[inst].args].get(at).copied()
2259}
2260
2261/// The value of an integer constant, read with its own sign.
2262pub(crate) fn constant(func: &Func, value: Value) -> Option<i128> {
2263 let Def::Result { inst, .. } = func[value].def else { return None };
2264 if func[inst].opcode != Opcode::IConst {
2265 return None;
2266 }
2267 let Extra::Imm(imm) = func[inst].extra else { return None };
2268 let ty = func[value].ty;
2269 ty.is_int().then(|| func[imm].signed(ty))
2270}
2271
2272/// Whether an established fact answers the check being asked about.
2273///
2274/// This function decides nothing. It puts the two together into the term the rule file is written
2275/// about and asks the table, which is the whole of section 7.7's split: the paragraph above worked
2276/// out that the two addresses are one value a constant apart, and whether that is enough is
2277/// somebody's proof rather than this file's opinion.
2278pub(crate) fn covers(fact: &Fact, asked: &Fact) -> bool {
2279 if fact.base != asked.base {
2280 return false;
2281 }
2282 let Some(delta) = asked.offset.checked_sub(fact.offset) else { return false };
2283 let mut question = Question::default();
2284 let at = question.opaque();
2285 let at = question.app("value.i64", &[at]);
2286 let span = question.number(fact.size);
2287 let span = question.app("iconst.i64", &[span]);
2288 let far = question.number(delta);
2289 let far = question.app("iconst.i64", &[far]);
2290 let reach = question.number(asked.size);
2291 let reach = question.app("iconst.i64", &[reach]);
2292 let term = question.app("covered.i64", &[at, span, far, reach]);
2293 match safety::TABLE.find(&question, term) {
2294 Some(found) => yes(&safety::TABLE, found.rule),
2295 None => false,
2296 }
2297}
2298
2299/// Whether an object holds every address a walk can land on.
2300///
2301/// The companion to [`covers`] for the question [`reach`] asks, and it decides nothing either. It
2302/// puts the object and the range of addresses into the term the rule file is written about and
2303/// asks the table. The distance the program actually walks is opaque in the question, which is
2304/// what makes one answer cover every value it could take.
2305fn reaches(fact: &Fact, asked: &Reach) -> bool {
2306 if fact.base != asked.base {
2307 return false;
2308 }
2309 let Some(delta) = asked.low.checked_sub(fact.offset) else { return false };
2310 let mut question = Question::default();
2311 let at = question.opaque();
2312 let at = question.app("value.i64", &[at]);
2313 let span = question.number(fact.size);
2314 let span = question.app("iconst.i64", &[span]);
2315 let delta = question.number(delta);
2316 let delta = question.app("iconst.i64", &[delta]);
2317 let width = question.number(asked.width);
2318 let width = question.app("iconst.i64", &[width]);
2319 let size = question.number(asked.size);
2320 let size = question.app("iconst.i64", &[size]);
2321 let step = question.opaque();
2322 let step = question.app("value.i64", &[step]);
2323 let term = question.app("reached.i64", &[at, span, delta, width, size, step]);
2324 match safety::TABLE.find(&question, term) {
2325 Some(found) => yes(&safety::TABLE, found.rule),
2326 None => false,
2327 }
2328}
2329
2330/// Whether the rule that fired answers yes.
2331///
2332/// A discharge rule replaces the question with a constant, and one is yes. Every rule in the file
2333/// answers that today, and reading it off the rule rather than assuming it is what keeps this
2334/// honest on the day one of them answers something else.
2335pub(crate) fn yes(table: &Table, rule: usize) -> bool {
2336 matches!(table.rules[rule].replacement, [Piece::App { .. }, Piece::Int(1)])
2337}
2338
2339/// A term built to be asked about, and nothing else.
2340///
2341/// The rules are matched against this rather than against the function, because what is being asked
2342/// about is not in the function: it is what the walk worked out about two of its instructions. So
2343/// the subject is a small arena of exactly the term being asked, built fresh for each question and
2344/// thrown away with the answer.
2345#[derive(Debug, Default)]
2346pub(crate) struct Question {
2347 held: Vec<Held>,
2348}
2349
2350/// One node of that term.
2351#[derive(Debug)]
2352enum Held {
2353 /// A number the pattern can read and a guard can be about.
2354 Int(i128),
2355 /// A head and its arguments.
2356 App(&'static str, Vec<usize>),
2357 /// Something with no structure, which is how an address the rule only names is written.
2358 Opaque,
2359}
2360
2361impl Question {
2362 /// Adds a constant and gives back where it went.
2363 ///
2364 /// Named for what it adds rather than for what it holds, because the arena also answers
2365 /// [`Subject::int`] and one name for the two would read as though building a term and asking
2366 /// about one were the same act.
2367 pub(crate) fn number(&mut self, value: i128) -> usize {
2368 self.held.push(Held::Int(value));
2369 self.held.len() - 1
2370 }
2371
2372 /// Adds an application of `head` to what is already in the arena.
2373 pub(crate) fn app(&mut self, head: &'static str, args: &[usize]) -> usize {
2374 self.held.push(Held::App(head, args.to_vec()));
2375 self.held.len() - 1
2376 }
2377
2378 /// Adds something the rule can bind and cannot look inside.
2379 pub(crate) fn opaque(&mut self) -> usize {
2380 self.held.push(Held::Opaque);
2381 self.held.len() - 1
2382 }
2383}
2384
2385impl Subject for Question {
2386 type Node = usize;
2387
2388 fn head(&self, node: usize) -> Option<(&str, usize)> {
2389 match &self.held[node] {
2390 Held::App(head, args) => Some((head, args.len())),
2391 Held::Int(_) | Held::Opaque => None,
2392 }
2393 }
2394
2395 fn arg(&self, node: usize, index: usize) -> usize {
2396 match &self.held[node] {
2397 Held::App(_, args) => args[index],
2398 // The walk only asks for an argument `head` said was there, so this is unreachable
2399 // rather than a case with an answer.
2400 Held::Int(_) | Held::Opaque => unreachable!("only an application has arguments"),
2401 }
2402 }
2403
2404 fn int(&self, node: usize) -> Option<i128> {
2405 match self.held[node] {
2406 Held::Int(value) => Some(value),
2407 Held::App(..) | Held::Opaque => None,
2408 }
2409 }
2410
2411 fn same(&self, a: usize, b: usize) -> bool {
2412 // Every node of a question is written once, so two places holding one thing are one place.
2413 a == b
2414 }
2415}
2416
2417#[cfg(test)]
2418mod tests {
2419 use rucc_base::Interner;
2420 use rucc_ir::{
2421 AsmInfo, Block, BlockCallList, Builder, Extra, Facts, Flags, Func, Inst, InstData, IntPred,
2422 MemInfo, MemOrder, Meta, Opcode, Restrict, Signature, Type, Value,
2423 };
2424
2425 use super::{DISCHARGE, Fact};
2426 use crate::stats::Kind;
2427 use crate::{Fuel, Pass, pass};
2428
2429 /// A function taking a pointer, with one block, ready to have accesses put in it.
2430 fn blank() -> (Interner, Func, Block, Value) {
2431 let mut names = Interner::new();
2432 let name = names.intern("f");
2433 let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR]));
2434 let block = func.create_block();
2435 let pointer = func.append_param(block, Type::PTR);
2436 (names, func, block, pointer)
2437 }
2438
2439 /// Puts `cap_of` and a `check_bounds` over `size` bytes at `pointer` into a block.
2440 ///
2441 /// The same shape `rucc-safety` emits, written out here rather than reached for, because
2442 /// `rucc-opt` is rank 9 alongside `rucc-safety` and cannot depend on it.
2443 fn check(build: &mut Builder<'_>, pointer: Value, size: u64) {
2444 checking_at(build, pointer, pointer, size);
2445 }
2446
2447 /// The same, with the capability taken at `from` rather than at the address being checked.
2448 ///
2449 /// What `rucc_safety::origin` writes, once a capability belongs to a pointer rather than to an
2450 /// access: a field read off a struct is checked through the capability the struct's pointer
2451 /// got, and there is one of those for the whole function rather than one per field.
2452 fn checking_at(build: &mut Builder<'_>, from: Value, pointer: Value, size: u64) {
2453 let args = build.func().push_values(&[from]);
2454 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2455 let info = MemInfo {
2456 size,
2457 align: 1,
2458 order: MemOrder::NotAtomic,
2459 tbaa: None,
2460 owns: 0,
2461 restrict: Restrict::NONE,
2462 };
2463 let args = build.func().push_values(&[capability, pointer]);
2464 let extra = Extra::Mem(build.func().add_mem(info));
2465 build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
2466 }
2467
2468 /// Puts `cap_of` and a `check_live` at `pointer` into a block.
2469 ///
2470 /// `rucc-safety` emits this straight after the bounds check for the same access and shares the
2471 /// one `cap_of` between the two. Sharing it is not what the pass reads, so the tests build a
2472 /// second one, which is the harder shape for it to accept.
2473 fn live(build: &mut Builder<'_>, pointer: Value) {
2474 let args = build.func().push_values(&[pointer]);
2475 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2476 let args = build.func().push_values(&[capability, pointer]);
2477 build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
2478 }
2479
2480 /// Both checks in front of one access, in the order `rucc-safety` writes them.
2481 fn access(build: &mut Builder<'_>, pointer: Value, size: u64) {
2482 check(build, pointer, size);
2483 live(build, pointer);
2484 }
2485
2486 /// Puts `cap_of` and a `check_init` over `size` bytes at `pointer` into a block.
2487 ///
2488 /// The shape `rucc_safety::began` writes in front of a read. It carries a `MemInfo` for the
2489 /// same reason the bounds check does, since how many bytes the access takes is the whole of
2490 /// what the check is about.
2491 fn began(build: &mut Builder<'_>, pointer: Value, size: u64) {
2492 let args = build.func().push_values(&[pointer]);
2493 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2494 let info = MemInfo {
2495 size,
2496 align: 1,
2497 order: MemOrder::NotAtomic,
2498 tbaa: None,
2499 owns: 0,
2500 restrict: Restrict::NONE,
2501 };
2502 let args = build.func().push_values(&[capability, pointer]);
2503 let extra = Extra::Mem(build.func().add_mem(info));
2504 build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckInit) }, &[]);
2505 }
2506
2507 /// How many init checks are left in a function.
2508 fn inits(func: &Func) -> usize {
2509 func.blocks()
2510 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
2511 .filter(|&inst| func[inst].opcode == Opcode::CheckInit)
2512 .count()
2513 }
2514
2515 /// Puts `cap_of` and a `check_type` over `size` bytes at `pointer` into a block, asking with
2516 /// plane entry `node`.
2517 ///
2518 /// The shape `rucc_safety::ask` writes in front of a read. The entry is a bare index because
2519 /// that is all this pass ever does with one: it compares two of them and it never looks the
2520 /// node up, so a number nothing in the module table answers is the same question to it.
2521 fn asked(build: &mut Builder<'_>, pointer: Value, size: u64, node: Meta) {
2522 let args = build.func().push_values(&[pointer]);
2523 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2524 let info = MemInfo {
2525 size,
2526 align: 1,
2527 order: MemOrder::NotAtomic,
2528 tbaa: Some(node),
2529 owns: 0,
2530 restrict: Restrict::NONE,
2531 };
2532 let args = build.func().push_values(&[capability, pointer]);
2533 let extra = Extra::Mem(build.func().add_mem(info));
2534 build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckType) }, &[]);
2535 }
2536
2537 /// Puts a `meta_type` over `size` bytes at `pointer` into a block, naming entry `node`.
2538 fn judged(build: &mut Builder<'_>, pointer: Value, size: i128, node: Meta) {
2539 let length = build.iconst(Type::int(64), size);
2540 let args = build.func().push_values(&[pointer, length]);
2541 let extra = Extra::Node(node);
2542 build.inst(InstData { args, extra, ..InstData::new(Opcode::MetaType) }, &[]);
2543 }
2544
2545 /// How many type checks are left in a function.
2546 fn types(func: &Func) -> usize {
2547 func.blocks()
2548 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
2549 .filter(|&inst| func[inst].opcode == Opcode::CheckType)
2550 .count()
2551 }
2552
2553 /// A pointer `bytes` past another one.
2554 fn past(build: &mut Builder<'_>, pointer: Value, bytes: i128) -> Value {
2555 let offset = build.iconst(Type::int(64), bytes);
2556 let args = build.func().push_values(&[pointer, offset]);
2557 build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
2558 }
2559
2560 /// A pointer a number of bytes past another one, where the number is not one anybody can read.
2561 ///
2562 /// The shape an indexed access leaves behind: `p[i]` is a step by `i * 4` and `normal` stops
2563 /// walking at it, so the base it reaches is the stepped pointer itself rather than `p`.
2564 fn stepped(build: &mut Builder<'_>, pointer: Value, step: Value) -> Value {
2565 let args = build.func().push_values(&[pointer, step]);
2566 build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
2567 }
2568
2569 /// A `check_live` with its capability taken at `from` rather than at the address it checks.
2570 fn living_at(build: &mut Builder<'_>, from: Value, pointer: Value) {
2571 let args = build.func().push_values(&[from]);
2572 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2573 let args = build.func().push_values(&[capability, pointer]);
2574 build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
2575 }
2576
2577 /// Puts the flag `crate::extents` writes onto every check in a function.
2578 ///
2579 /// The pass reads what the IR says, so what a test has to build is an IR that says it. Working
2580 /// out which checks deserve it is `crate::extents`, is about a module rather than a function,
2581 /// and has its own tests.
2582 fn marked(func: &mut Func) {
2583 flagged(func, Flags::STATIC);
2584 }
2585
2586 /// Puts that flag on every check in the function, the way an annotator before the pipeline
2587 /// would have.
2588 fn flagged(func: &mut Func, flag: Flags) {
2589 let insts: Vec<Inst> =
2590 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
2591 for inst in insts {
2592 let check = matches!(
2593 func[inst].opcode,
2594 Opcode::CheckBounds | Opcode::CheckLive | Opcode::CheckDeriv
2595 );
2596 if check {
2597 func[inst].flags |= flag;
2598 }
2599 }
2600 }
2601
2602 /// How many checks are left in a function.
2603 fn checks(func: &Func) -> usize {
2604 func.blocks()
2605 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
2606 .filter(|&inst| func[inst].opcode == Opcode::CheckBounds)
2607 .count()
2608 }
2609
2610 /// How many lifetime checks are left in a function.
2611 fn lives(func: &Func) -> usize {
2612 func.blocks()
2613 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
2614 .filter(|&inst| func[inst].opcode == Opcode::CheckLive)
2615 .count()
2616 }
2617
2618 fn run(func: &mut Func) -> crate::Stats {
2619 DISCHARGE.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
2620 }
2621
2622 /// The same, with one of the measurement's variants rather than the pass the pipeline runs.
2623 fn run_with(pass: &super::Discharge, func: &mut Func) -> crate::Stats {
2624 pass.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
2625 }
2626
2627 #[test]
2628 fn a_run_that_may_only_ask_an_object_leaves_what_dominance_would_have_taken() {
2629 // Two checks of the same bytes on a pointer that came from outside. Nothing here says how
2630 // big the object is, so the only thing that could answer the second one is the first one
2631 // having run, and a run that may not ask that has to keep both.
2632 let (_, mut func, block, pointer) = blank();
2633 let mut build = Builder::new(&mut func, block);
2634 check(&mut build, pointer, 4);
2635 check(&mut build, pointer, 4);
2636 build.ret(&[]);
2637 let stats = run_with(&super::OBJECTS, &mut func);
2638 assert_eq!(checks(&func), 2);
2639 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 0);
2640 }
2641
2642 #[test]
2643 fn a_run_that_may_only_ask_dominance_takes_the_second_check_of_the_same_bytes() {
2644 let (_, mut func, block, pointer) = blank();
2645 let mut build = Builder::new(&mut func, block);
2646 check(&mut build, pointer, 4);
2647 check(&mut build, pointer, 4);
2648 build.ret(&[]);
2649 let stats = run_with(&super::DOMINANCE, &mut func);
2650 assert_eq!(checks(&func), 1);
2651 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2652 }
2653
2654 #[test]
2655 fn a_run_that_may_only_ask_dominance_leaves_a_check_inside_a_local() {
2656 // The other way round. One check, nothing in front of it, and the bytes are inside an
2657 // `alloca` whose size is written on it. Only the object can answer that one.
2658 let (_, mut func, block, _) = blank();
2659 let mut build = Builder::new(&mut func, block);
2660 let slot = local(&mut build, 16);
2661 check(&mut build, slot, 4);
2662 build.ret(&[]);
2663 let stats = run_with(&super::DOMINANCE, &mut func);
2664 assert_eq!(checks(&func), 1);
2665 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
2666 assert_eq!(
2667 run_with(&super::OBJECTS, &mut func).count(Kind::Optimized, super::REMOVED_LOCAL),
2668 1
2669 );
2670 }
2671
2672 #[test]
2673 fn the_measurement_variants_answer_to_names_of_their_own() {
2674 // A run that cannot be reached by a flag is a run nobody can measure with.
2675 let names: Vec<&str> = [
2676 &DISCHARGE,
2677 &super::OBJECTS,
2678 &super::DOMINANCE,
2679 &super::SUMMARIES,
2680 &super::NARROW,
2681 &super::EVERY,
2682 ]
2683 .iter()
2684 .map(|pass| pass.name())
2685 .collect();
2686 assert_eq!(
2687 names,
2688 [
2689 "discharge",
2690 "discharge-objects",
2691 "discharge-dominance",
2692 "discharge-summaries",
2693 "discharge-narrow",
2694 "discharge-every"
2695 ]
2696 );
2697 for name in names {
2698 assert!(pass::find(name).is_some(), "`{name}` is not in the pass list");
2699 }
2700 }
2701
2702 #[test]
2703 fn a_second_check_of_the_same_bytes_goes() {
2704 let (_, mut func, block, pointer) = blank();
2705 let mut build = Builder::new(&mut func, block);
2706 check(&mut build, pointer, 4);
2707 check(&mut build, pointer, 4);
2708 build.ret(&[]);
2709 let stats = run(&mut func);
2710 assert_eq!(checks(&func), 1);
2711 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2712 }
2713
2714 #[test]
2715 fn a_second_init_check_inside_the_bytes_the_first_covered_goes() {
2716 // The same question the bounds arm's first test asks, about the other plane. Sixteen bytes
2717 // were read and passed on, and four of them being read again asks nothing new: bytes that
2718 // have been written stay written.
2719 let (_, mut func, block, pointer) = blank();
2720 let mut build = Builder::new(&mut func, block);
2721 began(&mut build, pointer, 16);
2722 let inside = past(&mut build, pointer, 8);
2723 began(&mut build, inside, 4);
2724 build.ret(&[]);
2725 let stats = run(&mut func);
2726 assert_eq!(inits(&func), 1);
2727 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_INIT), 1);
2728 }
2729
2730 #[test]
2731 fn an_init_check_past_what_the_first_covered_stays() {
2732 // Four bytes were read and the four after them were not, and nothing about the first four
2733 // says anything about the second four.
2734 let (_, mut func, block, pointer) = blank();
2735 let mut build = Builder::new(&mut func, block);
2736 began(&mut build, pointer, 4);
2737 let after = past(&mut build, pointer, 4);
2738 began(&mut build, after, 4);
2739 build.ret(&[]);
2740 let stats = run(&mut func);
2741 assert_eq!(inits(&func), 2);
2742 assert_eq!(stats.count(Kind::Missed, super::NOTHING_WROTE_IT), 2);
2743 }
2744
2745 #[test]
2746 fn an_init_check_a_call_stands_between_stays_and_is_counted() {
2747 // The bounds facts cross a call and these do not, which the `called` comment argues for.
2748 // The row is here so that what the conservatism costs is a number rather than a paragraph.
2749 let (mut names, mut func, block, pointer) = blank();
2750 let mut build = Builder::new(&mut func, block);
2751 began(&mut build, pointer, 8);
2752 let callee = names.intern("might_free");
2753 let signature = build.func().add_signature(Signature::new());
2754 build.call(callee, signature, &[]);
2755 began(&mut build, pointer, 8);
2756 build.ret(&[]);
2757 let stats = run(&mut func);
2758 assert_eq!(inits(&func), 2);
2759 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_INIT), 1);
2760 }
2761
2762 #[test]
2763 fn an_init_check_a_lifetime_starting_stands_between_stays() {
2764 // A `meta_begin` is storage becoming fresh, and fresh storage holds nothing anybody wrote,
2765 // so a read that was passed on in front of one says nothing behind it.
2766 let (_, mut func, block, pointer) = blank();
2767 let mut build = Builder::new(&mut func, block);
2768 began(&mut build, pointer, 8);
2769 let size = build.iconst(Type::int(64), 8);
2770 let args = build.func().push_values(&[pointer, size]);
2771 build.inst(InstData { args, ..InstData::new(Opcode::MetaBegin) }, &[]);
2772 began(&mut build, pointer, 8);
2773 build.ret(&[]);
2774 let stats = run(&mut func);
2775 assert_eq!(inits(&func), 2);
2776 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_INIT), 0);
2777 }
2778
2779 #[test]
2780 fn an_init_check_a_copy_stands_between_stays() {
2781 // A `meta_init_copy` gives the destination whatever the source said about itself, and an
2782 // uninitialized source says the destination is uninitialized too. It is the one plane
2783 // write that can take initialization away.
2784 let (_, mut func, block, pointer) = blank();
2785 let mut build = Builder::new(&mut func, block);
2786 began(&mut build, pointer, 8);
2787 let from = past(&mut build, pointer, 64);
2788 let size = build.iconst(Type::int(64), 8);
2789 let args = build.func().push_values(&[pointer, from, size]);
2790 build.inst(InstData { args, ..InstData::new(Opcode::MetaInitCopy) }, &[]);
2791 began(&mut build, pointer, 8);
2792 build.ret(&[]);
2793 let stats = run(&mut func);
2794 assert_eq!(inits(&func), 2);
2795 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_INIT), 0);
2796 }
2797
2798 #[test]
2799 fn a_second_type_check_inside_the_bytes_the_first_covered_goes() {
2800 // Sixteen bytes were read at one type and four of them are read again at the same type.
2801 // The plane was not written in between, so the second question has the first one's answer.
2802 let (_, mut func, block, pointer) = blank();
2803 let mut build = Builder::new(&mut func, block);
2804 asked(&mut build, pointer, 16, Meta::new(3));
2805 let inside = past(&mut build, pointer, 8);
2806 asked(&mut build, inside, 4, Meta::new(3));
2807 build.ret(&[]);
2808 let stats = run(&mut func);
2809 assert_eq!(types(&func), 1);
2810 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_TYPE), 1);
2811 }
2812
2813 #[test]
2814 fn a_second_type_check_at_another_type_stays() {
2815 // The same bytes and a different entry, which is a different question. Bytes that agree
2816 // with one type are exactly the bytes a read at another type is refused for.
2817 let (_, mut func, block, pointer) = blank();
2818 let mut build = Builder::new(&mut func, block);
2819 asked(&mut build, pointer, 8, Meta::new(3));
2820 asked(&mut build, pointer, 8, Meta::new(4));
2821 build.ret(&[]);
2822 let stats = run(&mut func);
2823 assert_eq!(types(&func), 2);
2824 assert_eq!(stats.count(Kind::Missed, super::NOTHING_TYPED_IT), 2);
2825 }
2826
2827 #[test]
2828 fn a_type_check_a_store_through_the_same_type_stands_between_goes() {
2829 // The judgement a store writes puts its own entry over the bytes it covered and leaves
2830 // every other byte where it was, so a range that agreed with that entry agrees with it
2831 // still, wherever the store landed. `Scope::retyped` is the argument.
2832 let (_, mut func, block, pointer) = blank();
2833 let mut build = Builder::new(&mut func, block);
2834 asked(&mut build, pointer, 8, Meta::new(3));
2835 judged(&mut build, pointer, 8, Meta::new(3));
2836 asked(&mut build, pointer, 8, Meta::new(3));
2837 build.ret(&[]);
2838 let stats = run(&mut func);
2839 assert_eq!(types(&func), 1);
2840 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_TYPE), 1);
2841 }
2842
2843 #[test]
2844 fn a_type_check_a_store_through_another_type_stands_between_stays() {
2845 // The union member store and the untyped store section 7.4 names, which arrive here as the
2846 // same instruction with a different entry on it. Where it landed is what this pass does not
2847 // know, so every range it might have covered stops agreeing with anything.
2848 let (_, mut func, block, pointer) = blank();
2849 let mut build = Builder::new(&mut func, block);
2850 asked(&mut build, pointer, 8, Meta::new(3));
2851 let far = past(&mut build, pointer, 64);
2852 judged(&mut build, far, 8, Meta::new(4));
2853 asked(&mut build, pointer, 8, Meta::new(3));
2854 build.ret(&[]);
2855 let stats = run(&mut func);
2856 assert_eq!(types(&func), 2);
2857 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_TYPE), 0);
2858 }
2859
2860 #[test]
2861 fn a_type_check_a_copy_stands_between_stays() {
2862 // The `memcpy` case. A `meta_type_copy` gives the destination whatever the source said, and
2863 // what the source said is not something this pass has any way to find out.
2864 let (_, mut func, block, pointer) = blank();
2865 let mut build = Builder::new(&mut func, block);
2866 asked(&mut build, pointer, 8, Meta::new(3));
2867 let from = past(&mut build, pointer, 64);
2868 let size = build.iconst(Type::int(64), 8);
2869 let args = build.func().push_values(&[pointer, from, size]);
2870 build.inst(InstData { args, ..InstData::new(Opcode::MetaTypeCopy) }, &[]);
2871 asked(&mut build, pointer, 8, Meta::new(3));
2872 build.ret(&[]);
2873 let stats = run(&mut func);
2874 assert_eq!(types(&func), 2);
2875 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_TYPE), 0);
2876 }
2877
2878 #[test]
2879 fn a_type_check_a_call_stands_between_stays_and_is_counted() {
2880 // Type facts go at a call for the reason the init ones do, and the row is here so that what
2881 // that costs is a number.
2882 let (mut names, mut func, block, pointer) = blank();
2883 let mut build = Builder::new(&mut func, block);
2884 asked(&mut build, pointer, 8, Meta::new(3));
2885 let callee = names.intern("might_free");
2886 let signature = build.func().add_signature(Signature::new());
2887 build.call(callee, signature, &[]);
2888 asked(&mut build, pointer, 8, Meta::new(3));
2889 build.ret(&[]);
2890 let stats = run(&mut func);
2891 assert_eq!(types(&func), 2);
2892 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_TYPE), 1);
2893 }
2894
2895 /// The same check over an access that assumes something about where it starts.
2896 ///
2897 /// [`check`] assumes nothing, which is the right default for the tests above it: what they are
2898 /// about is which bytes a check covers, and an access that assumes nothing has no alignment to
2899 /// answer and so reaches every rule. These are the ones about the alignment itself.
2900 fn assuming(build: &mut Builder<'_>, pointer: Value, size: u64, align: u32) {
2901 let args = build.func().push_values(&[pointer]);
2902 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
2903 let info = MemInfo {
2904 size,
2905 align,
2906 order: MemOrder::NotAtomic,
2907 tbaa: None,
2908 owns: 0,
2909 restrict: Restrict::NONE,
2910 };
2911 let args = build.func().push_values(&[capability, pointer]);
2912 let extra = Extra::Mem(build.func().add_mem(info));
2913 build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
2914 }
2915
2916 #[test]
2917 fn a_check_that_ran_answers_the_alignment_of_the_next_one_through_the_same_pointer() {
2918 // A pointer from outside, so nothing about where it came from says what it is aligned to,
2919 // and two checks of the same bytes. The first stays, because nothing covers its bytes, and
2920 // in staying it runs and refuses if the address is not a multiple of four. So on the way
2921 // to the second the address is a multiple of four whatever anybody knew before, the bytes
2922 // are covered by the first, and the second goes. This is the shape most of an ordinary
2923 // library is: a function reads a field of something it was handed and then reads it again.
2924 let (_, mut func, block, pointer) = blank();
2925 let mut build = Builder::new(&mut func, block);
2926 assuming(&mut build, pointer, 4, 4);
2927 assuming(&mut build, pointer, 4, 4);
2928 build.ret(&[]);
2929 let stats = run(&mut func);
2930 assert_eq!(checks(&func), 1);
2931 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
2932 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
2933 }
2934
2935 #[test]
2936 fn a_check_that_ran_answers_an_alignment_no_larger_than_the_one_it_tested() {
2937 // The first check assumes two bytes and the second assumes four, and two does not answer
2938 // four, so the second stays. Everything a check proves is what the access it stands beside
2939 // was allowed to assume and not a byte more.
2940 let (_, mut func, block, pointer) = blank();
2941 let mut build = Builder::new(&mut func, block);
2942 assuming(&mut build, pointer, 4, 2);
2943 assuming(&mut build, pointer, 4, 4);
2944 build.ret(&[]);
2945 let stats = run(&mut func);
2946 assert_eq!(checks(&func), 2);
2947 assert_eq!(stats.count(Kind::Missed, super::LOST_ALIGNMENT), 1);
2948 }
2949
2950 #[test]
2951 fn a_call_does_not_take_the_alignment_a_check_proved() {
2952 // What a call can do is free the storage and hand it back out smaller, which is why the
2953 // bounds facts are marked when one runs over them. It cannot change the number in a value,
2954 // and an alignment fact is about the number, so it crosses a call untouched. The bounds
2955 // half carries across too, marked, which is what leaves this with one check.
2956 let (mut names, mut func, block, pointer) = blank();
2957 let mut build = Builder::new(&mut func, block);
2958 assuming(&mut build, pointer, 4, 4);
2959 let callee = names.intern("might_free");
2960 let signature = build.func().add_signature(Signature::new());
2961 build.call(callee, signature, &[]);
2962 assuming(&mut build, pointer, 4, 4);
2963 build.ret(&[]);
2964 let stats = run(&mut func);
2965 assert_eq!(checks(&func), 1);
2966 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
2967 }
2968
2969 #[test]
2970 fn an_alignment_a_check_proved_reaches_only_the_blocks_that_check_dominates() {
2971 // The alignment is proved in one arm of a branch and read in the join, so on the other
2972 // path nothing has tested the address at all. A fact that leaked here would take a check
2973 // the misaligned read needs, and dominance is the only thing holding an alignment fact.
2974 // The check in the entry assumes a byte, which is an access that assumes nothing about
2975 // where it starts, so it covers the bytes for the one in the join without saying anything
2976 // about its alignment and the gate is what is left deciding.
2977 let (_, mut func, block, pointer) = blank();
2978 let arm = func.create_block();
2979 let join = func.create_block();
2980 let mut build = Builder::new(&mut func, block);
2981 assuming(&mut build, pointer, 16, 1);
2982 let condition = build.iconst(Type::int(32), 1);
2983 build.br_if(condition, arm, &[], join, &[]);
2984 let mut build = Builder::new(&mut func, arm);
2985 assuming(&mut build, pointer, 32, 4);
2986 build.jump(join, &[]);
2987 let mut build = Builder::new(&mut func, join);
2988 assuming(&mut build, pointer, 4, 4);
2989 build.ret(&[]);
2990 let stats = run(&mut func);
2991 assert_eq!(checks(&func), 3, "the one in the arm reaches further, so all three stay");
2992 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 1);
2993 }
2994
2995 #[test]
2996 fn an_alignment_written_on_a_pointer_handed_in_answers_a_check_on_it() {
2997 // The side table half of tamnd/rucc#1385. The first check assumes a byte, so it covers the
2998 // bytes the second reads and says nothing about where either of them starts, and the
2999 // second one is left with the alignment conjunct and nothing inside the function to answer
3000 // it with. The fact is the answer, and it is the only one there is for a pointer handed in.
3001 let (_, mut func, block, pointer) = blank();
3002 func.set_facts(pointer, Facts { align: Some(4), ..Facts::NONE });
3003 let mut build = Builder::new(&mut func, block);
3004 assuming(&mut build, pointer, 4, 1);
3005 assuming(&mut build, pointer, 4, 4);
3006 build.ret(&[]);
3007 let stats = run(&mut func);
3008 assert_eq!(checks(&func), 1);
3009 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
3010 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
3011 }
3012
3013 #[test]
3014 fn an_alignment_written_on_a_pointer_answers_no_more_than_it_says() {
3015 // The same function with the fact saying two and the access assuming four. Two does not
3016 // answer four, so the check stays, and it stays as an address this knows about rather than
3017 // as one nothing has heard of, which is the difference between the two rows.
3018 let (_, mut func, block, pointer) = blank();
3019 func.set_facts(pointer, Facts { align: Some(2), ..Facts::NONE });
3020 let mut build = Builder::new(&mut func, block);
3021 assuming(&mut build, pointer, 4, 1);
3022 assuming(&mut build, pointer, 4, 4);
3023 build.ret(&[]);
3024 let stats = run(&mut func);
3025 assert_eq!(checks(&func), 2);
3026 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
3027 assert_eq!(stats.count(Kind::Missed, super::LOST_ALIGNMENT), 1);
3028 }
3029
3030 #[test]
3031 fn an_alignment_every_arm_hands_in_reaches_the_join() {
3032 // Both predecessors hand the join a slot, both slots are aligned to eight, so the join's
3033 // parameter is aligned to eight whichever way control came. Nothing is written on the
3034 // function and nothing is assumed from a type: the answer is the least of what the
3035 // predecessors actually pass, which is arithmetic over values already here.
3036 let (_, mut func, block, _) = blank();
3037 let join = func.create_block();
3038 let arm = func.create_block();
3039 let carried = func.append_param(join, Type::PTR);
3040 let mut build = Builder::new(&mut func, block);
3041 let one = local(&mut build, 64);
3042 let condition = build.iconst(Type::int(32), 1);
3043 build.br_if(condition, arm, &[], join, &[one]);
3044 let mut build = Builder::new(&mut func, arm);
3045 let two = local(&mut build, 64);
3046 build.jump(join, &[two]);
3047 let mut build = Builder::new(&mut func, join);
3048 assuming(&mut build, carried, 16, 1);
3049 assuming(&mut build, carried, 4, 4);
3050 build.ret(&[]);
3051 let stats = run(&mut func);
3052 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
3053 assert_eq!(checks(&func), 1, "the one that covers the bytes stays, the aligned one goes");
3054 }
3055
3056 #[test]
3057 fn an_alignment_one_arm_does_not_hand_in_does_not_reach_the_join() {
3058 // The same function with one arm handing in the pointer the function was given. Nothing
3059 // here says anything about that one, so the least over the predecessors is nothing, and a
3060 // walk that took the other arm's answer would be reading a fact off the arm the program
3061 // did not take.
3062 let (_, mut func, block, pointer) = blank();
3063 let join = func.create_block();
3064 let arm = func.create_block();
3065 let carried = func.append_param(join, Type::PTR);
3066 let mut build = Builder::new(&mut func, block);
3067 let one = local(&mut build, 64);
3068 let condition = build.iconst(Type::int(32), 1);
3069 build.br_if(condition, arm, &[], join, &[one]);
3070 let mut build = Builder::new(&mut func, arm);
3071 build.jump(join, &[pointer]);
3072 let mut build = Builder::new(&mut func, join);
3073 assuming(&mut build, carried, 16, 1);
3074 assuming(&mut build, carried, 4, 4);
3075 build.ret(&[]);
3076 let stats = run(&mut func);
3077 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 1);
3078 assert_eq!(checks(&func), 2);
3079 }
3080
3081 #[test]
3082 fn a_pointer_that_walks_a_loop_keeps_only_what_the_step_leaves() {
3083 // The cut, which is the part of the join walk worth a test of its own. The header's
3084 // parameter comes in from the entry as a slot aligned to eight and comes round the back
3085 // edge as itself stepped by eight. The walk meets the parameter inside itself and takes
3086 // only the steps it took to get back there, which is the fixpoint rather than a guess:
3087 // going round again steps by eight again and eight is already the least. So four is
3088 // answered, and sixteen is refused by an answer rather than by nothing, which is the
3089 // difference between the two rows.
3090 let (_, mut func, block, _) = blank();
3091 let header = func.create_block();
3092 let exit = func.create_block();
3093 let carried = func.append_param(header, Type::PTR);
3094 let mut build = Builder::new(&mut func, block);
3095 let slot = local(&mut build, 64);
3096 build.jump(header, &[slot]);
3097 let mut build = Builder::new(&mut func, header);
3098 assuming(&mut build, carried, 16, 1);
3099 assuming(&mut build, carried, 4, 4);
3100 assuming(&mut build, carried, 4, 16);
3101 let next = past(&mut build, carried, 8);
3102 let condition = build.iconst(Type::int(32), 1);
3103 build.br_if(condition, header, &[next], exit, &[]);
3104 let mut build = Builder::new(&mut func, exit);
3105 build.ret(&[]);
3106 let stats = run(&mut func);
3107 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
3108 assert_eq!(stats.count(Kind::Missed, super::LOST_ALIGNMENT), 1);
3109 }
3110
3111 #[test]
3112 fn the_two_reasons_an_alignment_is_not_answered_are_counted_apart() {
3113 // One function with both in it. The pointer from outside is answered by nothing at all,
3114 // which is the row a fact from somewhere else could take, and the slot read a byte in is
3115 // answered by something that says no, which is the row no fact takes. The first check on
3116 // each is the one that covers the bytes for the second, since the gate is only asked once
3117 // a rule has answered those.
3118 let (_, mut func, block, pointer) = blank();
3119 let mut build = Builder::new(&mut func, block);
3120 assuming(&mut build, pointer, 16, 1);
3121 assuming(&mut build, pointer, 4, 4);
3122 let slot = local(&mut build, 16);
3123 let odd = past(&mut build, slot, 1);
3124 assuming(&mut build, odd, 4, 1);
3125 assuming(&mut build, odd, 4, 4);
3126 build.ret(&[]);
3127 let stats = run(&mut func);
3128 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 1);
3129 assert_eq!(stats.count(Kind::Missed, super::LOST_ALIGNMENT), 1);
3130 }
3131
3132 #[test]
3133 fn a_step_off_an_alignment_a_check_proved_is_walked_the_way_a_local_is() {
3134 // The pointer is proved four byte aligned by a check that ran, and then the two accesses
3135 // are at four bytes in, which keeps it, and at one byte in, which does not. Nothing about
3136 // the walk changes because the thing it ends at is a check rather than an `alloca`, which
3137 // is the point of putting the answer where `settled` already looks.
3138 let (_, mut func, block, pointer) = blank();
3139 let mut build = Builder::new(&mut func, block);
3140 assuming(&mut build, pointer, 64, 4);
3141 let even = past(&mut build, pointer, 4);
3142 assuming(&mut build, even, 4, 4);
3143 let odd = past(&mut build, pointer, 1);
3144 assuming(&mut build, odd, 4, 4);
3145 build.ret(&[]);
3146 let stats = run(&mut func);
3147 assert_eq!(checks(&func), 2, "the one four bytes in goes and the one a byte in stays");
3148 assert_eq!(stats.count(Kind::Missed, super::LOST_ALIGNMENT), 1);
3149 }
3150
3151 #[test]
3152 fn a_check_inside_a_local_at_an_offset_the_local_is_aligned_through_goes() {
3153 // An eight byte aligned slot read four bytes in, which is a member of a record and the
3154 // commonest access there is. The offset leaves four of the eight, the access assumes four,
3155 // and the check goes the way it did before any of this.
3156 let (_, mut func, block, _) = blank();
3157 let mut build = Builder::new(&mut func, block);
3158 let slot = local(&mut build, 16);
3159 let field = past(&mut build, slot, 4);
3160 assuming(&mut build, field, 4, 4);
3161 build.ret(&[]);
3162 let stats = run(&mut func);
3163 assert_eq!(checks(&func), 0);
3164 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
3165 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
3166 }
3167
3168 #[test]
3169 fn a_check_a_cast_moved_off_the_alignment_stays_however_well_its_bytes_are_covered() {
3170 // Row S7 written in IR. The bytes are inside the slot and the slot is aligned, but the
3171 // access starts one byte in and assumes four, and one byte in is where the alignment is
3172 // lost. This is the check the misaligned read needs and the one the accounting run found
3173 // going missing.
3174 let (_, mut func, block, _) = blank();
3175 let mut build = Builder::new(&mut func, block);
3176 let slot = local(&mut build, 16);
3177 let odd = past(&mut build, slot, 1);
3178 assuming(&mut build, odd, 4, 4);
3179 build.ret(&[]);
3180 let stats = run(&mut func);
3181 assert_eq!(checks(&func), 1);
3182 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
3183 assert_eq!(stats.count(Kind::Missed, super::LOST_ALIGNMENT), 1);
3184 }
3185
3186 #[test]
3187 fn a_subscript_that_steps_by_the_width_it_reads_settles_its_own_alignment() {
3188 // `p[i]` on an `int *` an allocator made. Nobody knows what the index is, and nobody has
3189 // to: the step is the index times four, four divides it whatever the index turns out to
3190 // be, and the allocation it starts from is aligned to more than that.
3191 let (_, mut func, inside, _, pointer, index) = allocation(64);
3192 let mut build = Builder::new(&mut func, inside);
3193 let four = build.iconst(Type::int(64), 4);
3194 let step = build.binary(Opcode::Mul, index, four, Flags::NONE);
3195 let args = build.func().push_values(&[pointer, step]);
3196 let at = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
3197 assuming(&mut build, at, 4, 4);
3198 build.ret(&[]);
3199 let stats = run(&mut func);
3200 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_ALIGNMENT), 0);
3201 }
3202
3203 #[test]
3204 fn what_answers_an_alignment_claim_is_the_rule_and_not_a_comparison() {
3205 // The four cases the rule is asked about, and the fifth is the reason it is a rule. A
3206 // number larger than the claim and not a multiple of it answers nothing, and the guard is
3207 // written so that the question never gets asked with one, because `super::settled` only
3208 // ever gives back a power of two. The last one is the same point from the other end: the
3209 // largest number there is is larger than every claim and divides nothing, and what the
3210 // walk means by it is that it took no step rather than that it found an alignment.
3211 assert!(super::settles(8, 8));
3212 assert!(super::settles(16, 8));
3213 assert!(!super::settles(4, 8));
3214 assert!(!super::settles(0, 8));
3215 assert!(!super::settles(u64::MAX, 8));
3216 }
3217
3218 #[test]
3219 fn a_step_by_something_nobody_can_read_settles_nothing() {
3220 // A step the ranges do bound, so the bytes are answered and the check was on its way out,
3221 // and a step nothing says the low bits of, so where the access starts is not answered. A
3222 // mask of seven is nought to seven and three is one of those.
3223 let (_, mut func, block, _, index) = indexed();
3224 let mut build = Builder::new(&mut func, block);
3225 let slot = local(&mut build, 16);
3226 let step = low_bits(&mut build, index, 7);
3227 let args = build.func().push_values(&[slot, step]);
3228 let at = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
3229 assuming(&mut build, at, 4, 4);
3230 build.ret(&[]);
3231 let stats = run(&mut func);
3232 assert_eq!(checks(&func), 1);
3233 assert_eq!(stats.count(Kind::Missed, super::LOST_ALIGNMENT), 1);
3234 }
3235
3236 #[test]
3237 fn a_check_over_a_length_the_program_worked_out_is_not_this_pass_to_read() {
3238 // Section 7.4's hoisted check covers as many bytes as its loop runs times, which is a value
3239 // and not a number. Every range this pass compares is a pair of numbers, so it says so and
3240 // leaves the check alone rather than reading the payload, whose size is one element.
3241 let (_, mut func, block, pointer) = blank();
3242 let mut build = Builder::new(&mut func, block);
3243 check(&mut build, pointer, 4);
3244 let args = build.func().push_values(&[pointer]);
3245 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
3246 let bytes = build.iconst(Type::int(64), 4);
3247 let info = MemInfo {
3248 size: 4,
3249 align: 1,
3250 order: MemOrder::NotAtomic,
3251 tbaa: None,
3252 owns: 0,
3253 restrict: Restrict::NONE,
3254 };
3255 let extra = Extra::Mem(build.func().add_mem(info));
3256 let args = build.func().push_values(&[capability, pointer, bytes]);
3257 build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
3258 build.ret(&[]);
3259
3260 let stats = run(&mut func);
3261 assert_eq!(checks(&func), 2, "the second one stays");
3262 assert_eq!(stats.count(Kind::Missed, super::COMPUTED_EXTENT), 1);
3263 }
3264
3265 #[test]
3266 fn a_check_of_bytes_inside_a_checked_range_goes() {
3267 // Four bytes at offset four, inside sixteen bytes at offset zero. This is the shape the
3268 // whole pass is for: a struct whose fields are read one after another through one pointer.
3269 let (_, mut func, block, pointer) = blank();
3270 let mut build = Builder::new(&mut func, block);
3271 check(&mut build, pointer, 16);
3272 let field = past(&mut build, pointer, 4);
3273 check(&mut build, field, 4);
3274 build.ret(&[]);
3275 run(&mut func);
3276 assert_eq!(checks(&func), 1);
3277 }
3278
3279 #[test]
3280 fn a_check_of_bytes_past_the_end_of_a_checked_range_stays() {
3281 // Four bytes at offset fourteen is two bytes past the end of the sixteen that were
3282 // checked, and those two bytes are what the check is for.
3283 let (_, mut func, block, pointer) = blank();
3284 let mut build = Builder::new(&mut func, block);
3285 check(&mut build, pointer, 16);
3286 let over = past(&mut build, pointer, 14);
3287 check(&mut build, over, 4);
3288 build.ret(&[]);
3289 assert!(!run(&mut func).changed());
3290 assert_eq!(checks(&func), 2);
3291 }
3292
3293 #[test]
3294 fn a_check_of_bytes_before_a_checked_range_stays() {
3295 // The guard's `delta` is not negative, and this is why. A read four bytes below what was
3296 // checked is a read of somebody else's memory, and it is the bug the check exists for.
3297 let (_, mut func, block, pointer) = blank();
3298 let mut build = Builder::new(&mut func, block);
3299 check(&mut build, pointer, 16);
3300 let under = past(&mut build, pointer, -4);
3301 check(&mut build, under, 4);
3302 build.ret(&[]);
3303 assert!(!run(&mut func).changed());
3304 assert_eq!(checks(&func), 2);
3305 }
3306
3307 #[test]
3308 fn a_check_whose_capability_was_taken_where_the_pointer_came_from_covers_the_bytes_between() {
3309 // The shape a capability that belongs to a pointer produces. The check is on a field eight
3310 // bytes in and the capability was taken at the struct's pointer, so what it says is that
3311 // those four bytes and that pointer are in one instance. An instance is a run of bytes, so
3312 // everything from the pointer up to the end of the field is in it, and that is the fact.
3313 // The second check is inside it and goes.
3314 let (_, mut func, block, pointer) = blank();
3315 let mut build = Builder::new(&mut func, block);
3316 let field = past(&mut build, pointer, 8);
3317 checking_at(&mut build, pointer, field, 4);
3318 checking_at(&mut build, pointer, pointer, 4);
3319 build.ret(&[]);
3320 run(&mut func);
3321 assert_eq!(checks(&func), 1);
3322 }
3323
3324 #[test]
3325 fn a_check_whose_capability_was_taken_where_the_pointer_came_from_says_nothing_past_the_end() {
3326 // And the run stops where the access does. Four bytes at twelve are past the twelve the
3327 // check above established, and nothing here says the instance reaches that far.
3328 let (_, mut func, block, pointer) = blank();
3329 let mut build = Builder::new(&mut func, block);
3330 let field = past(&mut build, pointer, 8);
3331 checking_at(&mut build, pointer, field, 4);
3332 let over = past(&mut build, pointer, 12);
3333 checking_at(&mut build, pointer, over, 4);
3334 build.ret(&[]);
3335 assert!(!run(&mut func).changed());
3336 assert_eq!(checks(&func), 2);
3337 }
3338
3339 #[test]
3340 fn a_check_whose_capability_is_about_neither_end_of_the_walk_stays() {
3341 // Two rules and no third. A capability is about the address being checked or about the
3342 // pointer that address came off, and one about anything else is asking after an instance
3343 // this pass has nothing to say about. The capability here is readable and names a
3344 // pointer this address was never walked off, which is a different instance and nothing to
3345 // be done about, so it stays in the row it was in.
3346 let mut names = Interner::new();
3347 let name = names.intern("two");
3348 let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::PTR]));
3349 let block = func.create_block();
3350 let pointer = func.append_param(block, Type::PTR);
3351 let other = func.append_param(block, Type::PTR);
3352 let mut build = Builder::new(&mut func, block);
3353 check(&mut build, pointer, 16);
3354 let field = past(&mut build, pointer, 4);
3355 checking_at(&mut build, other, field, 4);
3356 build.ret(&[]);
3357 let stats = run(&mut func);
3358 assert_eq!(checks(&func), 2);
3359 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_SHAPE), 1);
3360 }
3361
3362 #[test]
3363 fn the_two_reasons_a_shape_is_not_read_are_counted_apart() {
3364 // One row used to hold both of these and the row said the first thing about both. The
3365 // first check walks off the pointer by a step nobody here can read, and its capability
3366 // names that pointer, so what stopped it is that the capability is about something
3367 // further back than the base a walk this pass can follow reaches, and since the pointer is
3368 // a parameter nothing here has an extent for, it lands in the row that says so. The second
3369 // is checked through a capability taken at an unrelated pointer, which is a different
3370 // instance and is not the same problem at all.
3371 let mut names = Interner::new();
3372 let name = names.intern("two");
3373 let params = [Type::PTR, Type::PTR, Type::int(64)];
3374 let mut func = Func::new(name, Signature::new().with_params(¶ms));
3375 let block = func.create_block();
3376 let pointer = func.append_param(block, Type::PTR);
3377 let other = func.append_param(block, Type::PTR);
3378 let step = func.append_param(block, Type::int(64));
3379 let mut build = Builder::new(&mut func, block);
3380 let far = stepped(&mut build, pointer, step);
3381 checking_at(&mut build, pointer, far, 4);
3382 checking_at(&mut build, other, pointer, 4);
3383 build.ret(&[]);
3384 let stats = run(&mut func);
3385 assert_eq!(checks(&func), 2);
3386 assert_eq!(stats.count(Kind::Missed, super::MIDWAY_NO_EXTENT), 1);
3387 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_SHAPE), 1);
3388 }
3389
3390 #[test]
3391 fn a_lifetime_check_whose_capability_is_further_back_than_its_base_is_counted_apart_too() {
3392 // The same split on the other half, because the two rows are close to the same size on
3393 // the amalgamation and a relaxation would have to serve both.
3394 let mut names = Interner::new();
3395 let name = names.intern("two");
3396 let params = [Type::PTR, Type::PTR, Type::int(64)];
3397 let mut func = Func::new(name, Signature::new().with_params(¶ms));
3398 let block = func.create_block();
3399 let pointer = func.append_param(block, Type::PTR);
3400 let other = func.append_param(block, Type::PTR);
3401 let step = func.append_param(block, Type::int(64));
3402 let mut build = Builder::new(&mut func, block);
3403 let far = stepped(&mut build, pointer, step);
3404 living_at(&mut build, pointer, far);
3405 living_at(&mut build, other, pointer);
3406 build.ret(&[]);
3407 let stats = run(&mut func);
3408 assert_eq!(lives(&func), 2);
3409 assert_eq!(stats.count(Kind::Missed, super::MIDWAY_NO_EXTENT_LIVE), 1);
3410 assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_SHAPE_LIVE), 1);
3411 }
3412
3413 #[test]
3414 fn a_check_through_a_pointer_nothing_relates_to_the_first_stays() {
3415 let mut names = Interner::new();
3416 let name = names.intern("two");
3417 let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::PTR]));
3418 let block = func.create_block();
3419 let one = func.append_param(block, Type::PTR);
3420 let other = func.append_param(block, Type::PTR);
3421 let mut build = Builder::new(&mut func, block);
3422 check(&mut build, one, 16);
3423 check(&mut build, other, 4);
3424 build.ret(&[]);
3425 assert!(!run(&mut func).changed());
3426 assert_eq!(checks(&func), 2);
3427 }
3428
3429 #[test]
3430 fn a_bounds_check_a_call_stands_between_goes_and_its_lifetime_check_stays() {
3431 // The eighth box of tamnd/rucc#1241 and the module comment's section on why it is allowed.
3432 // The range the first check established is still one range on the far side of the call, or
3433 // the lifetime check at the second access is about to refuse, and that check is still here
3434 // to do it because the lifetime facts are still dropped.
3435 let (mut names, mut func, block, pointer) = blank();
3436 let mut build = Builder::new(&mut func, block);
3437 access(&mut build, pointer, 16);
3438 let callee = names.intern("might_free");
3439 let signature = build.func().add_signature(Signature::new());
3440 build.call(callee, signature, &[]);
3441 access(&mut build, pointer, 4);
3442 build.ret(&[]);
3443 let stats = run(&mut func);
3444 assert_eq!(checks(&func), 1, "the bounds check crossed the call");
3445 assert_eq!(lives(&func), 2, "and the lifetime check did not");
3446 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
3447 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
3448 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
3449 }
3450
3451 #[test]
3452 fn a_bounds_check_inline_assembly_stands_between_stays_and_is_counted() {
3453 // The other half of the split. A call hands the planes to the runtime and a block of
3454 // assembly does not, so this one drops both kinds and the row that says what that costs is
3455 // still reachable.
3456 let (_, mut func, block, pointer) = blank();
3457 let mut build = Builder::new(&mut func, block);
3458 check(&mut build, pointer, 16);
3459 build.inst(InstData::new(Opcode::InlineAsm), &[]);
3460 check(&mut build, pointer, 4);
3461 build.ret(&[]);
3462 let stats = run(&mut func);
3463 assert!(!stats.changed());
3464 assert_eq!(checks(&func), 2);
3465 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
3466 }
3467
3468 #[test]
3469 fn a_lifetime_check_is_not_widened_by_a_range_a_call_ran_over() {
3470 // The third of the three things the module comment says have to hold. The sixteen bytes
3471 // were one instance before the call and the call may have freed them and made something
3472 // smaller in their place, so the lifetime check at the pointer says the new instance is
3473 // alive and says nothing at all about the byte twelve further on. Widening by the older
3474 // range would discharge the second lifetime check, and the access it guards is the one
3475 // that would then land in storage the new instance does not own.
3476 let (mut names, mut func, block, pointer) = blank();
3477 let mut build = Builder::new(&mut func, block);
3478 check(&mut build, pointer, 16);
3479 let callee = names.intern("might_free");
3480 let signature = build.func().add_signature(Signature::new());
3481 build.call(callee, signature, &[]);
3482 live(&mut build, pointer);
3483 let field = past(&mut build, pointer, 12);
3484 live(&mut build, field);
3485 build.ret(&[]);
3486 let stats = run(&mut func);
3487 assert_eq!(lives(&func), 2, "the second one is not answered by the first");
3488 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 0);
3489 }
3490
3491 #[test]
3492 fn a_check_a_call_that_cannot_free_stands_between_goes() {
3493 // The other side of the paragraph above. The summary said this call reaches nothing that
3494 // ends a lifetime, so the range the first check established is still one range.
3495 let (mut names, mut func, block, pointer) = blank();
3496 let mut build = Builder::new(&mut func, block);
3497 check(&mut build, pointer, 16);
3498 let callee = names.intern("counts_them");
3499 let signature = build.func().add_signature(Signature::new());
3500 let call = build.call(callee, signature, &[]);
3501 check(&mut build, pointer, 4);
3502 build.ret(&[]);
3503 func[call].flags |= Flags::NOFREE;
3504 let stats = run(&mut func);
3505 assert_eq!(checks(&func), 1);
3506 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
3507 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
3508 }
3509
3510 #[test]
3511 fn inline_assembly_throws_the_facts_away_whatever_it_is_flagged() {
3512 // There is no flag that would make this safe. The template is text the compiler does not
3513 // read, so nothing worked anything out about what it reaches.
3514 let (mut names, mut func, block, pointer) = blank();
3515 let mut build = Builder::new(&mut func, block);
3516 check(&mut build, pointer, 16);
3517 build.inline_asm(
3518 AsmInfo {
3519 template: names.intern("nop"),
3520 constraints: names.intern(""),
3521 clobbers: names.intern(""),
3522 targets: BlockCallList::EMPTY,
3523 },
3524 &[],
3525 &[],
3526 Flags::NONE,
3527 );
3528 check(&mut build, pointer, 4);
3529 build.ret(&[]);
3530 let stats = run(&mut func);
3531 assert!(!stats.changed());
3532 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
3533 }
3534
3535 #[test]
3536 fn a_check_that_only_one_path_covers_stays() {
3537 // The dominator tree is what makes this right. The check in the arm covers the one in the
3538 // join on one path and not on the other, and a check that goes has to be one that ran.
3539 let (_, mut func, block, pointer) = blank();
3540 let arm = func.create_block();
3541 let join = func.create_block();
3542 let mut build = Builder::new(&mut func, block);
3543 let condition = build.iconst(Type::int(32), 1);
3544 build.br_if(condition, arm, &[], join, &[]);
3545 let mut build = Builder::new(&mut func, arm);
3546 check(&mut build, pointer, 16);
3547 build.jump(join, &[]);
3548 let mut build = Builder::new(&mut func, join);
3549 check(&mut build, pointer, 4);
3550 build.ret(&[]);
3551 assert!(!run(&mut func).changed());
3552 assert_eq!(checks(&func), 2);
3553 }
3554
3555 #[test]
3556 fn a_check_a_dominating_block_covers_goes() {
3557 let (_, mut func, block, pointer) = blank();
3558 let after = func.create_block();
3559 let mut build = Builder::new(&mut func, block);
3560 check(&mut build, pointer, 16);
3561 build.jump(after, &[]);
3562 let mut build = Builder::new(&mut func, after);
3563 let field = past(&mut build, pointer, 8);
3564 check(&mut build, field, 8);
3565 build.ret(&[]);
3566 run(&mut func);
3567 assert_eq!(checks(&func), 1);
3568 }
3569
3570 #[test]
3571 fn fuel_stops_the_removing_and_not_the_looking() {
3572 let (_, mut func, block, pointer) = blank();
3573 let mut build = Builder::new(&mut func, block);
3574 check(&mut build, pointer, 4);
3575 check(&mut build, pointer, 4);
3576 check(&mut build, pointer, 4);
3577 build.ret(&[]);
3578 let mut fuel = Fuel::of(1);
3579 let stats = DISCHARGE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
3580 assert_eq!(checks(&func), 2);
3581 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
3582 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
3583 }
3584
3585 #[test]
3586 fn a_second_lifetime_check_of_the_same_address_goes() {
3587 // The narrow fact on its own, with no range around it to widen into.
3588 let (_, mut func, block, pointer) = blank();
3589 let mut build = Builder::new(&mut func, block);
3590 live(&mut build, pointer);
3591 live(&mut build, pointer);
3592 build.ret(&[]);
3593 let stats = run(&mut func);
3594 assert_eq!(lives(&func), 1);
3595 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
3596 }
3597
3598 #[test]
3599 fn a_lifetime_check_inside_a_checked_range_goes() {
3600 // The shape the pass is for, with both halves of it. Sixteen bytes are checked and found
3601 // alive, then a field four bytes in is read, and neither check in front of it survives.
3602 let (_, mut func, block, pointer) = blank();
3603 let mut build = Builder::new(&mut func, block);
3604 access(&mut build, pointer, 16);
3605 let field = past(&mut build, pointer, 4);
3606 access(&mut build, field, 4);
3607 build.ret(&[]);
3608 let stats = run(&mut func);
3609 assert_eq!(checks(&func), 1);
3610 assert_eq!(lives(&func), 1);
3611 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
3612 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
3613 }
3614
3615 #[test]
3616 fn a_lifetime_check_outside_every_checked_range_stays() {
3617 // Four bytes at offset twenty are past the sixteen that were checked, so nothing says the
3618 // address is in the instance that was found alive, and it might be in no instance at all.
3619 let (_, mut func, block, pointer) = blank();
3620 let mut build = Builder::new(&mut func, block);
3621 access(&mut build, pointer, 16);
3622 let over = past(&mut build, pointer, 20);
3623 live(&mut build, over);
3624 build.ret(&[]);
3625 assert!(!run(&mut func).changed());
3626 assert_eq!(lives(&func), 2);
3627 }
3628
3629 #[test]
3630 fn a_lifetime_check_with_no_range_around_it_does_not_widen() {
3631 // Without the bounds check the first lifetime check speaks only for its own address, so
3632 // the one four bytes along is a different question and stays.
3633 let (_, mut func, block, pointer) = blank();
3634 let mut build = Builder::new(&mut func, block);
3635 live(&mut build, pointer);
3636 let field = past(&mut build, pointer, 4);
3637 live(&mut build, field);
3638 build.ret(&[]);
3639 assert!(!run(&mut func).changed());
3640 assert_eq!(lives(&func), 2);
3641 }
3642
3643 #[test]
3644 fn a_lifetime_check_a_call_stands_between_stays_and_is_counted() {
3645 // Section 8.8's number. This is the one the summaries were written for.
3646 let (mut names, mut func, block, pointer) = blank();
3647 let mut build = Builder::new(&mut func, block);
3648 access(&mut build, pointer, 16);
3649 let callee = names.intern("might_free");
3650 let signature = build.func().add_signature(Signature::new());
3651 build.call(callee, signature, &[]);
3652 let field = past(&mut build, pointer, 4);
3653 live(&mut build, field);
3654 build.ret(&[]);
3655 let stats = run(&mut func);
3656 assert!(!stats.changed());
3657 assert_eq!(lives(&func), 2);
3658 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
3659 }
3660
3661 #[test]
3662 fn a_lifetime_check_a_call_that_cannot_free_stands_between_goes() {
3663 let (mut names, mut func, block, pointer) = blank();
3664 let mut build = Builder::new(&mut func, block);
3665 access(&mut build, pointer, 16);
3666 let callee = names.intern("counts_them");
3667 let signature = build.func().add_signature(Signature::new());
3668 let call = build.call(callee, signature, &[]);
3669 let field = past(&mut build, pointer, 4);
3670 live(&mut build, field);
3671 build.ret(&[]);
3672 func[call].flags |= Flags::NOFREE;
3673 let stats = run(&mut func);
3674 assert_eq!(lives(&func), 1);
3675 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
3676 }
3677
3678 #[test]
3679 fn ending_a_lifetime_throws_the_facts_away() {
3680 // Nothing emits `meta_end` yet, so this is the test that says what will happen when
3681 // something does, rather than a test of anything the compiler does today.
3682 let (_, mut func, block, pointer) = blank();
3683 let mut build = Builder::new(&mut func, block);
3684 access(&mut build, pointer, 16);
3685 let size = build.iconst(Type::int(64), 16);
3686 let args = build.func().push_values(&[pointer, size]);
3687 build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
3688 access(&mut build, pointer, 16);
3689 build.ret(&[]);
3690 let stats = run(&mut func);
3691 assert!(!stats.changed());
3692 assert_eq!(checks(&func), 2);
3693 assert_eq!(lives(&func), 2);
3694 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
3695 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
3696 }
3697
3698 #[test]
3699 fn fuel_runs_out_over_both_kinds_of_check() {
3700 let (_, mut func, block, pointer) = blank();
3701 let mut build = Builder::new(&mut func, block);
3702 access(&mut build, pointer, 16);
3703 access(&mut build, pointer, 4);
3704 build.ret(&[]);
3705 let mut fuel = Fuel::of(1);
3706 let stats = DISCHARGE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel);
3707 assert_eq!(checks(&func), 1);
3708 assert_eq!(lives(&func), 2);
3709 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
3710 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
3711 }
3712
3713 #[test]
3714 fn a_distance_too_large_to_be_a_real_access_is_not_discharged() {
3715 // The guard's bound. The two readings of the arithmetic agree while the numbers stay
3716 // small, so a rule proved at sixty four bits is not asked about anything else. Nothing
3717 // here is wrong, it simply is not proved, and a check that is not proved to be unnecessary
3718 // stays.
3719 let huge = i128::from(u64::MAX) * 4;
3720 let fact = Fact { base: Value::new(0), offset: 0, size: huge };
3721 let asked = Fact { base: Value::new(0), offset: huge / 2, size: 4 };
3722 assert!(!super::covers(&fact, &asked));
3723 }
3724
3725 #[test]
3726 fn a_range_of_addresses_wider_than_the_rule_allows_is_not_discharged() {
3727 // The guard on `reached.i64` bounds each of the three numbers at four gigabytes, for the
3728 // reason the rule file gives: past there the compiler's `i128` reading of the guard and the
3729 // solver's sixty four bit reading part company, and a rule proved under one and run under
3730 // the other is a rule proved about arithmetic that is not happening. A step whose range is
3731 // that wide is the usual case rather than a corner, since an index nothing has bounded says
3732 // nothing about where the access lands.
3733 let base = Value::new(0);
3734 let whole = Fact::whole(base, i128::from(u64::MAX) * 4);
3735 let asked = super::Reach { base, low: 0, width: i128::from(u64::MAX), size: 4 };
3736 assert!(!super::reaches(&whole, &asked));
3737 }
3738
3739 #[test]
3740 fn a_range_of_addresses_that_ends_where_the_object_does_is_discharged() {
3741 // Sixteen bytes, a step somewhere in nought to eleven, four bytes read. The last address
3742 // the walk can reach is the last one in the object, which is inside it.
3743 let base = Value::new(0);
3744 let whole = Fact::whole(base, 16);
3745 let asked = super::Reach { base, low: 0, width: 12, size: 4 };
3746 assert!(super::reaches(&whole, &asked));
3747 let over = super::Reach { base, low: 0, width: 13, size: 4 };
3748 assert!(!super::reaches(&whole, &over), "one byte further runs off the end");
3749 }
3750
3751 #[test]
3752 fn a_walk_by_a_bounded_step_off_a_local_takes_its_derivation_check_with_it() {
3753 // The shape `derives` cannot read at all: the pointer that went in is the slot and the one
3754 // that came out is a value past it, so the two are not one base and two constants. Both
3755 // ends widen to the slot, the slot holds both ranges, and one thing holding both is what a
3756 // derivation check asks about.
3757 let (_, mut func, block, _, index) = indexed();
3758 let mut build = Builder::new(&mut func, block);
3759 let slot = local(&mut build, 16);
3760 let step = low_bits(&mut build, index, 7);
3761 let at = walk(&mut build, slot, step);
3762 deriv(&mut build, slot, at, 4);
3763 build.ret(&[]);
3764 let stats = run(&mut func);
3765 assert_eq!(derivs(&func), 0);
3766 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 1);
3767 }
3768
3769 #[test]
3770 fn a_walk_that_can_leave_the_local_keeps_its_derivation_check() {
3771 // Nought to fifteen off a slot of eight. Every step is bounded and the answer is still no,
3772 // because the question is whether the slot holds every address the walk can reach.
3773 let (_, mut func, block, _, index) = indexed();
3774 let mut build = Builder::new(&mut func, block);
3775 let slot = local(&mut build, 8);
3776 let step = low_bits(&mut build, index, 15);
3777 let at = walk(&mut build, slot, step);
3778 deriv(&mut build, slot, at, 4);
3779 build.ret(&[]);
3780 let stats = run(&mut func);
3781 assert_eq!(derivs(&func), 1);
3782 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 0);
3783 assert_eq!(stats.count(Kind::Missed, super::OVER_THE_LOCAL_DERIV), 1);
3784 }
3785
3786 #[test]
3787 fn a_lifetime_check_a_bounded_walk_lands_inside_a_checked_range_goes() {
3788 // An access over thirty two bytes establishes the range, and the lifetime check beside it
3789 // makes that range one a check found alive. The lifetime check on the walk then goes,
3790 // because every address the walk can reach is in the range that was found alive.
3791 //
3792 // Written off a parameter rather than a slot because a slot answers the narrow question on
3793 // its own. What has to answer this one is a range a check was passed on.
3794 let (_, mut func, block, pointer, index) = indexed();
3795 let mut build = Builder::new(&mut func, block);
3796 access(&mut build, pointer, 32);
3797 let step = low_bits(&mut build, index, 7);
3798 let at = walk(&mut build, pointer, step);
3799 live(&mut build, at);
3800 build.ret(&[]);
3801 let stats = run(&mut func);
3802 assert_eq!(lives(&func), 1, "the one in front of the access stays");
3803 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 1);
3804 }
3805
3806 #[test]
3807 fn a_lifetime_check_a_bounded_walk_can_leave_the_checked_range_keeps_it() {
3808 // The same over eight bytes, under a walk that can go fifteen past the start. A range of
3809 // eight bytes does not hold an address fifteen along from where it begins.
3810 let (_, mut func, block, pointer, index) = indexed();
3811 let mut build = Builder::new(&mut func, block);
3812 access(&mut build, pointer, 8);
3813 let step = low_bits(&mut build, index, 15);
3814 let at = walk(&mut build, pointer, step);
3815 live(&mut build, at);
3816 build.ret(&[]);
3817 let stats = run(&mut func);
3818 assert_eq!(lives(&func), 2);
3819 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 0);
3820 }
3821
3822 /// A stack slot of `size` bytes, in the entry block where the verifier wants one.
3823 fn local(build: &mut Builder<'_>, size: u64) -> Value {
3824 let info = MemInfo {
3825 size,
3826 align: 8,
3827 order: MemOrder::NotAtomic,
3828 tbaa: None,
3829 owns: 0,
3830 restrict: Restrict::NONE,
3831 };
3832 let extra = Extra::Mem(build.func().add_mem(info));
3833 build.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
3834 }
3835
3836 /// A function taking a pointer and an index, with one block.
3837 fn indexed() -> (Interner, Func, Block, Value, Value) {
3838 let mut names = Interner::new();
3839 let name = names.intern("f");
3840 let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::int(64)]));
3841 let block = func.create_block();
3842 let pointer = func.append_param(block, Type::PTR);
3843 let index = func.append_param(block, Type::int(64));
3844 (names, func, block, pointer, index)
3845 }
3846
3847 /// A pointer a value past another one.
3848 fn walk(build: &mut Builder<'_>, pointer: Value, by: Value) -> Value {
3849 let args = build.func().push_values(&[pointer, by]);
3850 build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
3851 }
3852
3853 /// The low bits of a value, which is a step the ranges can put a number on.
3854 fn low_bits(build: &mut Builder<'_>, value: Value, mask: i128) -> Value {
3855 let bits = build.iconst(Type::int(64), mask);
3856 build.binary(Opcode::And, value, bits, Flags::NONE)
3857 }
3858
3859 #[test]
3860 fn a_walk_by_a_step_the_ranges_bound_inside_a_local_goes() {
3861 // Section 7.2's third source. The step is not a constant, so the walk stops at the
3862 // `ptr_add` and the fact that comes out is about a base nobody knows the size of. What
3863 // the ranges say is that the step is somewhere in nought to seven, so the four bytes the
3864 // access wants are somewhere in nought to eleven, and all of that is inside the sixteen
3865 // the slot is.
3866 let (_, mut func, block, _, index) = indexed();
3867 let mut build = Builder::new(&mut func, block);
3868 let slot = local(&mut build, 16);
3869 let step = low_bits(&mut build, index, 7);
3870 let at = walk(&mut build, slot, step);
3871 check(&mut build, at, 4);
3872 build.ret(&[]);
3873 let stats = run(&mut func);
3874 assert_eq!(checks(&func), 0);
3875 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
3876 }
3877
3878 #[test]
3879 fn a_check_whose_capability_is_further_back_than_its_base_is_answered_by_the_ranges() {
3880 // The row #1390 is about. The capability was taken at the pointer, the address is that
3881 // pointer stepped by something nobody can read, and the constant reader stops at the step
3882 // so it has no base and no constant to ask a rule with. Carrying the walk on past the
3883 // step with the ranges lands on the pointer the capability names, and the sixty four
3884 // bytes an earlier check proved hold every address the step can reach.
3885 let (_, mut func, block, pointer, index) = indexed();
3886 let mut build = Builder::new(&mut func, block);
3887 check(&mut build, pointer, 64);
3888 let step = low_bits(&mut build, index, 7);
3889 let at = walk(&mut build, pointer, step);
3890 checking_at(&mut build, pointer, at, 4);
3891 build.ret(&[]);
3892 let stats = run(&mut func);
3893 assert_eq!(checks(&func), 1);
3894 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MIDWAY), 1);
3895 }
3896
3897 #[test]
3898 fn a_step_that_can_reach_past_what_was_checked_keeps_its_check() {
3899 // The same function with the mask widened. The step is somewhere in nought to a hundred
3900 // and twenty seven, the four bytes can start as far out as that, and the check that ran
3901 // proved sixty four. Nothing here says the rest of it belongs to the same instance.
3902 let (_, mut func, block, pointer, index) = indexed();
3903 let mut build = Builder::new(&mut func, block);
3904 check(&mut build, pointer, 64);
3905 let step = low_bits(&mut build, index, 127);
3906 let at = walk(&mut build, pointer, step);
3907 checking_at(&mut build, pointer, at, 4);
3908 build.ret(&[]);
3909 let stats = run(&mut func);
3910 assert_eq!(checks(&func), 2);
3911 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MIDWAY), 0);
3912 // The row that says a fact about this pointer was there and the walk leaves it, rather
3913 // than the row for a pointer nothing has an extent for. This is the honest refusal.
3914 assert_eq!(stats.count(Kind::Missed, super::MIDWAY_OVER), 1);
3915 }
3916
3917 #[test]
3918 fn a_lifetime_check_further_back_than_its_base_is_answered_the_same_way() {
3919 // The other half. The first access puts a lifetime fact in, widened by its own bounds
3920 // check to the sixty four bytes that check proved are one instance, and every address the
3921 // step can reach is inside it.
3922 let (_, mut func, block, pointer, index) = indexed();
3923 let mut build = Builder::new(&mut func, block);
3924 access(&mut build, pointer, 64);
3925 let step = low_bits(&mut build, index, 7);
3926 let at = walk(&mut build, pointer, step);
3927 living_at(&mut build, pointer, at);
3928 build.ret(&[]);
3929 let stats = run(&mut func);
3930 assert_eq!(lives(&func), 1);
3931 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MIDWAY_LIVE), 1);
3932 }
3933
3934 #[test]
3935 fn the_two_reasons_a_midway_lifetime_check_is_left_alone_are_counted_apart() {
3936 // The same pair as on the bounds half, so that all four midway rows are pinned. The first
3937 // function has a lifetime fact about the pointer and a step that walks off the end of it,
3938 // and the second has no fact about the pointer at all. The pass refuses both and the
3939 // rows have to say which refusal it was, because one of them is a range worth tightening
3940 // and the other is an object nothing here will ever have an extent for.
3941 let (_, mut func, block, pointer, index) = indexed();
3942 let mut build = Builder::new(&mut func, block);
3943 access(&mut build, pointer, 64);
3944 let step = low_bits(&mut build, index, 127);
3945 let at = walk(&mut build, pointer, step);
3946 living_at(&mut build, pointer, at);
3947 build.ret(&[]);
3948 let stats = run(&mut func);
3949 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MIDWAY_LIVE), 0);
3950 assert_eq!(stats.count(Kind::Missed, super::MIDWAY_OVER_LIVE), 1);
3951
3952 let (_, mut func, block, pointer, index) = indexed();
3953 let mut build = Builder::new(&mut func, block);
3954 let step = low_bits(&mut build, index, 7);
3955 let at = walk(&mut build, pointer, step);
3956 living_at(&mut build, pointer, at);
3957 build.ret(&[]);
3958 let stats = run(&mut func);
3959 assert_eq!(stats.count(Kind::Missed, super::MIDWAY_NO_EXTENT_LIVE), 1);
3960 assert_eq!(stats.count(Kind::Missed, super::MIDWAY_OVER_LIVE), 0);
3961 }
3962
3963 #[test]
3964 fn a_walk_by_a_step_the_ranges_cannot_bound_is_left_alone() {
3965 // The same function with the mask taken off. A parameter can be anything, so the range of
3966 // addresses the walk reaches is the whole of memory and no slot covers it.
3967 let (_, mut func, block, _, index) = indexed();
3968 let mut build = Builder::new(&mut func, block);
3969 let slot = local(&mut build, 16);
3970 let at = walk(&mut build, slot, index);
3971 check(&mut build, at, 4);
3972 build.ret(&[]);
3973 let stats = run(&mut func);
3974 assert_eq!(checks(&func), 1);
3975 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
3976 }
3977
3978 #[test]
3979 fn a_walk_a_bounded_step_can_take_off_the_end_of_a_local_is_left_alone() {
3980 // Nought to seven again, four bytes again, and a slot of eight this time. The step being
3981 // bounded is not the question. The question is whether every address it can reach is
3982 // inside the slot, and seven plus four is not.
3983 let (_, mut func, block, _, index) = indexed();
3984 let mut build = Builder::new(&mut func, block);
3985 let slot = local(&mut build, 8);
3986 let step = low_bits(&mut build, index, 7);
3987 let at = walk(&mut build, slot, step);
3988 check(&mut build, at, 4);
3989 build.ret(&[]);
3990 let stats = run(&mut func);
3991 assert_eq!(checks(&func), 1);
3992 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
3993 }
3994
3995 #[test]
3996 fn a_constant_step_past_a_bounded_one_is_walked_too() {
3997 // A field of an element of an array of structs, which is the shape this is for. The array
3998 // index needs a range and the field offset does not, and the walk has to get through both.
3999 let (_, mut func, block, _, index) = indexed();
4000 let mut build = Builder::new(&mut func, block);
4001 let slot = local(&mut build, 32);
4002 let step = low_bits(&mut build, index, 15);
4003 let element = walk(&mut build, slot, step);
4004 let field = past(&mut build, element, 8);
4005 check(&mut build, field, 4);
4006 build.ret(&[]);
4007 let stats = run(&mut func);
4008 assert_eq!(checks(&func), 0);
4009 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
4010 }
4011
4012 #[test]
4013 fn what_a_range_discharge_records_is_the_bytes_and_not_the_range() {
4014 // The second check is the same bytes as the first, and the first went because a made up
4015 // range around it was inside the slot. What the first one proved is that those bytes are
4016 // in the slot, so the second one goes on that rather than on the ranges being asked all
4017 // over again.
4018 let (_, mut func, block, _, index) = indexed();
4019 let mut build = Builder::new(&mut func, block);
4020 let slot = local(&mut build, 16);
4021 let step = low_bits(&mut build, index, 7);
4022 let at = walk(&mut build, slot, step);
4023 check(&mut build, at, 4);
4024 check(&mut build, at, 4);
4025 build.ret(&[]);
4026 let stats = run(&mut func);
4027 assert_eq!(checks(&func), 0);
4028 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
4029 assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
4030 }
4031
4032 /// A stack slot whose size the program works out, which is what a variable length array is.
4033 fn growable(build: &mut Builder<'_>, size: Value) -> Value {
4034 let info = MemInfo {
4035 size: 0,
4036 align: 8,
4037 order: MemOrder::NotAtomic,
4038 tbaa: None,
4039 owns: 0,
4040 restrict: Restrict::NONE,
4041 };
4042 let extra = Extra::Mem(build.func().add_mem(info));
4043 let args = build.func().push_values(&[size]);
4044 build.value(InstData { args, extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
4045 }
4046
4047 #[test]
4048 fn a_check_of_bytes_inside_a_local_goes_with_nothing_in_front_of_it() {
4049 // Section 7.2's first source. No check established this and none had to: an `alloca` of
4050 // sixteen bytes is sixteen bytes of one storage instance because that is what it makes.
4051 let (_, mut func, block, _) = blank();
4052 let mut build = Builder::new(&mut func, block);
4053 let slot = local(&mut build, 16);
4054 let field = past(&mut build, slot, 8);
4055 check(&mut build, field, 4);
4056 build.ret(&[]);
4057 let stats = run(&mut func);
4058 assert_eq!(checks(&func), 0);
4059 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
4060 }
4061
4062 #[test]
4063 fn a_check_past_the_end_of_a_local_stays() {
4064 // The slot is sixteen bytes and the access runs to twenty. Nothing about it being a local
4065 // says anything about the four bytes after it, which belong to whatever the frame puts
4066 // there next.
4067 let (_, mut func, block, _) = blank();
4068 let mut build = Builder::new(&mut func, block);
4069 let slot = local(&mut build, 16);
4070 let field = past(&mut build, slot, 16);
4071 check(&mut build, field, 4);
4072 build.ret(&[]);
4073 let stats = run(&mut func);
4074 assert_eq!(checks(&func), 1);
4075 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
4076 }
4077
4078 #[test]
4079 fn a_check_of_bytes_inside_a_local_goes_across_a_call() {
4080 // The other half of what makes the fact worth having. A callee cannot free a frame slot,
4081 // so unlike everything the walk carries this one is not thrown away at a call.
4082 let (mut names, mut func, block, _) = blank();
4083 let mut build = Builder::new(&mut func, block);
4084 let slot = local(&mut build, 16);
4085 let callee = names.intern("might_free");
4086 let signature = build.func().add_signature(Signature::new());
4087 build.call(callee, signature, &[]);
4088 check(&mut build, slot, 4);
4089 build.ret(&[]);
4090 let stats = run(&mut func);
4091 assert_eq!(checks(&func), 0);
4092 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
4093 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
4094 }
4095
4096 #[test]
4097 fn a_check_inside_a_variable_length_array_stays() {
4098 // How many bytes it is is a value the program works out, and the payload's size field
4099 // reads zero. A pass that read it anyway would discharge every check in the array.
4100 let (_, mut func, block, _) = blank();
4101 let mut build = Builder::new(&mut func, block);
4102 let bytes = build.iconst(Type::int(64), 64);
4103 let slot = growable(&mut build, bytes);
4104 check(&mut build, slot, 4);
4105 build.ret(&[]);
4106 let stats = run(&mut func);
4107 assert_eq!(checks(&func), 1);
4108 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
4109 }
4110
4111 #[test]
4112 fn a_lifetime_check_in_a_local_goes_with_nothing_in_front_of_it() {
4113 // The frame slot rule, and the point is that neither of these has a check in front of it.
4114 // A slot is alive until the function returns, so a lifetime check anywhere inside one is
4115 // asking a question the `alloca` already answered.
4116 let (_, mut func, block, _) = blank();
4117 let mut build = Builder::new(&mut func, block);
4118 let slot = local(&mut build, 16);
4119 live(&mut build, slot);
4120 let field = past(&mut build, slot, 12);
4121 live(&mut build, field);
4122 build.ret(&[]);
4123 let stats = run(&mut func);
4124 assert_eq!(lives(&func), 0);
4125 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 2);
4126 }
4127
4128 #[test]
4129 fn a_lifetime_check_inside_a_local_goes_across_a_call() {
4130 // The last of the five ways a lifetime check is taken out, pinned here because the
4131 // argument in the module comment about keeping bounds facts across a call is an argument
4132 // about all five. Three of them survive a call and none of the three is about storage a
4133 // callee could free, which is what makes them harmless to a bounds fact that crossed. This
4134 // is the frame slot one, and the other two already have a test each.
4135 let (mut names, mut func, block, _) = blank();
4136 let mut build = Builder::new(&mut func, block);
4137 let slot = local(&mut build, 16);
4138 let callee = names.intern("might_free");
4139 let signature = build.func().add_signature(Signature::new());
4140 build.call(callee, signature, &[]);
4141 live(&mut build, slot);
4142 build.ret(&[]);
4143 let stats = run(&mut func);
4144 assert_eq!(lives(&func), 0);
4145 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 1);
4146 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 0);
4147 }
4148
4149 #[test]
4150 fn a_lifetime_check_past_the_end_of_a_local_stays() {
4151 // The slot answers for its own bytes and no further, so an address outside it is a
4152 // different instance and a question nothing has answered.
4153 let (_, mut func, block, _) = blank();
4154 let mut build = Builder::new(&mut func, block);
4155 let slot = local(&mut build, 16);
4156 live(&mut build, slot);
4157 let field = past(&mut build, slot, 24);
4158 live(&mut build, field);
4159 build.ret(&[]);
4160 let stats = run(&mut func);
4161 assert_eq!(lives(&func), 1);
4162 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 1);
4163 }
4164
4165 #[test]
4166 fn something_ending_a_lifetime_turns_the_frame_slot_rule_off() {
4167 // The gate, and with it the widening the frame slot rule usually hides. With a `meta_end`
4168 // anywhere in the function the slot answers nothing, so the first check stays and pays,
4169 // and what takes the second one out is the first one widened to the whole slot.
4170 let (_, mut func, block, pointer) = blank();
4171 let mut build = Builder::new(&mut func, block);
4172 let slot = local(&mut build, 16);
4173 live(&mut build, slot);
4174 let field = past(&mut build, slot, 12);
4175 live(&mut build, field);
4176 let size = build.iconst(Type::int(64), 16);
4177 let args = build.func().push_values(&[pointer, size]);
4178 build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
4179 build.ret(&[]);
4180 let stats = run(&mut func);
4181 assert_eq!(lives(&func), 1);
4182 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 0);
4183 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
4184 }
4185
4186 /// Puts `cap_of` and a `check_deriv` for a walk from `from` to `to` into a block.
4187 ///
4188 /// The stride is the width of one element, which is what `rucc-safety` passes and what the
4189 /// runtime uses for a pointer that walked off the near end. This pass does not read it.
4190 fn deriv(build: &mut Builder<'_>, from: Value, to: Value, stride: i128) {
4191 deriving_at(build, from, from, to, stride);
4192 }
4193
4194 /// The same, with the capability taken at `held` rather than at the address the walk starts on.
4195 fn deriving_at(build: &mut Builder<'_>, held: Value, from: Value, to: Value, stride: i128) {
4196 let args = build.func().push_values(&[held]);
4197 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
4198 let width = build.iconst(Type::int(64), stride);
4199 let args = build.func().push_values(&[capability, from, to, width]);
4200 build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
4201 }
4202
4203 /// How many derivation checks are left in a function.
4204 fn derivs(func: &Func) -> usize {
4205 func.blocks()
4206 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
4207 .filter(|&inst| func[inst].opcode == Opcode::CheckDeriv)
4208 .count()
4209 }
4210
4211 #[test]
4212 fn a_walk_inside_a_checked_range_goes() {
4213 // Sixteen bytes were checked, and the walk goes from the start of them to eight in. Both
4214 // ends are in one range, so the second address is in the instance the first belongs to.
4215 let (_, mut func, block, pointer) = blank();
4216 let mut build = Builder::new(&mut func, block);
4217 check(&mut build, pointer, 16);
4218 let field = past(&mut build, pointer, 8);
4219 deriv(&mut build, pointer, field, 4);
4220 build.ret(&[]);
4221 let stats = run(&mut func);
4222 assert_eq!(derivs(&func), 0);
4223 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 1);
4224 }
4225
4226 #[test]
4227 fn a_walk_that_leaves_the_checked_range_stays() {
4228 // Four bytes were checked and the walk goes eight past them. Nothing here says the two
4229 // addresses are in one instance, which is the whole of what the check is about.
4230 let (_, mut func, block, pointer) = blank();
4231 let mut build = Builder::new(&mut func, block);
4232 check(&mut build, pointer, 4);
4233 let field = past(&mut build, pointer, 8);
4234 deriv(&mut build, pointer, field, 4);
4235 build.ret(&[]);
4236 let stats = run(&mut func);
4237 assert_eq!(derivs(&func), 1);
4238 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
4239 }
4240
4241 #[test]
4242 fn a_walk_whose_capability_was_taken_where_the_pointer_came_from_is_read_too() {
4243 // The same two rules on the near end of a walk. Sixteen bytes were checked, the walk runs
4244 // from eight in to twelve in, and the capability is the one the pointer those two came off
4245 // got. The near end has to reach back to that pointer for the answer to be about the
4246 // instance the capability names, which is what the fact it asks does.
4247 let (_, mut func, block, pointer) = blank();
4248 let mut build = Builder::new(&mut func, block);
4249 check(&mut build, pointer, 16);
4250 let field = past(&mut build, pointer, 8);
4251 let next = past(&mut build, pointer, 12);
4252 deriving_at(&mut build, pointer, field, next, 4);
4253 build.ret(&[]);
4254 let stats = run(&mut func);
4255 assert_eq!(derivs(&func), 0);
4256 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 1);
4257 }
4258
4259 #[test]
4260 fn two_ranges_holding_one_end_each_do_not_answer_a_walk() {
4261 // The case the one fact rule is written for. Both addresses have been checked, so both are
4262 // inside some instance, and nothing says it is the same one. The walk stays.
4263 let (_, mut func, block, pointer) = blank();
4264 let mut build = Builder::new(&mut func, block);
4265 check(&mut build, pointer, 4);
4266 let field = past(&mut build, pointer, 64);
4267 check(&mut build, field, 4);
4268 deriv(&mut build, pointer, field, 4);
4269 build.ret(&[]);
4270 let stats = run(&mut func);
4271 assert_eq!(derivs(&func), 1);
4272 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
4273 }
4274
4275 #[test]
4276 fn a_walk_inside_a_local_goes_with_nothing_in_front_of_it() {
4277 // The shape almost every derivation check in real code has: a field of a local struct.
4278 // `rucc-safety` emits the walk before the bounds check on what it produced, so a fact from
4279 // an earlier check is usually the wrong size for it and the local is what answers.
4280 let (_, mut func, block, _) = blank();
4281 let mut build = Builder::new(&mut func, block);
4282 let slot = local(&mut build, 16);
4283 let field = past(&mut build, slot, 8);
4284 deriv(&mut build, slot, field, 4);
4285 build.ret(&[]);
4286 let stats = run(&mut func);
4287 assert_eq!(derivs(&func), 0);
4288 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 1);
4289 }
4290
4291 #[test]
4292 fn a_walk_off_the_end_of_a_local_stays() {
4293 // Where the slot stops is where the fact stops. One past the end is the case the runtime
4294 // has slack for and this pass does not use any of it.
4295 let (_, mut func, block, _) = blank();
4296 let mut build = Builder::new(&mut func, block);
4297 let slot = local(&mut build, 16);
4298 let field = past(&mut build, slot, 16);
4299 deriv(&mut build, slot, field, 4);
4300 build.ret(&[]);
4301 let stats = run(&mut func);
4302 assert_eq!(derivs(&func), 1);
4303 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 0);
4304 }
4305
4306 #[test]
4307 fn a_walk_a_call_stands_between_stays_and_is_counted() {
4308 // The same price the other two kinds pay, reported the same way, so the cost of not
4309 // trusting a call is a number per function rather than a paragraph.
4310 let (mut names, mut func, block, pointer) = blank();
4311 let mut build = Builder::new(&mut func, block);
4312 check(&mut build, pointer, 16);
4313 let callee = names.intern("might_free");
4314 let signature = build.func().add_signature(Signature::new());
4315 build.call(callee, signature, &[]);
4316 let field = past(&mut build, pointer, 8);
4317 deriv(&mut build, pointer, field, 4);
4318 build.ret(&[]);
4319 let stats = run(&mut func);
4320 assert_eq!(derivs(&func), 1);
4321 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_DERIV), 1);
4322 }
4323
4324 #[test]
4325 fn a_check_the_module_says_is_inside_a_global_goes_with_nothing_in_front_of_it() {
4326 // The other half of section 7.2's first source. The size of a global lives on the module
4327 // and this pass is given one function, so the answer arrives as a flag `crate::extents`
4328 // wrote before the pipeline started, and all three kinds carry it.
4329 let (_, mut func, block, pointer) = blank();
4330 let mut build = Builder::new(&mut func, block);
4331 let field = past(&mut build, pointer, 8);
4332 deriv(&mut build, pointer, field, 1);
4333 access(&mut build, field, 4);
4334 build.ret(&[]);
4335 marked(&mut func);
4336 let stats = run(&mut func);
4337 assert_eq!(checks(&func), 0);
4338 assert_eq!(lives(&func), 0);
4339 assert_eq!(derivs(&func), 0);
4340 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_STATIC), 1);
4341 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_STATIC), 1);
4342 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_STATIC), 1);
4343 }
4344
4345 #[test]
4346 fn a_check_the_module_says_every_caller_hands_in_goes_with_nothing_in_front_of_it() {
4347 // Section 7.5's summaries, arriving the same way a global's extent does and for the same
4348 // reason: which object a caller passes is a fact about a different function. What the flag
4349 // says is an extent and a lifetime, because the objects `crate::params` believes are a
4350 // caller's frame slot and a global and both are alive for as long as the call runs.
4351 let (_, mut func, block, pointer) = blank();
4352 let mut build = Builder::new(&mut func, block);
4353 let field = past(&mut build, pointer, 8);
4354 deriv(&mut build, pointer, field, 1);
4355 access(&mut build, field, 4);
4356 build.ret(&[]);
4357 flagged(&mut func, Flags::HANDED);
4358 let stats = run(&mut func);
4359 assert_eq!(checks(&func), 0);
4360 assert_eq!(lives(&func), 0);
4361 assert_eq!(derivs(&func), 0);
4362 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_HANDED), 1);
4363 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_HANDED), 1);
4364 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_HANDED), 1);
4365 }
4366
4367 /// A function that takes an index, allocates `size` bytes and tests the answer against null.
4368 ///
4369 /// Gives back the block where the test has passed, the block where it has not, the pointer and
4370 /// the index. The flag is put on by hand, because which calls deserve it is a question about a
4371 /// module and `crate::heap` is what answers it.
4372 ///
4373 /// The index is there for the tests about a walk by a value. A parameter on its own is any
4374 /// number at all, so a test that wants a bounded one puts [`low_bits`] over it the same way the
4375 /// local tests do.
4376 fn allocation(size: i128) -> (Interner, Func, Block, Block, Value, Value) {
4377 let mut names = Interner::new();
4378 let name = names.intern("f");
4379 let mut func = Func::new(name, Signature::new().with_params(&[Type::int(64)]));
4380 let entry = func.create_block();
4381 let inside = func.create_block();
4382 let outside = func.create_block();
4383 let index = func.append_param(entry, Type::int(64));
4384 let mut build = Builder::new(&mut func, entry);
4385 let signature = build.func().add_signature(
4386 Signature::new().with_params(&[Type::int(64)]).with_returns(&[Type::PTR]),
4387 );
4388 let bytes = build.iconst(Type::int(64), size);
4389 let call = build.call(names.intern("malloc"), signature, &[bytes]);
4390 let at = build.func();
4391 at[call].flags |= Flags::HEAP;
4392 let pointer = at[call].results().next().expect("a call that gives back a pointer");
4393 let zero = build.iconst(Type::int(64), 0);
4394 let null = build.unary(Opcode::IntToPtr, zero, Type::PTR);
4395 let condition = build.icmp(IntPred::Ne, pointer, null);
4396 build.br_if(condition, inside, &[], outside, &[]);
4397 let mut build = Builder::new(&mut func, outside);
4398 build.ret(&[]);
4399 (names, func, inside, outside, pointer, index)
4400 }
4401
4402 #[test]
4403 fn a_check_inside_an_allocation_the_program_tested_goes() {
4404 // The third of the objects whose extent nobody had to check for. `malloc(16)` says how
4405 // many bytes it made in the call, and the branch on null is what makes it true here.
4406 let (_, mut func, inside, _, pointer, _) = allocation(16);
4407 let mut build = Builder::new(&mut func, inside);
4408 let field = past(&mut build, pointer, 8);
4409 deriv(&mut build, pointer, field, 1);
4410 access(&mut build, field, 4);
4411 build.ret(&[]);
4412 let stats = run(&mut func);
4413 assert_eq!(checks(&func), 0);
4414 assert_eq!(derivs(&func), 0);
4415 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 1);
4416 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 1);
4417 // The lifetime check is the one an allocation says nothing about, because a `free` in this
4418 // same function can end it, and it is what reports a use after free.
4419 assert_eq!(lives(&func), 1);
4420 }
4421
4422 #[test]
4423 fn a_check_on_an_allocation_nobody_tested_stays() {
4424 // Down the other arm the pointer is null, a null pointer is inside no object at all, and
4425 // the check is one that is supposed to fail.
4426 let (_, mut func, _, outside, pointer, _) = allocation(16);
4427 let mut build = Builder::new(&mut func, outside);
4428 access(&mut build, pointer, 4);
4429 build.ret(&[]);
4430 let stats = run(&mut func);
4431 assert_eq!(checks(&func), 1);
4432 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
4433 }
4434
4435 #[test]
4436 fn a_check_past_the_end_of_an_allocation_stays() {
4437 // Four bytes at offset fourteen is two bytes past the sixteen that were asked for, and
4438 // those two bytes are what the check is for.
4439 let (_, mut func, inside, _, pointer, _) = allocation(16);
4440 let mut build = Builder::new(&mut func, inside);
4441 let field = past(&mut build, pointer, 14);
4442 access(&mut build, field, 4);
4443 build.ret(&[]);
4444 let stats = run(&mut func);
4445 assert_eq!(checks(&func), 1);
4446 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
4447 }
4448
4449 #[test]
4450 fn a_walk_that_leaves_an_allocation_stays() {
4451 // One end inside and the other past the end is a walk out of the object, which is what a
4452 // derivation check is there to catch, so both ends have to be inside before it goes.
4453 let (_, mut func, inside, _, pointer, _) = allocation(16);
4454 let mut build = Builder::new(&mut func, inside);
4455 let field = past(&mut build, pointer, 32);
4456 deriv(&mut build, pointer, field, 1);
4457 build.ret(&[]);
4458 let stats = run(&mut func);
4459 assert_eq!(derivs(&func), 1);
4460 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 0);
4461 }
4462
4463 #[test]
4464 fn a_check_inside_an_allocation_goes_across_a_call() {
4465 // The other reason a fact read off the instruction is worth having. How many bytes an
4466 // allocator made is not something a callee can change, so unlike a fact from a check that
4467 // ran this one is still there on the far side of a call.
4468 let (mut names, mut func, inside, _, pointer, _) = allocation(16);
4469 let mut build = Builder::new(&mut func, inside);
4470 access(&mut build, pointer, 4);
4471 let signature = build.func().add_signature(Signature::new());
4472 build.call(names.intern("g"), signature, &[]);
4473 access(&mut build, pointer, 4);
4474 build.ret(&[]);
4475 let stats = run(&mut func);
4476 assert_eq!(checks(&func), 0);
4477 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 2);
4478 // Both lifetime checks stay, and the second one is the one a `free` inside `g` would make
4479 // report.
4480 assert_eq!(lives(&func), 2);
4481 }
4482
4483 /// A function that allocates `size` bytes and never looks at what it got back.
4484 ///
4485 /// The shape `bench/safety/a-strided-column-sum.c` has. The size on its own must not answer a
4486 /// check here, because reading through what `malloc` gave back without testing it is the bug
4487 /// this compiler is for.
4488 fn untested(size: i128) -> (Interner, Func, Block, Value, Value) {
4489 let mut names = Interner::new();
4490 let name = names.intern("f");
4491 let mut func = Func::new(name, Signature::new().with_params(&[Type::int(64)]));
4492 let block = func.create_block();
4493 let index = func.append_param(block, Type::int(64));
4494 let mut build = Builder::new(&mut func, block);
4495 let signature = build.func().add_signature(
4496 Signature::new().with_params(&[Type::int(64)]).with_returns(&[Type::PTR]),
4497 );
4498 let bytes = build.iconst(Type::int(64), size);
4499 let call = build.call(names.intern("malloc"), signature, &[bytes]);
4500 let at = build.func();
4501 at[call].flags |= Flags::HEAP;
4502 let pointer = at[call].results().next().expect("a call that gives back a pointer");
4503 (names, func, block, pointer, index)
4504 }
4505
4506 #[test]
4507 fn a_walk_by_a_step_the_ranges_bound_inside_an_allocation_goes() {
4508 // The first half of tamnd/rucc#880. The step is not a constant, so the walk stops at the
4509 // `ptr_add` and what answers the check has to be asked of the range of addresses it can
4510 // reach. That range is nought to seven plus the four bytes the access wants, all of it
4511 // inside the sixteen the call says it made, and the branch on null is what makes the
4512 // sixteen true here.
4513 let (_, mut func, inside, _, pointer, index) = allocation(16);
4514 let mut build = Builder::new(&mut func, inside);
4515 let step = low_bits(&mut build, index, 7);
4516 let at = walk(&mut build, pointer, step);
4517 check(&mut build, at, 4);
4518 build.ret(&[]);
4519 let stats = run(&mut func);
4520 assert_eq!(checks(&func), 0);
4521 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 1);
4522 }
4523
4524 #[test]
4525 fn a_walk_by_a_step_that_can_leave_an_allocation_stays() {
4526 // The same function with the mask widened. Nought to thirty one plus four bytes runs off
4527 // the end of sixteen, and the bytes past the end are what the check is for.
4528 let (_, mut func, inside, _, pointer, index) = allocation(16);
4529 let mut build = Builder::new(&mut func, inside);
4530 let step = low_bits(&mut build, index, 31);
4531 let at = walk(&mut build, pointer, step);
4532 check(&mut build, at, 4);
4533 build.ret(&[]);
4534 let stats = run(&mut func);
4535 assert_eq!(checks(&func), 1);
4536 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
4537 }
4538
4539 #[test]
4540 fn a_derivation_by_a_step_the_ranges_bound_inside_an_allocation_goes() {
4541 // The same for the derivation check, which is the one the column sum is left with. Both
4542 // ends have to be inside and inside the same object: the near end is the pointer itself and
4543 // the far end is anywhere in nought to seven past it.
4544 let (_, mut func, inside, _, pointer, index) = allocation(16);
4545 let mut build = Builder::new(&mut func, inside);
4546 let step = low_bits(&mut build, index, 7);
4547 let at = walk(&mut build, pointer, step);
4548 deriv(&mut build, pointer, at, 1);
4549 build.ret(&[]);
4550 let stats = run(&mut func);
4551 assert_eq!(derivs(&func), 0);
4552 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 1);
4553 }
4554
4555 #[test]
4556 fn a_walk_into_an_allocation_nobody_tested_stays() {
4557 // The other half of the rule, which this does not weaken. A program that walks into what
4558 // `malloc` gave back without ever looking at it is a program that reads through null when
4559 // the allocation fails, and the checks are what report it.
4560 let (_, mut func, block, pointer, index) = untested(16);
4561 let mut build = Builder::new(&mut func, block);
4562 let step = low_bits(&mut build, index, 7);
4563 let at = walk(&mut build, pointer, step);
4564 check(&mut build, at, 4);
4565 deriv(&mut build, pointer, at, 1);
4566 build.ret(&[]);
4567 let stats = run(&mut func);
4568 assert_eq!(checks(&func), 1);
4569 assert_eq!(derivs(&func), 1);
4570 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_MADE), 0);
4571 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_MADE), 0);
4572 }
4573
4574 #[test]
4575 fn a_check_every_caller_hands_in_goes_across_a_call() {
4576 // The reason the flag is worth having at all. A frame slot of the caller is not something
4577 // the callee's own callees can free, so the fact does not die at a call the way a fact
4578 // from a check that ran does.
4579 let (mut names, mut func, block, pointer) = blank();
4580 let mut build = Builder::new(&mut func, block);
4581 access(&mut build, pointer, 4);
4582 let signature = build.func().add_signature(Signature::new());
4583 build.call(names.intern("g"), signature, &[]);
4584 access(&mut build, pointer, 4);
4585 build.ret(&[]);
4586 flagged(&mut func, Flags::HANDED);
4587 let stats = run(&mut func);
4588 assert_eq!(checks(&func), 0);
4589 assert_eq!(lives(&func), 0);
4590 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_HANDED), 2);
4591 assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_HANDED), 2);
4592 }
4593
4594 #[test]
4595 fn a_check_inside_a_global_goes_across_a_call() {
4596 // A callee can free what a global points at and cannot free the global, which lives as
4597 // long as the program does. So this is the one fact besides a local that a call leaves
4598 // standing, and it is read off the instruction rather than out of the scope for that
4599 // reason.
4600 let (mut names, mut func, block, pointer) = blank();
4601 let mut build = Builder::new(&mut func, block);
4602 let callee = names.intern("might_free");
4603 let signature = build.func().add_signature(Signature::new());
4604 build.call(callee, signature, &[]);
4605 access(&mut build, pointer, 4);
4606 build.ret(&[]);
4607 marked(&mut func);
4608 let stats = run(&mut func);
4609 assert_eq!(checks(&func), 0);
4610 assert_eq!(lives(&func), 0);
4611 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
4612 assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 0);
4613 }
4614
4615 #[test]
4616 fn a_check_the_module_marked_costs_fuel_like_any_other() {
4617 // A discharge is a discharge whatever established the fact, so `-fpass-fuel` has to stop
4618 // this one too or a bisection would step over it.
4619 let (_, mut func, block, pointer) = blank();
4620 let mut build = Builder::new(&mut func, block);
4621 access(&mut build, pointer, 4);
4622 build.ret(&[]);
4623 marked(&mut func);
4624 let stats =
4625 DISCHARGE.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
4626 assert_eq!(checks(&func) + lives(&func), 1);
4627 assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
4628 }
4629
4630 #[test]
4631 fn a_walk_whose_two_ends_are_off_two_pointers_with_no_ranges_says_the_same() {
4632 // Nothing in this function steps by a value, so the ranges are never built and the answer
4633 // has to come out of the constant reader alone. That reader stopped for one reason, and it
4634 // is the same reason.
4635 let (_, mut func, block, pointer) = blank();
4636 let other = func.append_param(block, Type::PTR);
4637 let mut build = Builder::new(&mut func, block);
4638 let at = past(&mut build, other, 8);
4639 deriv(&mut build, pointer, at, 4);
4640 build.ret(&[]);
4641 let stats = run(&mut func);
4642 assert_eq!(derivs(&func), 1);
4643 assert_eq!(stats.count(Kind::Missed, super::TWO_BASES_DERIV), 1);
4644 }
4645
4646 #[test]
4647 fn a_walk_whose_two_ends_are_off_two_pointers_says_so() {
4648 // Nothing comparable to ask about. Both ends are readable and each is somewhere inside
4649 // something, and two facts of that shape say nothing at all about it being one something,
4650 // which is the only thing a derivation check wants to know.
4651 let (_, mut func, block, pointer, index) = indexed();
4652 let other = func.append_param(block, Type::PTR);
4653 let mut build = Builder::new(&mut func, block);
4654 let step = low_bits(&mut build, index, 7);
4655 let at = walk(&mut build, other, step);
4656 deriv(&mut build, pointer, at, 4);
4657 build.ret(&[]);
4658 let stats = run(&mut func);
4659 assert_eq!(derivs(&func), 1);
4660 assert_eq!(stats.count(Kind::Missed, super::TWO_BASES_DERIV), 1);
4661 }
4662
4663 #[test]
4664 fn a_walk_off_a_pointer_this_function_was_handed_says_so() {
4665 // The largest pile after a loaded pointer, 1321 checks on SQLite. Everything about the
4666 // shape is readable: one base, a step the ranges bound, both ends off that base. What is
4667 // missing is how many bytes belong to the object, and a pointer that arrived as a
4668 // parameter is one nothing in the function can say that about. Section 7.5's summaries are
4669 // what would.
4670 let (_, mut func, block, pointer, index) = indexed();
4671 let mut build = Builder::new(&mut func, block);
4672 let step = low_bits(&mut build, index, 7);
4673 let at = walk(&mut build, pointer, step);
4674 deriv(&mut build, pointer, at, 4);
4675 build.ret(&[]);
4676 let stats = run(&mut func);
4677 assert_eq!(derivs(&func), 1);
4678 assert_eq!(stats.count(Kind::Missed, super::NO_EXTENT_HANDED), 1);
4679 }
4680
4681 #[test]
4682 fn a_walk_off_a_pointer_this_function_loaded_says_so() {
4683 // The largest pile of the lot, 2155 checks on SQLite, and the shape is `p->field[i]`. The
4684 // extent of what a pointer in memory points at is not written down anywhere the compiler
4685 // can see today, which is what `__counted_by` and the type plane are for.
4686 let (_, mut func, block, pointer, index) = indexed();
4687 let mut build = Builder::new(&mut func, block);
4688 let info = MemInfo {
4689 size: 8,
4690 align: 8,
4691 order: MemOrder::NotAtomic,
4692 tbaa: None,
4693 owns: 0,
4694 restrict: Restrict::NONE,
4695 };
4696 let args = build.func().push_values(&[pointer]);
4697 let extra = Extra::Mem(build.func().add_mem(info));
4698 let held = build.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR);
4699 let step = low_bits(&mut build, index, 7);
4700 let at = walk(&mut build, held, step);
4701 deriv(&mut build, held, at, 4);
4702 build.ret(&[]);
4703 let stats = run(&mut func);
4704 assert_eq!(derivs(&func), 1);
4705 assert_eq!(stats.count(Kind::Missed, super::NO_EXTENT_LOADED), 1);
4706 }
4707
4708 #[test]
4709 fn a_walk_off_a_global_says_so() {
4710 // 485 checks on SQLite, and the one pile of the four where somebody does know the answer.
4711 // A global's extent is on the module, `crate::extents` reads it and writes the fact onto
4712 // every check it can settle before the pipeline starts, and it cannot settle this one
4713 // because it runs before anything has put a number on the index. See tamnd/rucc#878.
4714 let (mut names, mut func, block, _, index) = indexed();
4715 let mut build = Builder::new(&mut func, block);
4716 let extra = Extra::Symbol(names.intern("g"));
4717 let base = build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
4718 let step = low_bits(&mut build, index, 7);
4719 let at = walk(&mut build, base, step);
4720 deriv(&mut build, base, at, 4);
4721 build.ret(&[]);
4722 let stats = run(&mut func);
4723 assert_eq!(derivs(&func), 1);
4724 assert_eq!(stats.count(Kind::Missed, super::NO_EXTENT_GLOBAL), 1);
4725 }
4726
4727 #[test]
4728 fn a_walk_off_a_pointer_the_check_does_not_name_stays() {
4729 // The capability has to be the `cap_of` of the pointer that went in. One naming something
4730 // else is asking about a different instance and is not this pass's to answer.
4731 let (_, mut func, block, pointer) = blank();
4732 let mut build = Builder::new(&mut func, block);
4733 check(&mut build, pointer, 16);
4734 let field = past(&mut build, pointer, 8);
4735 let args = build.func().push_values(&[field]);
4736 let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
4737 let width = build.iconst(Type::int(64), 4);
4738 let args = build.func().push_values(&[capability, pointer, field, width]);
4739 build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
4740 build.ret(&[]);
4741 let stats = run(&mut func);
4742 assert_eq!(derivs(&func), 1);
4743 assert_eq!(stats.count(Kind::Missed, super::NOT_ITS_CAPABILITY_DERIV), 1);
4744 }
4745}