rucc_safety/lib.rs
1//! The memory safety monitor: check insertion over the IR.
2//!
3//! Design: `spec/safe-memory/06-instrumentation.md` section 6.3.
4//!
5//! The one decision this crate exists to make is *when* checks are inserted. Every sanitizer that
6//! came before instruments after the optimizer, so that the optimizer cannot delete its checks,
7//! and pays the full naive cost of every one of them forever. We insert before the optimizer and
8//! let it discharge what it can prove, which is only possible because a check is an instruction
9//! with defined semantics rather than a call the optimizer has no opinion about.
10//!
11//! # What is here so far
12//!
13//! The three checks milestone S1 in `spec/safe-memory/16-milestones.md` asks for: bounds and
14//! lifetime on every access, and a derivation check on every pointer computed from another
15//! pointer. Nothing is discharged, so a function comes out with a check in front of everything,
16//! which is the baseline every elimination claim at S4 is measured against.
17//!
18//! And the boundary, in [`mod@wrap`]: a call the program wrote to one of the C library functions
19//! `rucc-safe-rt` has a row for is pointed at that row's wrapper instead, so the judgements happen
20//! before the call rather than not at all. That is milestone S2 and
21//! `spec/safe-memory/10-boundaries.md` section 10.3 is what it implements.
22//!
23//! And the other end of it, in [`mod@lower`]: after the optimizer has run, every check still standing
24//! becomes a call to the runtime carrying the index of a row in a table this crate puts in the
25//! object. That module is where the reason S1's checks are calls rather than compares is argued.
26//!
27//! And the rest of the boundary, in [`mod@boundary`]: the places where a pointer crosses between
28//! this build and code nobody instrumented, which is a function of this file that somebody else can
29//! call and a call this file makes to a library that has no wrapper. Neither can be modelled, so
30//! each of them is counted instead, which is what section 10.2 says the honest answer to a question
31//! you cannot answer is.
32//!
33//! And what all of that came to, in [`mod@summary`]: the counts `--emit=safety-summary` prints,
34//! which are what `spec/safe-memory/10-boundaries.md` section 10.2 means by a trust set that is
35//! counted per build rather than asserted.
36//!
37//! And the first of the planes, in [`mod@plane`]: every store records what the bytes it wrote were
38//! stored through, which is the judgement C 6.5 says a store makes, and every copy carries whatever
39//! the bytes it read said over to the bytes it wrote, which is the other half of the same rule.
40//! Those two are every write the type plane has, and every read that names a type now asks the
41//! plane whether the bytes agree with it, which is judgement J3. The order was deliberate: a check
42//! against a plane that only some of the writes maintain reports on programs that are correct, so
43//! the writes went in first and the question went in once they were all in.
44//!
45//! And the second of them, over the same two writes: every store records that the bytes it wrote
46//! hold something, and every copy carries whether the bytes it read held anything over to the bytes
47//! it wrote. The init plane is one bit per byte, so a store records the same thing whatever it
48//! stored, and a copy is the reason padding a member by member fill never touched is still padding
49//! nothing wrote after the structure moves. Every read now asks whether anything ever wrote the
50//! bytes it is about to read, which is document 03's Y6, and the writes went in first for the
51//! reason the type plane's did.
52//!
53//! And the one check that is not about a single access, in [`mod@promise`]: a block that declares
54//! `restrict` pointers keeps a record of what each of them reached, and every access through one of
55//! them asks whether another got there first. That is judgement J8 and it is off unless the build
56//! asks for it with `-fsafety-restrict`, which is the only check here that is, and the reason is on
57//! [`rucc_session::Promise`].
58//!
59//! The padding rule of `spec/safe-memory/09-type-init-and-races.md` section 9.3 arrives here as one
60//! number. A store carries how much padding the member it went through owns, and the range it
61//! records is the wider of that and what it wrote, which is all of `-fsafety-init=nopadding`. How
62//! far the padding goes takes a record's layout and this crate reads IR, so the front end is what
63//! decides it and `MemInfo.owns` is how the answer travels.
64//!
65//! The two questions a read asks come apart in one place. A read the front end named no type for
66//! asks the type plane nothing, because the question there is which type the bytes hold, and it
67//! asks the init plane the same thing every other read does, because the question there is about
68//! the bytes rather than about the access. What no `load` in any program asks about is padding: a
69//! read compiled into a `load` reads a member and a member is never padding, so the reads that
70//! cover padding are `memcmp` of two structures, hashing one and handing one to `write`, every one
71//! of which is a call into the movement group of [`mod@wrap`]. Which is why the flag selects what a
72//! store records rather than what a read asks about: the reads that would need it are not `load`s.
73//!
74//! The race check is not here, because the epoch plane is not written at all and a check against a
75//! plane nobody maintains would either report on every access or on none. That is S6. Neither are
76//! the other plane writes: `meta_begin` and `meta_end` for an automatic instance need the escape
77//! analysis of document 08 section 8.4, and until that exists the only instances the runtime knows
78//! about are the ones the allocator reports, which is also why a store to a local records into a
79//! plane that is not there and costs a call that decides nothing.
80//!
81//! # Why the rank matters
82//!
83//! `rucc-safety` is rank 10, alongside `rucc-lower` and `rucc-opt`, so it can depend on neither.
84//! That is the constraint and not an inconvenience: it consumes IR and produces IR, it never sees
85//! the AST, and `rucc-driver` at rank 13 is what sequences it between the two.
86//! `spec/safe-memory/15-integration.md` section 15.1 argues it out.
87//!
88//! # Stability
89//!
90//! Every crate in the workspace is published, and publishing implies a promise. This one is
91//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
92//! Depend on the `rucc` binary's behaviour, not on this.
93
94#![doc(html_root_url = "https://docs.rs/rucc-safety/0.10.75")]
95
96pub mod boundary;
97pub mod ending;
98pub mod frame;
99pub mod handover;
100pub mod lower;
101pub mod origin;
102pub mod plane;
103pub mod promise;
104pub mod slot;
105pub mod summary;
106pub mod wrap;
107
108pub use boundary::{Sites, WITNESS, witness};
109pub use lower::{Descriptor, SECTION, lower};
110pub use plane::Plane;
111pub use promise::{Kept, promise};
112pub use summary::{Frames, Summary, summarize};
113pub use wrap::{INTERPOSED, PREFIX, redirect};
114
115use rucc_ir::{Def, Extra, Func, Imm, Inst, InstData, Module, Opcode, Type, Value};
116pub use rucc_session::{Promise, Races, Subobject};
117
118/// How many checks a run of [`insert`] put in.
119///
120/// Reported rather than discarded because the number of checks a function starts with is the
121/// denominator of everything document 13 measures, and it is not recoverable later: by the time
122/// the optimizer has run, the checks that were discharged are gone and nothing says how many
123/// there were.
124///
125/// The three counts are kept apart rather than added up because they are discharged by different
126/// rules and at very different rates. Document 07 expects bounds to go away often, lifetime to go
127/// away when the instance does not escape, and derivation to survive, so one number would hide
128/// exactly the thing the measurement is for.
129#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
130pub struct Counts {
131 /// Accesses that were given a bounds check.
132 pub checked: usize,
133 /// Accesses that were given a lifetime check, which is the same set as `checked`.
134 pub live: usize,
135 /// Calls that end a storage instance and were given a check in front of them.
136 ///
137 /// Not filled in by [`insert`], which is the one count here that is not. It comes from
138 /// [`mod@ending`], a pass of its own for the reason that module gives, and the driver puts what
139 /// that returns in here so that a check class is reported beside the other check classes rather
140 /// than off to one side.
141 pub freed: usize,
142 /// Pointers computed from another pointer that were given a derivation check.
143 pub derived: usize,
144 /// Accesses that got nothing, because the pointer they go through is not a value this pass
145 /// can take the capability of.
146 pub skipped: usize,
147 /// Stores that recorded what the bytes they wrote were stored through.
148 ///
149 /// Not in `--emit=safety-summary` yet, which is the one count here that is not. The summary
150 /// reports a class as a pair, how many went in and how many are left, and nothing discharges a
151 /// plane write today, so the pair would be one number written twice. It goes in beside the
152 /// first rule that removes one.
153 pub judged: usize,
154 /// Copies that carried whatever the bytes they read said over to the bytes they wrote.
155 ///
156 /// Kept apart from `judged` for the reason the three check counts are kept apart. A store
157 /// records a type the compiler knows and a copy records one only the plane knows, so the two
158 /// are discharged by different rules: a store into storage nothing watches can be dropped by
159 /// looking at the store, and a copy cannot be looked at the same way.
160 pub carried: usize,
161 /// Accesses that asked the plane whether the bytes agree with the type they name.
162 ///
163 /// Fewer than `checked`, and the reason is in `ask`: an access the front end did not name a
164 /// type for has no question to put. Without `-fsafety-subobject` it is fewer again, because
165 /// only a read asks, and the reason a store asks only when somebody asked for it is on
166 /// [`rucc_session::Subobject`].
167 pub asked: usize,
168 /// Stores that recorded that the bytes they wrote hold what they wrote.
169 ///
170 /// The same set as `checked` minus the reads, and unlike `judged` it does not thin: a store
171 /// records into the init plane whatever it was storing through, because what the init plane
172 /// holds is whether anything was stored at all.
173 pub wrote: usize,
174 /// Reads that asked the plane whether anything ever wrote the bytes they are about to read.
175 ///
176 /// The same set as the reads in `checked`, and unlike `asked` it does not thin: the question is
177 /// whether the bytes hold anything at all, which is a question about every read whatever type
178 /// the front end did or did not name for it.
179 pub filled: usize,
180 /// Copies that carried whether the bytes they read held anything over to the bytes they wrote.
181 ///
182 /// The same set as `carried`, and counted beside it for the reason `wrote` is counted beside
183 /// `judged`: the two planes will be discharged by different rules, so the day one of them
184 /// thins the numbers have to be able to differ.
185 pub moved: usize,
186 /// Copies that carried the capability beside every pointer they moved over to the bytes they
187 /// wrote.
188 ///
189 /// The same set again, and counted apart from the two of them because it is not a plane write:
190 /// what it moves is the aux, which is the storage `saved` fills one word at a time. A build with
191 /// many of these and few of `saved` is a build whose pointers mostly travel inside structures
192 /// being copied whole, which is a different shape of program and worth being able to see.
193 pub relocated: usize,
194 /// Accesses that asked their block whether another `restrict` pointer of it got there first.
195 ///
196 /// Zero without `-fsafety-restrict`, and zero in the overwhelming majority of functions with
197 /// it, because the only accesses that ask are the ones the front end traced back to a
198 /// `restrict` declaration. [`mod@promise`] is where both of those are argued.
199 pub promised: usize,
200 /// Accesses of a pointer that asked whether another thread reached the same granule first.
201 ///
202 /// Zero without `-fsafety-races`. Every store that stamps also asks, so with `metadata` this
203 /// equals `stamped`, and `pointer` adds the loads, which is the one thing separating the two
204 /// modes. [`mod@rucc_session`]'s `Races` is where that is argued.
205 pub watched: usize,
206 /// Stores of a pointer that recorded which thread wrote it and how far that thread had counted.
207 ///
208 /// Zero without `-fsafety-races`, and far fewer than `wrote` with it, because the epoch plane
209 /// watches pointer shaped words and not every byte a program stores. [`mod@rucc_session`]'s
210 /// `Races` is where that is argued.
211 pub stamped: usize,
212 /// Halves of a synchronization edge put around an atomic, which is nought, one or two of them
213 /// per atomic depending on what the program asked that atomic to order.
214 ///
215 /// Zero without `-fsafety-races`, and zero with it in a program that uses no atomics, which is
216 /// most of them. Counted apart from `stamped` because it is not a plane write: the two of them
217 /// answer different questions and a build with a great many of one and none of the other is a
218 /// build somebody should be able to see.
219 pub edged: usize,
220 /// Stores of a pointer that wrote the pointer's capability into the slot beside it.
221 ///
222 /// A subset of `wrote`, and on ordinary code a small one, since most of what a program stores is
223 /// not a pointer. Counted apart from `stamped`, which is the other thing only a pointer store
224 /// does, because the two are paid for by different builds: that one is zero without
225 /// `-fsafety-races` and this one is not optional, being where the capability of everything read
226 /// back out of memory comes from.
227 pub saved: usize,
228 /// Reads of a pointer that took its capability out of the slot beside it.
229 ///
230 /// The one count here that is a saving rather than a cost. Every one of these is a `cap_of` that
231 /// did not go in, and a `cap_of` over a pointer nothing else produced is a walk of the lifetime
232 /// plane. What it becomes instead is a slot read, or the same walk when the word turns out to
233 /// have no slot, which is every local and every global today.
234 pub recalled: usize,
235 /// Blocks that opened a scope, which is one per `restrict` clique that has an access in it.
236 ///
237 /// Kept apart from `promised` because it is the part of the cost that is paid per call rather
238 /// than per access: two calls and a stack slot, against which a block that checks a thousand
239 /// accesses and a block that checks one look very different.
240 pub scoped: usize,
241}
242
243impl Counts {
244 /// Adds another function's counts to these.
245 fn add(&mut self, other: Counts) {
246 self.checked += other.checked;
247 self.live += other.live;
248 self.freed += other.freed;
249 self.derived += other.derived;
250 self.skipped += other.skipped;
251 self.judged += other.judged;
252 self.carried += other.carried;
253 self.asked += other.asked;
254 self.wrote += other.wrote;
255 self.filled += other.filled;
256 self.moved += other.moved;
257 self.relocated += other.relocated;
258 self.stamped += other.stamped;
259 self.saved += other.saved;
260 self.recalled += other.recalled;
261 self.watched += other.watched;
262 self.edged += other.edged;
263 self.promised += other.promised;
264 self.scoped += other.scoped;
265 }
266
267 /// Adds what the `restrict` walk of one function came to.
268 ///
269 /// A second function rather than a second [`Counts`] because that walk counts two things and
270 /// has no opinion about the other ten, and a conversion that filled in ten zeroes would let a
271 /// later count be lost by being added to a zero.
272 fn add_kept(&mut self, kept: Kept) {
273 self.promised += kept.promised;
274 self.scoped += kept.scoped;
275 }
276}
277
278/// Puts checks in every function a module defines.
279///
280/// The whole module rather than a function at a time, because that is the unit the driver hands
281/// around and because the pass has nothing to say about the order: no check depends on anything
282/// outside the function it is in. A declaration has no body and is skipped, for the same reason
283/// the back end skips it.
284///
285/// Whether this runs at all is `-fsafety=`, and the driver decides it. This crate does not read
286/// the flag, because a pass that decides for itself whether it runs is a pass whose effect cannot
287/// be read off the pipeline.
288pub fn run(module: &mut Module, subobject: Subobject, promise: Promise, races: Races) -> Counts {
289 // Before the walk, because the entries live in the module and a function is borrowed out of
290 // the module while its stores are being instrumented. It is also the reason this is the entry
291 // point rather than [`insert`]: there is one plane per module and every function records into
292 // the same one.
293 let plane = Plane::build(module);
294 // The one thing a pointer typed access cannot work out for itself, which is how wide it is.
295 let width = u64::from(module.datalayout.pointer_bits / 8);
296 let mut counts = Counts::default();
297 for id in module.funcs() {
298 if !module[id].is_declaration() {
299 counts.add(insert(&mut module[id], &plane, width, subobject, promise, races));
300 }
301 }
302 counts
303}
304
305/// Puts checks in front of every access and every derivation in a function.
306///
307/// Section 6.3: every `load` and `store` gets `check_bounds` and `check_live`, with the
308/// capability coming from the pointer operand, and every `ptr_add` gets `check_deriv` on the
309/// pointer it was computed from. The size and the alignment are the access's own, since a check
310/// that asked about a different number of bytes from the access it guards would be checking
311/// something the program does not do.
312///
313/// A capability belongs to a pointer rather than to an access, so two checks through the same
314/// pointer read one `cap_of` and a walk reads the one belonging to what it walked off.
315/// [`mod@origin`] is where it is made and where that is argued.
316///
317/// The two access checks are separate instructions rather than one fused check, which section
318/// 6.2.2 asks for and which matters more than it looks: the common case document 07 is built
319/// around is that the bounds check is discharged and the lifetime check is not, or the other way
320/// round for a local whose frame the compiler can see. One instruction would mean keeping both
321/// whenever either survived. Where both do survive, the backend fuses them behind one branch.
322///
323/// Nothing is discharged here. A `check_bounds` on a pointer whose bounds are statically obvious
324/// is still emitted, and the fact propagation in `rucc-opt` is what removes it. That split is the
325/// whole design: this pass is a walk anybody can read, and the deletions are rules that are
326/// verified.
327pub fn insert(
328 func: &mut Func,
329 plane: &Plane,
330 width: u64,
331 subobject: Subobject,
332 promise: Promise,
333 races: Races,
334) -> Counts {
335 let mut counts = Counts::default();
336 // One table for the whole function, because a capability belongs to a pointer and not to an
337 // access: two checks through the same pointer read the same one. [`mod@origin`] is where that
338 // is argued and where the placement that makes it sound is.
339 let mut origins = origin::Origins::new();
340 let insts: Vec<Inst> =
341 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
342 for inst in insts {
343 match func[inst].opcode {
344 Opcode::Load | Opcode::Store => match pointer_of(func, inst) {
345 Some(pointer) => {
346 let capability = check(func, &mut origins, inst, pointer, width);
347 counts.checked += 1;
348 counts.live += 1;
349 if func[inst].opcode == Opcode::Store {
350 // In front of the store, and only when the build asked for it. Every other
351 // question at a store is a recording made afterwards, and this is the one
352 // that can refuse, so it has to be asked while the bytes still say what
353 // they said before.
354 if subobject.asks() && ask(func, plane, inst, pointer, capability, width) {
355 counts.asked += 1;
356 }
357 // Also in front, and in second so that it lands nearest the store of the
358 // two that can refuse. It has to be on this side of the store for the
359 // reason [`raced`] gives, and being the last question asked is what makes
360 // the stamp it reads the one that was there when the store began.
361 if races.records() && raced(func, inst, pointer, capability, width) {
362 counts.watched += 1;
363 }
364 // First of everything that goes after the store, so it ends up furthest
365 // from it. It is the only one of these that is not a plane write: the rest
366 // record something about the bytes, keyed on the address, and this writes
367 // the slot that sits beside the word. Nothing here reads what anything else
368 // here wrote, so the order is a matter of what reads well rather than of
369 // what is correct.
370 if saved(func, &mut origins, inst, pointer, capability) {
371 counts.saved += 1;
372 }
373 // The init plane's write goes in first so that the type plane's ends up in
374 // front of it, since both are inserted after the store and the one that
375 // goes in second is the one that lands nearer to it.
376 if wrote(func, inst, pointer, width) {
377 counts.wrote += 1;
378 }
379 if judge(func, plane, inst, pointer, width) {
380 counts.judged += 1;
381 }
382 // Last, so that it ends up nearest the store of the three recordings, which
383 // is where the one that is read by another thread belongs. The question
384 // above it went in front of the store, so it reads the plane before this
385 // overwrites what it read.
386 if races.records() && stamped(func, inst, pointer, width) {
387 counts.stamped += 1;
388 }
389 } else {
390 // Both go in front of the read, and the one that goes in second is the one
391 // that lands nearer to it, so this order prints the type question and then
392 // the init question. Either order is correct: neither reads what the other
393 // wrote and the read happens after both.
394 if ask(func, plane, inst, pointer, capability, width) {
395 counts.asked += 1;
396 }
397 if filled(func, inst, pointer, capability, width) {
398 counts.filled += 1;
399 }
400 // Only under `-fsafety-races=pointer`, which is the mode that reports C2.
401 // The other one watches what a store does and leaves a load alone.
402 if races.reads() && raced(func, inst, pointer, capability, width) {
403 counts.watched += 1;
404 }
405 // After the read, since what it is about is the value the read produced.
406 // The only one of these that puts something in behind the access rather
407 // than in front of it, and the only one that answers rather than asks.
408 if recalled(func, &mut origins, inst, pointer, capability) {
409 counts.recalled += 1;
410 }
411 }
412 }
413 None => counts.skipped += 1,
414 },
415 Opcode::Memcpy | Opcode::Memmove => {
416 // The two ends of a copy are two accesses of the same width, a read through one
417 // pointer and a write through the other, so each gets the pair an access of its own
418 // would get. The destination's pair goes in first and so stands first in the
419 // stream, which is the order a report reads best in when both ends are bad: the
420 // write is what the program was for and the read is how it got there. Both pairs
421 // carry the one payload the copy has, since the width is the same width twice and
422 // the two ends of a copy the front end wrote have the same type as each other.
423 for through in func.bulk(inst).into_iter().flat_map(|bulk| [bulk.to, bulk.with]) {
424 if spanned(func, &mut origins, inst, through) {
425 counts.checked += 1;
426 counts.live += 1;
427 } else {
428 counts.skipped += 1;
429 }
430 }
431 // The aux first of the three behind it, so that it ends up behind the two plane
432 // copies in the stream. Any order is right here, since none of the three reads what
433 // another writes and all three happen after the copy, and this one matches the
434 // order the wrapper around the library's own `memcpy` does its three in.
435 if relocation(func, inst) {
436 counts.relocated += 1;
437 }
438 // Second for the reason a store's two are in the order they are in.
439 if moved(func, inst) {
440 counts.moved += 1;
441 }
442 if carry(func, inst) {
443 counts.carried += 1;
444 }
445 }
446 Opcode::Memset => {
447 // The two checks in front, in the order a store's two go in, because a fill is a
448 // write of as many bytes as it says through a pointer somebody handed it and a
449 // fill that runs off the end of an object is the overflow this build exists to
450 // report.
451 let filled = func.bulk(inst).map(|bulk| bulk.to);
452 if filled.is_some_and(|to| spanned(func, &mut origins, inst, to)) {
453 counts.checked += 1;
454 counts.live += 1;
455 } else {
456 counts.skipped += 1;
457 }
458 // Both plane writes go in behind it, and they go in together or not at all.
459 if recorded(func, plane, inst) {
460 counts.wrote += 1;
461 counts.judged += 1;
462 }
463 }
464 Opcode::PtrAdd => {
465 if derivation(func, &mut origins, inst) {
466 counts.derived += 1;
467 } else {
468 counts.skipped += 1;
469 }
470 }
471 // The edges, which are nothing like the rest of this walk: they put no question and
472 // record nothing about the bytes the atomic touched. What they do is tell the epoch
473 // plane that two threads were ordered here, which is the one thing a plane made of
474 // per thread counters cannot work out for itself. Both modes emit them, because what
475 // separates the modes is which classes are reported and an edge reports nothing.
476 Opcode::AtomicLoad | Opcode::AtomicStore | Opcode::AtomicRmw | Opcode::Cmpxchg
477 if races.records() =>
478 {
479 counts.edged += edges(func, inst);
480 }
481 // The same edge without a key. A fence is the other way a C program orders two
482 // threads without calling anything, and the reason it is a separate arm is that it
483 // has no address in it at all.
484 Opcode::Fence if races.records() => {
485 counts.edged += fenced(func, inst);
486 }
487 _ => {}
488 }
489 }
490 // Last, so that the check it puts in front of an access lands after the bounds check that is
491 // already there. It is its own walk rather than another arm above because what it puts in is
492 // not one check per access: the scopes are per function and the two calls that keep one go in
493 // the entry block and at every exit.
494 if promise.checks() {
495 counts.add_kept(promise::promise(func, width));
496 }
497 counts
498}
499
500/// The pointer an access goes through.
501///
502/// A `load` reads through its first operand and a `store` writes through its second, the value
503/// being written coming first because that is the order the text writes them in.
504fn pointer_of(func: &Func, access: Inst) -> Option<Value> {
505 let args = &func[func[access].args];
506 let at = match func[access].opcode {
507 Opcode::Load => 0,
508 Opcode::Store => 1,
509 _ => return None,
510 };
511 let &value = args.get(at)?;
512 func[value].ty.is_ptr().then_some(value)
513}
514
515/// Puts `check_bounds` and `check_live` immediately before one access, over the pointer's
516/// capability.
517///
518/// Gives back the capability the two checks read, so that a third check on the same access can read
519/// the same one rather than taking it again. An access with no payload gets nothing and answers
520/// nothing, which is the shape a caller has to handle anyway.
521///
522/// The capability comes out of `origins` rather than being taken here, so an access through a
523/// pointer some earlier access already asked about reads what that one read. Where it is made and
524/// why that is sound is [`mod@origin`].
525fn check(
526 func: &mut Func,
527 origins: &mut origin::Origins,
528 access: Inst,
529 pointer: Value,
530 width: u64,
531) -> Option<Value> {
532 let span = func.span(access);
533 let Extra::Mem(info) = func[access].extra else { return None };
534 let mut info = func[info];
535 info.size = covered(func, access, info.size, width);
536 // Not the padding after it. What a check is about is the bytes the access touches, and the
537 // padding is about what a store records rather than about what it reads or writes.
538 info.owns = 0;
539
540 let capability = origins.of(func, pointer, access);
541
542 // The check reads the same bytes the access does, so it carries the access's own payload
543 // rather than a copy of it that could later disagree.
544 let args = func.push_values(&[capability, pointer]);
545 let extra = Extra::Mem(func.add_mem(info));
546 let bounds =
547 func.create_inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[], span);
548 func.insert_before(bounds, access);
549
550 // No payload on this one. Whether the capability still names whoever owns the address is a
551 // question about the pointer and not about how many bytes are being read through it.
552 let args = func.push_values(&[capability, pointer]);
553 let live = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[], span);
554 func.insert_before(live, access);
555
556 Some(capability)
557}
558
559/// Puts `check_bounds` and `check_live` immediately before one fill, over the bytes it covers.
560///
561/// The same two instructions [`check`] puts in front of a `load` or a `store`, and a separate
562/// function rather than a case in that one because of where the byte count comes from. That one
563/// takes it out of the access's payload, and half the fills in the IR hold their count in an
564/// operand instead, since an object whose length the program works out is zeroed by a fill of a
565/// length the program works out. `check_bounds` has an operand for exactly that, which
566/// `spec/safe-memory/07-check-elimination.md` section 7.4 put there for the one check that stands
567/// for a loop, and a fill is the other thing that needs it. The payload still travels either way,
568/// because it holds the alignment and what the front end named the bytes.
569///
570/// The lifetime check has no payload for the reason it has none in front of an access: whether the
571/// capability still names whoever owns the address is a question about the pointer rather than
572/// about how many bytes are going through it.
573fn spanned(func: &mut Func, origins: &mut origin::Origins, bulk: Inst, through: Value) -> bool {
574 let Some(length) = func.bulk(bulk).map(|bulk| bulk.length) else { return false };
575 let Extra::Mem(info) = func[bulk].extra else { return false };
576 let mut info = func[info];
577 // Not the padding after it, for the reason [`check`] gives about an access.
578 info.owns = 0;
579
580 let span = func.span(bulk);
581 let capability = origins.of(func, through, bulk);
582
583 let mut operands = vec![capability, through];
584 operands.extend(length);
585 let args = func.push_values(&operands);
586 let extra = Extra::Mem(func.add_mem(info));
587 let bounds =
588 func.create_inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[], span);
589 func.insert_before(bounds, bulk);
590
591 let args = func.push_values(&[capability, through]);
592 let live = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[], span);
593 func.insert_before(live, bulk);
594
595 true
596}
597
598/// Puts the two plane writes a fill owes in behind it, and says whether it did.
599///
600/// One function rather than two calls beside each other in the walk, because both read the same
601/// length and the length of a fill whose payload holds it is a constant that has to be made:
602/// asking for it twice would make it twice.
603///
604/// The init plane's write goes in first so that the type plane's ends up in front of it, since both
605/// are inserted after the same instruction and the one that goes in second is the one that lands
606/// nearer to it. That is the order a store's two are in, and it is a matter of what reads well
607/// rather than of what is correct, since neither reads what the other wrote.
608fn recorded(func: &mut Func, plane: &Plane, fill: Inst) -> bool {
609 let Some(to) = func.bulk(fill).map(|bulk| bulk.to) else { return false };
610 let Some((made, length)) = copied(func, fill) else { return false };
611 scrubbed(func, fill, made, to, length);
612 untyped(func, plane, fill, made, to, length);
613 true
614}
615
616/// Puts a `meta_init` immediately after one fill, recording that the bytes it covered hold
617/// something.
618///
619/// What [`wrote`] does for a store, said about a range the fill already carries rather than about a
620/// width worked out from a type. A fill writes a byte into every one of those bytes, so every one
621/// of them holds what was written, and a plane that went on saying otherwise is what made
622/// `struct S s = {0};` leave a zeroed object the monitor thought was never written.
623///
624/// The length and the instruction to go in after are handed over rather than worked out, because
625/// the type plane's write beside this one reads the same length and a second [`copied`] would make
626/// a second constant for it.
627fn scrubbed(func: &mut Func, fill: Inst, made: Inst, to: Value, length: Value) {
628 let span = func.span(fill);
629 let args = func.push_values(&[to, length]);
630 let data = InstData { args, ..InstData::new(Opcode::MetaInit) };
631 let written = func.create_inst(data, &[], span);
632 func.insert_after(written, made);
633}
634
635/// Puts a `meta_type` immediately after one fill, recording that the bytes it covered hold no type.
636///
637/// The type-plane operation `spec/safe-memory/06-instrumentation.md` section 6.3 asks a fill for,
638/// and the entry is the untyped one every time rather than whatever the front end named. A fill
639/// writes a byte over a range and a byte is not a value of any type, so C 6.5 leaves the bytes with
640/// no effective type at all and the plane's untyped entry is the one that says so.
641///
642/// What it is really for is taking away what was there before. Without it the plane goes on
643/// describing an object that has been filled over, so a program that stores a `float` through a
644/// piece of allocated storage, fills the whole thing and reads it back as an `int` is refused for
645/// reading bytes at a type they do not hold, which is over-reporting and is the direction
646/// `spec/safe-memory/09-type-init-and-races.md` section 9.1 says this design does not go in. The
647/// untyped entry is compatible with every access, so what it costs is a question that is not asked.
648fn untyped(func: &mut Func, plane: &Plane, fill: Inst, made: Inst, to: Value, length: Value) {
649 let span = func.span(fill);
650 let args = func.push_values(&[to, length]);
651 let extra = Extra::Node(plane.entry(None));
652 let data = InstData { args, extra, ..InstData::new(Opcode::MetaType) };
653 let judged = func.create_inst(data, &[], span);
654 func.insert_after(judged, made);
655}
656
657/// Puts a `meta_type` immediately after one store, recording what its bytes were stored through.
658///
659/// The judgement of C 6.5: a store through an lvalue of type `T` sets the effective type of what it
660/// wrote to `T`, and the plane is where that is written down. What the store names is the aliasing
661/// node the walk put on it, and [`Plane::entry`] is the translation from that to the entry the
662/// plane holds, including the two cases that are not a type.
663///
664/// After the store rather than before it, which is the one thing about the placement that matters.
665/// The bytes are stored through that type once the store has happened, and a plane that said so
666/// first would be describing a store that the bounds check in front of it may yet refuse.
667///
668/// The length is a value rather than a field of the payload because that is the shape the opcode
669/// has, and it is a `meta_type` over a range because one store writes a run of bytes. Where the
670/// value comes from is [`extent`].
671fn judge(func: &mut Func, plane: &Plane, store: Inst, pointer: Value, width: u64) -> bool {
672 let Extra::Mem(info) = func[store].extra else { return false };
673 let size = covered(func, store, func[info].size, width);
674 // A store whose width nothing states covers no bytes anybody can name, and a plane write over
675 // nothing is an instruction with no effect.
676 if size == 0 {
677 return false;
678 }
679 let node = plane.entry(func[info].tbaa);
680
681 let span = func.span(store);
682 let (made, length) = extent(func, store, size);
683 let args = func.push_values(&[pointer, length]);
684 let data = InstData { args, extra: Extra::Node(node), ..InstData::new(Opcode::MetaType) };
685 let judged = func.create_inst(data, &[], span);
686 // After the constant it reads rather than after the store, since both go in the same place and
687 // the one that goes in second ends up in front.
688 func.insert_after(judged, made);
689 true
690}
691
692/// Puts a `check_type` immediately before one read, asking whether the bytes agree with the type
693/// they are about to be read as.
694///
695/// Judgement J3, and the half of the type plane that decides something. The two writes record what
696/// a store and a copy left behind, and this is the question they were recorded for: the effective
697/// type rule of C 6.5 says an object's stored value may only be read through a type compatible with
698/// the one it was stored through, and the plane is where the compiler wrote down which that was.
699///
700/// # Why only a read
701///
702/// A store does not ask, it answers. The plane covers storage the allocator reported and nothing
703/// else, which is exactly the storage C gives no declared type, and the effective type of such an
704/// object is whatever the last store through it set. So a store cannot disagree with the plane: it
705/// is what makes the plane say what it says, and a check in front of one would refuse the reuse of
706/// a buffer that the standard permits.
707///
708/// # Why a read that names no type asks nothing
709///
710/// An access whose payload carries no aliasing node is an access the front end did not say the type
711/// of, which is a copy of an aggregate, an array, or anything else reached by address. That is not
712/// the same as reading bytes nothing has been stored through, and the plane's untyped entry means
713/// the second one. Asking with it would refuse every read of a structure whose members had been
714/// stored through their own types, which is every correct program that has one.
715///
716/// The check goes in front of the read, behind the bounds and lifetime checks that are also in
717/// front of it. A question about what the bytes say is worth asking only once somebody owns them,
718/// and what the runtime answers for an address no region covers is nothing rather than a refusal.
719fn ask(
720 func: &mut Func,
721 plane: &Plane,
722 read: Inst,
723 pointer: Value,
724 capability: Option<Value>,
725 width: u64,
726) -> bool {
727 let Some(capability) = capability else { return false };
728 let Extra::Mem(at) = func[read].extra else { return false };
729 let mut info = func[at];
730 let Some(node) = info.tbaa else { return false };
731 info.size = covered(func, read, info.size, width);
732 // A read whose width nothing states reads no bytes anybody can name, the same way a store of
733 // none writes none.
734 if info.size == 0 {
735 return false;
736 }
737 // The payload the check carries is the access's, with the aliasing node replaced by the plane
738 // entry for it, because the plane and the aliasing tree are two vocabularies and the question is
739 // put in the plane's.
740 info.tbaa = Some(plane.entry(Some(node)));
741 // As in `access_checks`, and here it could never be anything else: a read carries no padding.
742 info.owns = 0;
743
744 let span = func.span(read);
745 let args = func.push_values(&[capability, pointer]);
746 let extra = Extra::Mem(func.add_mem(info));
747 let data = InstData { args, extra, ..InstData::new(Opcode::CheckType) };
748 let asked = func.create_inst(data, &[], span);
749 func.insert_before(asked, read);
750 true
751}
752
753/// Puts a `meta_type_copy` immediately after one copy, carrying what its source said to its
754/// destination.
755///
756/// The other half of the judgement C 6.5 describes. A copy does not store through a type, so there
757/// is no type for the compiler to record: what the copied bytes are is whatever the bytes they came
758/// from were, and the only place that is written down is the plane over the source. So this names
759/// two ranges and no node, and the runtime moves the entries across.
760///
761/// Without it the destination would keep whatever the bytes there said before the copy, which is
762/// the thing that makes a check against the plane unusable. A structure copied into a fresh
763/// allocation would come out untyped at best and, once the allocation had been reused, wrong at
764/// worst, and the very next read of a field would be refused on a program that is correct.
765///
766/// After the copy rather than before it, for the same reason a store's judgement goes after the
767/// store. The bytes say the new thing once the copy has happened. Reading the source's plane
768/// afterwards is the same answer as reading it before, overlap included, because a copy writes no
769/// plane entries of its own.
770fn carry(func: &mut Func, copy: Inst) -> bool {
771 let Some(bulk) = func.bulk(copy) else { return false };
772 let (to, from) = (bulk.to, bulk.with);
773 let Some((made, length)) = copied(func, copy) else { return false };
774
775 let span = func.span(copy);
776 let args = func.push_values(&[to, from, length]);
777 let data = InstData { args, ..InstData::new(Opcode::MetaTypeCopy) };
778 let carried = func.create_inst(data, &[], span);
779 // After the constant it reads rather than after the copy, since both go in the same place and
780 // the one that goes in second ends up in front.
781 func.insert_after(carried, made);
782 true
783}
784
785/// Puts a `meta_init` immediately after one store, recording that its bytes hold what it wrote.
786///
787/// The judgement of `spec/safe-memory/09-type-init-and-races.md` section 9.2, and the write the
788/// init plane is made of. An instance beginning is the only thing that makes a byte unwritten, and
789/// this is the only thing that makes one written again, so between the two of them the plane holds
790/// exactly the bytes the monitor watched a store land on.
791///
792/// After the store, and for the same reason the type plane's judgement goes after one: the bytes
793/// hold what was written once the store has happened, and saying so first would be describing a
794/// store the bounds check in front of it may yet refuse.
795///
796/// # Where the padding rule lives
797///
798/// Section 9.3 says a store that writes an object as a whole initializes it as a whole, padding
799/// included, and that a member by member fill leaves the padding alone. Nothing here implements
800/// that, and nothing has to. Both arrive as a range and the range is the access's own width: a
801/// store through a member of a structure is a `store` of the member's width and names the member,
802/// and a structure assigned whole, a `= {0}`, a `memset` and a `memcpy` are all a copy of `sizeof`
803/// bytes and name the object. The rule falls out of what the front end already lowered rather than
804/// out of anything this pass knows about structures, which is what keeps it one rule rather than a
805/// special case per shape.
806///
807/// # Why this does not thin
808///
809/// A store records into the init plane whatever type it was storing through, including the two
810/// cases the type plane has no entry for. What the init plane holds is whether anything was stored
811/// at all, and the answer to that does not depend on what the store thought it was writing, so
812/// every store that covers a byte records it.
813fn wrote(func: &mut Func, store: Inst, pointer: Value, width: u64) -> bool {
814 let Extra::Mem(info) = func[store].extra else { return false };
815 // The padding after a member, where the front end was asked to say how far it goes. That is
816 // the whole of `-fsafety-init=nopadding` and it is a number rather than a mode here, because
817 // what the padding is takes a record's layout and this pass reads IR.
818 let size = covered(func, store, func[info].size, width).max(u64::from(func[info].owns));
819 // A store whose width nothing states writes no bytes anybody can name, the same way the type
820 // plane's judgement over one records nothing.
821 if size == 0 {
822 return false;
823 }
824
825 let span = func.span(store);
826 let (made, length) = extent(func, store, size);
827 let args = func.push_values(&[pointer, length]);
828 let data = InstData { args, ..InstData::new(Opcode::MetaInit) };
829 let judged = func.create_inst(data, &[], span);
830 // After the constant it reads rather than after the store, since both go in the same place and
831 // the one that goes in second ends up in front.
832 func.insert_after(judged, made);
833 true
834}
835
836/// Puts a `check_init` in front of one read, asking whether anything ever wrote the bytes it is
837/// about to read.
838///
839/// Document 03's Y6, and the class MSan exists for. The two writes are in, a store recording that
840/// the bytes it wrote hold something and a copy carrying whether the bytes it read held anything,
841/// so the plane now says something true about every byte a program wrote and this is the question
842/// those writes were recorded for.
843///
844/// # Why every read and not only the ones that named a type
845///
846/// [`ask`] passes over a read the front end named no type for, because a question about which type
847/// bytes hold has nothing to ask when the access names none. This one has no such case: the plane
848/// holds one bit per byte and the bit says whether anything was ever stored there, which is a fact
849/// about the bytes and not about the access, so a read of an aggregate by address asks it just as a
850/// read of an `int` does.
851///
852/// # Padding
853///
854/// It does not come up here and that is worth writing down, because section 9.3 is where the
855/// padding rule lives and this is the check the rule is about. A read compiled into a `load` reads
856/// a member, and a member is never padding, so no `load` in any program covers a byte a
857/// member-by-member fill left alone. The reads that do cover padding are `memcmp` of two
858/// structures, hashing one, and handing one to `write`, and every one of those is a call into the
859/// movement group of `crate::wrap` rather than a `load`. That is where `-fsafety-init=padding` will
860/// have something to select, and it is why the flag is not here.
861///
862/// # Why a whole width and not a byte
863///
864/// The payload's size, the same one [`ask`] uses, so a read that straddles the end of what was
865/// written is refused on the first byte nothing wrote rather than on the byte the address names.
866/// A read of four bytes where two were written is a read of memory that was never written, and
867/// reporting it at the access is the only place a report means anything.
868fn filled(
869 func: &mut Func,
870 read: Inst,
871 pointer: Value,
872 capability: Option<Value>,
873 width: u64,
874) -> bool {
875 let Some(capability) = capability else { return false };
876 let Extra::Mem(at) = func[read].extra else { return false };
877 let mut info = func[at];
878 info.size = covered(func, read, info.size, width);
879 // A read whose width nothing states reads no bytes anybody can name, as in [`ask`].
880 if info.size == 0 {
881 return false;
882 }
883 // The plane holds no types, so whatever the access named is not a thing this question is in
884 // terms of, and carrying it would suggest the check compares against it.
885 info.tbaa = None;
886
887 let span = func.span(read);
888 let args = func.push_values(&[capability, pointer]);
889 let extra = Extra::Mem(func.add_mem(info));
890 let data = InstData { args, extra, ..InstData::new(Opcode::CheckInit) };
891 let asked = func.create_inst(data, &[], span);
892 func.insert_before(asked, read);
893 true
894}
895
896/// Puts a `cap_copy` immediately after one copy, carrying the capability beside every pointer it
897/// moved.
898///
899/// The third of the three, and the one about the aux rather than about a plane. A pointer written
900/// to memory leaves its capability in the slot beside it, and a copy of a structure moves the
901/// pointer without touching the slot, so a copy with nothing here leaves every pointer in the
902/// destination described by whatever the slot said before. On fresh storage that is nothing, and
903/// the first access through the copied pointer is refused on a program that is correct. On storage
904/// the allocator has handed out before it is worse and quieter, because the slot holds an older
905/// instance's answer and a pointer that really is stale inherits a version that says it is live.
906///
907/// `struct point *a = b;` does not need this and does not get it: an assignment of one pointer is a
908/// `store` and [`saved`] puts a `cap_store` behind it. What needs it is `*a = *b;` of a structure
909/// with a pointer in it, which the front end turns into a `memcpy` of the whole object, and there
910/// the pointers being moved are bytes rather than values and no `store` ever sees them.
911///
912/// The library's own `memcpy` has had this all along. The wrapper in
913/// `runtime/rucc-safe-rt/src/effects.rs` calls the same three, so what this closes is the
914/// difference between a copy the program wrote by hand and one the compiler wrote for it, which is
915/// tamnd/rucc#1471.
916fn relocation(func: &mut Func, copy: Inst) -> bool {
917 let Some(bulk) = func.bulk(copy) else { return false };
918 let (to, from) = (bulk.to, bulk.with);
919 let Some((made, length)) = copied(func, copy) else { return false };
920
921 let span = func.span(copy);
922 let args = func.push_values(&[to, from, length]);
923 let data = InstData { args, ..InstData::new(Opcode::CapCopy) };
924 let carried = func.create_inst(data, &[], span);
925 func.insert_after(carried, made);
926 true
927}
928
929/// Puts a `meta_init_copy` immediately after one copy, carrying whether its source held anything
930/// over to its destination.
931///
932/// The other half of the same write, and the thing that makes an infoleak visible rather than what
933/// hides it. A copy writes no values of its own: whether a destination byte holds anything is
934/// whether the byte it came from did, and the only place that is written down is the plane over the
935/// source. So this names two ranges and a length and nothing else, exactly as the type plane's
936/// carriage does.
937///
938/// A structure filled member by member and then handed whole to `write` or to a socket is the case
939/// worth stating. Marking the destination written would lose it, because the bytes that leave the
940/// program would be bytes the plane had just been told were fine, and those are exactly the bytes
941/// of CWE-200. Carrying the source's answer keeps the padding unwritten all the way to the
942/// boundary, which is where the read that matters happens.
943fn moved(func: &mut Func, copy: Inst) -> bool {
944 let Some(bulk) = func.bulk(copy) else { return false };
945 let (to, from) = (bulk.to, bulk.with);
946 let Some((made, length)) = copied(func, copy) else { return false };
947
948 let span = func.span(copy);
949 let args = func.push_values(&[to, from, length]);
950 let data = InstData { args, ..InstData::new(Opcode::MetaInitCopy) };
951 let carried = func.create_inst(data, &[], span);
952 func.insert_after(carried, made);
953 true
954}
955
956/// The constant a plane write over a range reads its length from, put in just after `at`.
957///
958/// Gives back the instruction as well as the value, because the caller inserts itself after the
959/// constant rather than after `at`: both go in the same place, and the one that goes in second ends
960/// up in front of the one that went in first.
961///
962/// Written in sixty four bits here and put into the target's width by [`lower::lower`], which is
963/// where the only thing that knows the target's width is.
964fn extent(func: &mut Func, at: Inst, size: u64) -> (Inst, Value) {
965 let span = func.span(at);
966 let word = Type::int(64);
967 let extra = Extra::Imm(func.add_imm(Imm::int(i128::from(size), word)));
968 let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[word], span);
969 func.insert_after(made, at);
970 let length = func[made].results().next().expect("a constant created with one result has one");
971 (made, length)
972}
973
974/// How long the copy at `at` is, as a value, and what a plane write over it goes in after.
975///
976/// The same pair [`extent`] hands back and for the same reason, except that this one asks the copy
977/// rather than being told a number. A copy whose length the program works out hands its own operand
978/// over and there is no constant to make, so what the caller goes in after is the copy itself,
979/// which puts the write in the same place either way.
980///
981/// `None` for a copy that moves no bytes, which the verifier refuses, so it is a shape that does
982/// not arise rather than a case being handled.
983fn copied(func: &mut Func, at: Inst) -> Option<(Inst, Value)> {
984 let bulk = func.bulk(at)?;
985 if let Some(length) = bulk.length {
986 return Some((at, length));
987 }
988 let Extra::Mem(info) = func[at].extra else { return None };
989 let size = func[info].size;
990 if size == 0 {
991 return None;
992 }
993 Some(extent(func, at, size))
994}
995
996/// How many bytes an access covers.
997///
998/// An ordinary `load` or `store` leaves the `size` field of its payload at zero and takes its width
999/// from the type instead, which is fine for an access and no use at all to a check: a check is
1000/// asked how many bytes are being touched and has no type of its own to read. So the width is
1001/// worked out here and written into the copy of the payload the check carries, and an access that
1002/// did fill the field in keeps what it said.
1003///
1004/// `width` is the target's pointer width in bytes, and it is a parameter because a pointer is the
1005/// one type in the IR that has no width of its own. Reading a zero off `Type::PTR` and passing it
1006/// on is what made every check over a pointer decide over a single byte, which is #953.
1007fn covered(func: &Func, access: Inst, stated: u64, width: u64) -> u64 {
1008 if stated != 0 {
1009 return stated;
1010 }
1011 // A `load` produces the value and a `store` takes it as its first operand.
1012 let ty = match func[access].opcode {
1013 Opcode::Load => func[access].results().next().map(|value| func[value].ty),
1014 Opcode::Store => func[func[access].args].first().map(|&value| func[value].ty),
1015 _ => None,
1016 };
1017 ty.map_or(0, |ty| {
1018 if ty.is_ptr() {
1019 return width;
1020 }
1021 u64::from(ty.bits().div_ceil(8)) * u64::from(ty.lanes())
1022 })
1023}
1024
1025/// Puts a `meta_epoch` immediately after one store, recording which thread wrote a pointer.
1026///
1027/// The recording half of `spec/safe-memory/09-type-init-and-races.md` section 9.5, which is
1028/// judgement J9 of document 04 and document 03's C1 through C4. The plane holds one stamp per
1029/// eight bytes, the stamp names a thread and the step that thread had reached, and everything the
1030/// race classes decide is a comparison against a stamp some store left here.
1031///
1032/// After the store, for the reason the other two recordings go after one: the bytes hold what was
1033/// written once the store has happened, and the check that reads the plane back has to run before
1034/// this one so that it is asking about somebody else's write rather than about this one.
1035///
1036/// # Why only a store of a pointer
1037///
1038/// Because the plane's granule is a pointer and the classes are about pointers. Section 9.5 is the
1039/// section where a race produces a wrong *pointer* rather than a wrong number, which is what makes
1040/// it worth watching at a cost a program can carry in production: a torn integer is a wrong answer
1041/// and a torn pointer is a memory safety failure. Recording every store instead would put two
1042/// threads writing neighbouring bytes of one granule into the plane as each other's strangers, and
1043/// neighbouring bytes are exactly what a granule holding no pointer is made of.
1044///
1045/// So the thinning is the same shape as the type plane's and lands in a different place: that one
1046/// records a store whose type the plane has a name for, and this one records a store whose value is
1047/// a pointer. A store through a `void *` variable is one. A `memcpy` that happens to move pointers
1048/// is not, and that is a lost report rather than a wrong answer, since a copy does not say what the
1049/// bytes it moved were.
1050fn stamped(func: &mut Func, store: Inst, pointer: Value, width: u64) -> bool {
1051 if !pointer_valued(func, store) {
1052 return false;
1053 }
1054
1055 let span = func.span(store);
1056 let (made, length) = extent(func, store, width);
1057 let args = func.push_values(&[pointer, length]);
1058 let data = InstData { args, ..InstData::new(Opcode::MetaEpoch) };
1059 let judged = func.create_inst(data, &[], span);
1060 // After the constant it reads rather than after the store, as [`wrote`] does and for the same
1061 // reason: both go in the same place and the one that goes in second ends up in front.
1062 func.insert_after(judged, made);
1063 true
1064}
1065
1066/// Puts a `cap_store` immediately after one store of a pointer, so the capability goes with it.
1067///
1068/// The writing half of `spec/safe-memory/06-instrumentation.md` section 6.2.2, and the half that
1069/// makes [`recalled`] able to answer. A pointer in a register has its capability in another register
1070/// and a pointer in memory has nowhere to keep one, so the aux slot beside the word is where it
1071/// goes, and this is the instruction that puts it there.
1072///
1073/// It is the expensive half, which is worth saying plainly. Nothing discharges an aux write today,
1074/// so this is a call at every store of a pointer, and the capability it writes is one the stored
1075/// pointer may not have had, in which case asking for it here is what makes it exist. What buys that
1076/// back is on the other side: a pointer read out of memory stops being a walk of the lifetime plane
1077/// and becomes a slot read, and a program that keeps its pointers in structures reads them far more
1078/// often than it stores them.
1079///
1080/// Four operands, and the shape is the call's, which tamnd/rucc#1080 is where that was decided. The
1081/// container's capability comes first because finding the slot starts from the object the word is
1082/// in, then the address of the word, then the pointer that was written, then that pointer's own
1083/// capability, which is the thing being written down.
1084///
1085/// A store whose value is not a pointer has nothing to write and no slot to write it in. A store the
1086/// access could not be given a capability for is left alone as well, since the container is where
1087/// the slot is found and there is no second way to find it.
1088fn saved(
1089 func: &mut Func,
1090 origins: &mut origin::Origins,
1091 store: Inst,
1092 pointer: Value,
1093 container: Option<Value>,
1094) -> bool {
1095 let Some(container) = container else { return false };
1096 if !pointer_valued(func, store) {
1097 return false;
1098 }
1099 let Some(&value) = func[func[store].args].first() else { return false };
1100
1101 let held = origins.of(func, value, store);
1102 let span = func.span(store);
1103 let args = func.push_values(&[container, pointer, value, held]);
1104 let data = InstData { args, ..InstData::new(Opcode::CapStore) };
1105 let made = func.create_inst(data, &[], span);
1106 func.insert_after(made, store);
1107 true
1108}
1109
1110/// Puts a `cap_load` immediately after one read of a pointer, and makes that the pointer's own.
1111///
1112/// The reading half of section 6.2.2. Without it a pointer read out of memory has no producer but
1113/// `cap_of`, which lowers to a walk of the lifetime plane linear in the size of the object, and a
1114/// C program of any size keeps its pointers in structures and reads them back. That walk is the cost
1115/// tamnd/rucc#1241 is about and this is the second of the three producers that take it away.
1116///
1117/// It answers a question the walk cannot, which matters more than the speed. Recovering from an
1118/// address says which instance owns those bytes now. The slot says which instance the pointer was
1119/// written for. A pointer stored in a structure, freed, and the storage handed to somebody else has
1120/// those two disagree, and the version in the slot is what makes `check_live` refuse at the first
1121/// access through it rather than pass because the address landed inside a live object.
1122///
1123/// Three operands rather than four: the container, the word, and the value that came out of it, with
1124/// the capability being what comes back instead of what goes in.
1125///
1126/// [`origin::Origins::seed`] is what stops a second producer being made for the same value. The load
1127/// is the definition of the pointer, so this sits exactly where [`mod@origin`] would have put a
1128/// `cap_of`, and everything downstream that asks for the pointer's capability gets this one.
1129fn recalled(
1130 func: &mut Func,
1131 origins: &mut origin::Origins,
1132 load: Inst,
1133 pointer: Value,
1134 container: Option<Value>,
1135) -> bool {
1136 let Some(container) = container else { return false };
1137 let Some(value) = func[load].results().next() else { return false };
1138 if !func[value].ty.is_ptr() {
1139 return false;
1140 }
1141
1142 let span = func.span(load);
1143 let args = func.push_values(&[container, pointer, value]);
1144 let data = InstData { args, ..InstData::new(Opcode::CapLoad) };
1145 let made = func.create_inst(data, &[Type::CAP], span);
1146 func.insert_after(made, load);
1147 let Some(held) = func[made].results().next() else { return false };
1148 origins.seed(value, held);
1149 true
1150}
1151
1152/// Whether what an access carries is a pointer, which is the one thing the epoch plane watches.
1153///
1154/// A `store` carries it as its first operand, the value being written coming before the place it
1155/// goes. A `load` carries it as its result. The plane's granule is eight bytes because a pointer is
1156/// eight bytes, so a granule two threads both reach is a granule holding no pointer, and watching
1157/// anything wider than this would put two threads writing neighbouring members of one structure
1158/// into the plane as each other's strangers.
1159fn pointer_valued(func: &Func, access: Inst) -> bool {
1160 let carried = match func[access].opcode {
1161 Opcode::Store => func[func[access].args].first().map(|&value| func[value].ty),
1162 Opcode::Load => func[access].results().next().map(|value| func[value].ty),
1163 _ => None,
1164 };
1165 carried.is_some_and(Type::is_ptr)
1166}
1167
1168/// Puts a `check_race` immediately before one access, asking whether another thread reached these
1169/// bytes with nothing ordering that against this thread.
1170///
1171/// Judgement J9, and the reading half of section 9.5. The plane holds one stamp per granule saying
1172/// which thread last stored a pointer there and how far that thread had counted, and a stamp this
1173/// thread has not got past is a write no synchronization edge puts before this access. Which class
1174/// that is depends on which side asked: a store finding one is C3, two threads writing the same
1175/// slot with nothing between them, and a load finding one is C2, the pointer word race. One check
1176/// covers both because the comparison is the same from either side.
1177///
1178/// In front of the access, and at a store that puts it in front of the `meta_epoch` that goes
1179/// after. That order is the whole of what makes the question answerable: [`stamped`] overwrites the
1180/// stamp this reads, so a check on the other side of the store would be asking about the write it
1181/// was called for.
1182///
1183/// Only an access that carries a pointer, which [`pointer_valued`] argues. So this is not one check
1184/// per access the way the bounds check is, and on ordinary code it is a small fraction of them.
1185fn raced(
1186 func: &mut Func,
1187 access: Inst,
1188 pointer: Value,
1189 capability: Option<Value>,
1190 width: u64,
1191) -> bool {
1192 let Some(capability) = capability else { return false };
1193 if !pointer_valued(func, access) {
1194 return false;
1195 }
1196 let Extra::Mem(at) = func[access].extra else { return false };
1197 let mut info = func[at];
1198 info.size = covered(func, access, info.size, width);
1199 // An access whose width nothing states touches no bytes anybody can name, as in [`filled`].
1200 if info.size == 0 {
1201 return false;
1202 }
1203 // Neither field means anything to this question. The plane holds stamps rather than types, and
1204 // the alignment conjunct of J1 is the bounds check's to make.
1205 info.tbaa = None;
1206 info.align = 1;
1207 info.owns = 0;
1208
1209 let span = func.span(access);
1210 let args = func.push_values(&[capability, pointer]);
1211 let extra = Extra::Mem(func.add_mem(info));
1212 let data = InstData { args, extra, ..InstData::new(Opcode::CheckRace) };
1213 let asked = func.create_inst(data, &[], span);
1214 func.insert_before(asked, access);
1215 true
1216}
1217
1218/// Puts a `meta_release` in front of an atomic, a `meta_acquire` after it, or both, or neither.
1219///
1220/// The synchronization edges of `spec/safe-memory/09-type-init-and-races.md` section 9.5, for the
1221/// one kind of edge that cannot be interposed. Every other edge the monitor knows about is a
1222/// `pthread` call and `rucc_safe_rt::sync` wraps it. A C11 release store is a machine instruction,
1223/// so there is no call to wrap and the compiler is the only thing in the build that can say an
1224/// ordering happened here.
1225///
1226/// This matters more than a missing recording does, and in the opposite direction. Everywhere else
1227/// in this pass, instrumentation nobody wrote costs recall: a store that was not stamped is a race
1228/// that is not found. Here it costs precision. The epoch plane is a counter per thread and nothing
1229/// else, so two threads that really were ordered, by an edge this pass did not emit, are two
1230/// threads whose clocks say they are concurrent, and the check reports a race in a program that
1231/// has none. That is why these go in before the check is ever on by default.
1232///
1233/// Which side the marker lands on is which side the ordering is on. A release publishes everything
1234/// the thread has already done, so the clock has to be written down while that is still true, which
1235/// is in front of the atomic. An acquire takes an ordering from whatever the atomic just read, so
1236/// it is not there to be taken until the atomic has run, which puts it after. An `acq_rel` or a
1237/// `seq_cst` read-modify-write is both, and gets one of each.
1238///
1239/// No thinning by what the atomic carries, unlike [`stamped`] and [`raced`]. A release on an atomic
1240/// `int` is the ordinary publication pattern, the flag being set is not the pointer, and refusing
1241/// to record that edge because no pointer went through it would lose exactly the ordering the
1242/// pointers stored before it depend on.
1243///
1244/// A `fence` goes through [`fenced`] instead, because it has no address in it to be the key.
1245fn edges(func: &mut Func, atomic: Inst) -> usize {
1246 let Some(pointer) = keyed(func, atomic) else { return 0 };
1247 let order = match func[atomic].extra {
1248 Extra::Mem(at) => func[at].order,
1249 Extra::Rmw(_, at) => func[at].order,
1250 _ => return 0,
1251 };
1252
1253 let span = func.span(atomic);
1254 let mut put = 0;
1255 if order.is_release() {
1256 let args = func.push_values(&[pointer]);
1257 let data = InstData { args, ..InstData::new(Opcode::MetaRelease) };
1258 let made = func.create_inst(data, &[], span);
1259 func.insert_before(made, atomic);
1260 put += 1;
1261 }
1262 if order.is_acquire() {
1263 let args = func.push_values(&[pointer]);
1264 let data = InstData { args, ..InstData::new(Opcode::MetaAcquire) };
1265 let made = func.create_inst(data, &[], span);
1266 func.insert_after(made, atomic);
1267 put += 1;
1268 }
1269 put
1270}
1271
1272/// Puts a `meta_fence_release` in front of a fence, a `meta_fence_acquire` after it, or both.
1273///
1274/// The other way a C program orders two threads without calling anything, and the harder half of
1275/// [`edges`]. A fence orders against every other thread rather than against one object, so there is
1276/// no address in it to file the edge under and the markers it gets take no operands. The relaxed
1277/// atomic that usually sits next to a fence in the source is not the key either: what the fence
1278/// orders is everything the thread did, not that one word, and keying on the word would miss every
1279/// other pair the fence really ordered.
1280///
1281/// So the runtime keeps one cell for all of them, and that orders more pairs of threads than the
1282/// program did. `rucc_safe_rt::sync` carries the argument for why that is the safe direction, which
1283/// is the same argument the stale entries there already rest on: a thread put further ahead than it
1284/// needed to be reports fewer races, never a race that is not there. Given that a missing edge is
1285/// the one kind of missing instrumentation that costs precision, too much ordering beats none.
1286///
1287/// Which side the marker lands on is the same question as in [`edges`] and has the same answer.
1288fn fenced(func: &mut Func, fence: Inst) -> usize {
1289 let Extra::Order(order) = func[fence].extra else { return 0 };
1290
1291 let span = func.span(fence);
1292 let mut put = 0;
1293 if order.is_release() {
1294 let made = func.create_inst(InstData::new(Opcode::MetaFenceRelease), &[], span);
1295 func.insert_before(made, fence);
1296 put += 1;
1297 }
1298 if order.is_acquire() {
1299 let made = func.create_inst(InstData::new(Opcode::MetaFenceAcquire), &[], span);
1300 func.insert_after(made, fence);
1301 put += 1;
1302 }
1303 put
1304}
1305
1306/// The address an atomic operates on, which is the key its edge is filed under.
1307///
1308/// The same shape as [`pointer_of`] and a different set of opcodes: an `atomic_store` writes
1309/// through its second operand for the reason an ordinary store does, and the other three take the
1310/// object first because nothing comes before it.
1311fn keyed(func: &Func, atomic: Inst) -> Option<Value> {
1312 let args = &func[func[atomic].args];
1313 let at = match func[atomic].opcode {
1314 Opcode::AtomicStore => 1,
1315 Opcode::AtomicLoad | Opcode::AtomicRmw | Opcode::Cmpxchg => 0,
1316 _ => return None,
1317 };
1318 let &value = args.get(at)?;
1319 func[value].ty.is_ptr().then_some(value)
1320}
1321
1322/// Puts `check_deriv` immediately after one `ptr_add`, over the capability of what it walked off.
1323///
1324/// Judgement J2, which is the one that catches a pointer walking off its object *before* anything
1325/// is read through it. C says computing such a pointer is already undefined, and catching it here
1326/// rather than at the eventual access is what lets the report name the loop that ran too far
1327/// instead of whatever unrelated line finally dereferenced the result.
1328///
1329/// The check is handed the pointer the derivation produced, so it goes immediately after the
1330/// derivation rather than in front of it like the access checks. That is what section 6.2.2's
1331/// third operand means: the judgement is about where the derived pointer landed, and there is
1332/// nothing to decide before it has landed.
1333///
1334/// The fourth operand is the stride, which is how wide one element of whatever is being stepped
1335/// over is. Document 03 section 3.1 widened S5's window to `[lo - stride, hi]`, so the runtime
1336/// cannot decide the low end without it, and it is a value rather than a constant because a walk
1337/// over a variable length array steps by a width the program computes.
1338fn derivation(func: &mut Func, origins: &mut origin::Origins, add: Inst) -> bool {
1339 let Some(&base) = func[func[add].args].first() else { return false };
1340 if !func[base].ty.is_ptr() {
1341 return false;
1342 }
1343 let Some(derived) = func[add].results().next() else { return false };
1344
1345 let span = func.span(add);
1346 let width = stride(func, add);
1347 let capability = origins.of(func, base, add);
1348 let args = func.push_values(&[capability, base, derived, width]);
1349 let check = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[], span);
1350 func.insert_after(check, add);
1351 true
1352}
1353
1354/// How wide one element of the thing a `ptr_add` steps over is.
1355///
1356/// C computes a byte offset before the pointer arithmetic happens, so `ptr_add` takes bytes and the
1357/// element width is not in it. What is in it is the shape the frontend left behind, because this
1358/// pass runs before the optimizer and the offset operand is still exactly what lowering emitted:
1359/// `mul index, k` for a constant width, `mul index, w` for one the program computes, either of them
1360/// under a `sub 0, ...` for a walk that goes backwards, and the bare index when the width is one.
1361///
1362/// So the width is read back off that shape. Getting it wrong is not a soundness question: the
1363/// stride only decides how far below an object a derivation may land before it is refused, and an
1364/// access below the object is refused by judgement J1 either way. A shape nobody recognises answers
1365/// one byte, which is the strict reading of C and is where this check was before the window moved.
1366fn stride(func: &mut Func, add: Inst) -> Value {
1367 // The offset is the one operand of a `ptr_add` that is an integer, so its type is the width an
1368 // address is computed in and is the type the check's fourth operand has to have.
1369 let Some(&offset) = func[func[add].args].get(1) else { return one(func, add, Type::int(64)) };
1370 let word = func[offset].ty;
1371 // A walk that goes backwards negates the offset rather than the width, so the shape underneath
1372 // is the same one a forward walk has.
1373 let forwards = match operand_of(func, offset, Opcode::Sub, 0) {
1374 Some(zero) if is_zero(func, zero) => operand_of(func, offset, Opcode::Sub, 1),
1375 _ => None,
1376 };
1377 let scaled = forwards.unwrap_or(offset);
1378 match operand_of(func, scaled, Opcode::Mul, 1) {
1379 // The width is the right operand because `step` builds the multiply that way round, with
1380 // the index on the left and the size of one element on the right.
1381 Some(width) if func[width].ty == word => width,
1382 _ => one(func, add, word),
1383 }
1384}
1385
1386/// Operand `index` of the instruction that produced `value`, when that instruction is `opcode`.
1387fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
1388 let Def::Result { inst, .. } = func[value].def else { return None };
1389 if func[inst].opcode != opcode {
1390 return None;
1391 }
1392 func[func[inst].args].get(index).copied()
1393}
1394
1395/// Whether a value is a constant zero, which is the left half of how a backwards walk is spelled.
1396fn is_zero(func: &Func, value: Value) -> bool {
1397 let Def::Result { inst, .. } = func[value].def else { return false };
1398 match func[inst].extra {
1399 Extra::Imm(imm) if func[inst].opcode == Opcode::IConst => func[imm].bits() == 0,
1400 _ => false,
1401 }
1402}
1403
1404/// A stride of one byte, which is what a shape this pass does not recognise answers.
1405fn one(func: &mut Func, at: Inst, ty: Type) -> Value {
1406 let span = func.span(at);
1407 let extra = Extra::Imm(func.add_imm(Imm::int(1, ty)));
1408 let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
1409 func.insert_before(made, at);
1410 func[made].results().next().expect("a constant created with one result has one")
1411}
1412
1413#[cfg(test)]
1414mod tests {
1415 use rucc_base::Interner;
1416 use rucc_ir::{
1417 Builder, Flags, MemInfo, MemOrder, Meta, MetaNode, PlaneNode, Restrict, RmwOp, Signature,
1418 TbaaNode, print_func, verify_func,
1419 };
1420 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1421
1422 use super::*;
1423
1424 fn target() -> TargetInfo {
1425 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
1426 }
1427
1428 /// A module to record into, and the plane entries it holds.
1429 ///
1430 /// The plane is the module's, so a test that instruments a bare function still has to have one
1431 /// to hand. It is empty of types here, since the functions these tests build name none.
1432 fn planed(names: &mut Interner, unit: &str) -> (Module, Plane) {
1433 let mut module = Module::new(names.intern(unit), &target());
1434 let plane = Plane::build(&mut module);
1435 (module, plane)
1436 }
1437
1438 /// A function that loads through its parameter and stores what it read back.
1439 fn one_of_each(names: &mut Interner) -> Func {
1440 let i32_ = Type::int(32);
1441 let mut func = Func::new(
1442 names.intern("both"),
1443 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1444 );
1445 let entry = func.create_block();
1446 let p = func.append_param(entry, Type::PTR);
1447
1448 let info = MemInfo {
1449 size: 4,
1450 align: 4,
1451 order: MemOrder::NotAtomic,
1452 tbaa: None,
1453 owns: 0,
1454 restrict: Restrict::NONE,
1455 };
1456 let mut b = Builder::new(&mut func, entry);
1457 let args = b.func().push_values(&[p]);
1458 let extra = Extra::Mem(b.func().add_mem(info));
1459 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
1460 let args = b.func().push_values(&[loaded, p]);
1461 let extra = Extra::Mem(b.func().add_mem(info));
1462 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1463 b.ret(&[loaded]);
1464 func
1465 }
1466
1467 /// The same shape, with the two accesses said to go through two `restrict` pointers of a block.
1468 fn promising(names: &mut Interner) -> Func {
1469 let i32_ = Type::int(32);
1470 let mut func = Func::new(
1471 names.intern("kernel"),
1472 Signature::new().with_params(&[Type::PTR, Type::PTR]),
1473 );
1474 let entry = func.create_block();
1475 let to = func.append_param(entry, Type::PTR);
1476 let from = func.append_param(entry, Type::PTR);
1477
1478 let info = MemInfo {
1479 size: 4,
1480 align: 4,
1481 order: MemOrder::NotAtomic,
1482 tbaa: None,
1483 owns: 0,
1484 restrict: Restrict { clique: 1, base: 1 },
1485 };
1486 let mut b = Builder::new(&mut func, entry);
1487 let read = MemInfo { restrict: Restrict { clique: 1, base: 2 }, ..info };
1488 let loaded = b.load(i32_, from, read, Flags::default());
1489 b.store(loaded, to, info, Flags::default());
1490 b.ret(&[]);
1491 func
1492 }
1493
1494 #[test]
1495 fn the_restrict_checks_wait_until_the_build_asks_for_them() {
1496 // The one check in this crate that is off by default. What it costs is paid by the blocks
1497 // that declare `restrict` pointers and nobody else, and what it reports includes programs
1498 // the standard permits, so which it is is the build's decision. `rucc_session::Promise` is
1499 // where that is argued.
1500 let mut names = Interner::new();
1501 let (_, plane) = planed(&mut names, "kernel.c");
1502
1503 let mut quiet = promising(&mut names);
1504 let counts = insert(&mut quiet, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
1505 assert_eq!((counts.promised, counts.scoped), (0, 0));
1506
1507 let mut asked = promising(&mut names);
1508 let counts = insert(&mut asked, &plane, 8, Subobject::Off, Promise::Blocks, Races::Off);
1509 assert_eq!((counts.promised, counts.scoped), (2, 1));
1510 }
1511
1512 #[test]
1513 fn every_access_gets_a_bounds_check_and_a_lifetime_check() {
1514 let mut names = Interner::new();
1515 let mut func = one_of_each(&mut names);
1516 let (module, plane) = planed(&mut names, "both.c");
1517 assert_eq!(
1518 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
1519 Counts { checked: 2, live: 2, judged: 1, wrote: 1, filled: 1, ..Counts::default() }
1520 );
1521
1522 assert_eq!(
1523 print_func(&module, &func, &names),
1524 // The plane writes are after the store and not in front of it. The bytes were stored
1525 // through that type, and were stored at all, once the store has happened, and the
1526 // check in front of it may yet refuse the store both of them are about.
1527 //
1528 // One `cap_of` for two accesses, because both go through the same pointer and a
1529 // capability is about the pointer. `origin` is where that is argued.
1530 "func @both(ptr) -> i32, linkage(external) {\n\
1531 block0(%0: ptr):\n \
1532 %1 = cap_of %0\n \
1533 check_bounds %1, %0, size 4, align 4\n \
1534 check_live %1, %0\n \
1535 check_init %1, %0, size 4, align 4\n \
1536 %2 = load.i32 %0, size 4, align 4\n \
1537 check_bounds %1, %0, size 4, align 4\n \
1538 check_live %1, %0\n \
1539 store %2 -> %0, size 4, align 4\n \
1540 %3 = iconst.i64 4\n \
1541 meta_type %0, %3, tbaa !1\n \
1542 %4 = iconst.i64 4\n \
1543 meta_init %0, %4\n \
1544 return %2\n\
1545 }\n"
1546 );
1547 }
1548
1549 /// A function that loads a pointer through its parameter.
1550 ///
1551 /// The payload states no size, which is what the front end emits: a load takes its width from
1552 /// the type it produces, and for a pointer that is the one type with no width of its own.
1553 fn one_pointer_read(names: &mut Interner) -> Func {
1554 let mut func = Func::new(
1555 names.intern("deref"),
1556 Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::PTR]),
1557 );
1558 let entry = func.create_block();
1559 let p = func.append_param(entry, Type::PTR);
1560
1561 let info = MemInfo {
1562 size: 0,
1563 align: 8,
1564 order: MemOrder::NotAtomic,
1565 tbaa: None,
1566 owns: 0,
1567 restrict: Restrict::NONE,
1568 };
1569 let mut b = Builder::new(&mut func, entry);
1570 let args = b.func().push_values(&[p]);
1571 let extra = Extra::Mem(b.func().add_mem(info));
1572 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR);
1573 b.ret(&[loaded]);
1574 func
1575 }
1576
1577 /// A function that writes one of its pointer parameters through the other.
1578 fn one_pointer_write(names: &mut Interner) -> Func {
1579 let mut func =
1580 Func::new(names.intern("keep"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
1581 let entry = func.create_block();
1582 let at = func.append_param(entry, Type::PTR);
1583 let value = func.append_param(entry, Type::PTR);
1584
1585 let info = MemInfo {
1586 size: 0,
1587 align: 8,
1588 order: MemOrder::NotAtomic,
1589 tbaa: None,
1590 owns: 0,
1591 restrict: Restrict::NONE,
1592 };
1593 let mut b = Builder::new(&mut func, entry);
1594 // The value first and the place second, which is the order the opcode is written in.
1595 let args = b.func().push_values(&[value, at]);
1596 let extra = Extra::Mem(b.func().add_mem(info));
1597 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1598 b.ret(&[]);
1599 func
1600 }
1601
1602 /// The same function writing a number, which is the one thing that differs from a pointer.
1603 fn one_number_write(names: &mut Interner) -> Func {
1604 let i32_ = Type::int(32);
1605 let mut func =
1606 Func::new(names.intern("set"), Signature::new().with_params(&[Type::PTR, i32_]));
1607 let entry = func.create_block();
1608 let at = func.append_param(entry, Type::PTR);
1609 let value = func.append_param(entry, i32_);
1610
1611 let info = MemInfo {
1612 size: 4,
1613 align: 4,
1614 order: MemOrder::NotAtomic,
1615 tbaa: None,
1616 owns: 0,
1617 restrict: Restrict::NONE,
1618 };
1619 let mut b = Builder::new(&mut func, entry);
1620 let args = b.func().push_values(&[value, at]);
1621 let extra = Extra::Mem(b.func().add_mem(info));
1622 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1623 b.ret(&[]);
1624 func
1625 }
1626
1627 /// The same function reading a number, so the two answers differ in one thing.
1628 fn one_number_read(names: &mut Interner) -> Func {
1629 let i32_ = Type::int(32);
1630 let mut func = Func::new(
1631 names.intern("count"),
1632 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1633 );
1634 let entry = func.create_block();
1635 let p = func.append_param(entry, Type::PTR);
1636
1637 let info = MemInfo {
1638 size: 0,
1639 align: 4,
1640 order: MemOrder::NotAtomic,
1641 tbaa: None,
1642 owns: 0,
1643 restrict: Restrict::NONE,
1644 };
1645 let mut b = Builder::new(&mut func, entry);
1646 let args = b.func().push_values(&[p]);
1647 let extra = Extra::Mem(b.func().add_mem(info));
1648 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
1649 b.ret(&[loaded]);
1650 func
1651 }
1652
1653 #[test]
1654 fn an_access_that_reads_a_pointer_is_checked_over_the_targets_pointer_width() {
1655 // A pointer is the one type in the IR with no width of its own, so the width has to come
1656 // from the target. Answering zero is what left a bounds check over a pointer deciding
1657 // about a single byte and left the init question out of it altogether, which was #953.
1658 let mut names = Interner::new();
1659 let mut func = one_pointer_read(&mut names);
1660 let (module, plane) = planed(&mut names, "deref.c");
1661 assert_eq!(
1662 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
1663 Counts { checked: 1, live: 1, filled: 1, recalled: 1, ..Counts::default() }
1664 );
1665
1666 let printed = print_func(&module, &func, &names);
1667 assert!(printed.contains("check_bounds %1, %0, size 8, align 8\n"), "{printed}");
1668 assert!(printed.contains("check_init %1, %0, size 8, align 8\n"), "{printed}");
1669 }
1670
1671 #[test]
1672 fn a_store_of_a_pointer_writes_its_capability_into_the_slot_beside_it() {
1673 // The other end of the aux pair. Four operands, and the order is the call's: the capability
1674 // of the object the word is in, the address of the word, the pointer written there, and
1675 // that pointer's own capability, which is the thing being written down. It comes last of
1676 // what goes in behind the store because it went in first, which is what puts it furthest.
1677 let mut names = Interner::new();
1678 let mut func = one_pointer_write(&mut names);
1679 let (module, plane) = planed(&mut names, "keep.c");
1680 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
1681 assert_eq!(counts.saved, 1);
1682 assert_eq!(counts.recalled, 0);
1683
1684 let printed = print_func(&module, &func, &names);
1685 assert!(printed.contains("cap_store %3, %0, %1, %2\n return\n"), "{printed}");
1686 }
1687
1688 #[test]
1689 fn a_store_of_something_that_is_not_a_pointer_writes_no_capability() {
1690 // There is nothing to write down and no slot for it. The aux is beside a pointer sized word
1691 // holding a pointer, and a number stored there is what makes the slot say so instead.
1692 let mut names = Interner::new();
1693 let mut func = one_number_write(&mut names);
1694 let (_module, plane) = planed(&mut names, "keep.c");
1695 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
1696 assert_eq!(counts.saved, 0);
1697 }
1698
1699 #[test]
1700 fn a_pointer_width_of_four_is_what_a_thirty_two_bit_target_gets() {
1701 // The number is the target's and not this crate's, so a build for a target where a pointer
1702 // is four bytes asks about four.
1703 let mut names = Interner::new();
1704 let mut func = one_pointer_read(&mut names);
1705 let (module, plane) = planed(&mut names, "deref.c");
1706 insert(&mut func, &plane, 4, Subobject::Off, Promise::Off, Races::Off);
1707
1708 let printed = print_func(&module, &func, &names);
1709 assert!(printed.contains("check_bounds %1, %0, size 4, align 8\n"), "{printed}");
1710 }
1711
1712 /// A module with one aliasing node under the root, and the plane built over it.
1713 ///
1714 /// Two nodes rather than one, because the root is the character type and a type of its own has
1715 /// to hang under something. What comes back is the module, the plane, and the node for `int`.
1716 fn typed(names: &mut Interner, unit: &str) -> (Module, Plane, Meta) {
1717 let mut module = Module::new(names.intern(unit), &target());
1718 let root = names.intern("char");
1719 let root =
1720 module.add_meta(MetaNode::Tbaa(TbaaNode { name: root, parent: None, offset: 0 }));
1721 let int = names.intern("int");
1722 let int =
1723 module.add_meta(MetaNode::Tbaa(TbaaNode { name: int, parent: Some(root), offset: 0 }));
1724 let plane = Plane::build(&mut module);
1725 (module, plane, int)
1726 }
1727
1728 /// A function that reads through its parameter as an `int`, naming that type on the access.
1729 fn reading(names: &mut Interner, node: Option<Meta>) -> Func {
1730 let i32_ = Type::int(32);
1731 let mut func = Func::new(
1732 names.intern("read"),
1733 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1734 );
1735 let entry = func.create_block();
1736 let p = func.append_param(entry, Type::PTR);
1737 let info = MemInfo {
1738 size: 0,
1739 align: 4,
1740 order: MemOrder::NotAtomic,
1741 tbaa: node,
1742 owns: 0,
1743 restrict: Restrict::NONE,
1744 };
1745 let mut b = Builder::new(&mut func, entry);
1746 let args = b.func().push_values(&[p]);
1747 let extra = Extra::Mem(b.func().add_mem(info));
1748 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
1749 b.ret(&[loaded]);
1750 func
1751 }
1752
1753 #[test]
1754 fn a_read_asks_the_plane_whether_the_bytes_agree_with_the_type_it_reads_them_as() {
1755 // Judgement J3, which is what the two plane writes were recorded for. The question is put
1756 // in the plane's vocabulary rather than the aliasing tree's, so what the check carries is
1757 // the entry for `int` and not the node for it.
1758 let mut names = Interner::new();
1759 let (module, plane, int) = typed(&mut names, "read.c");
1760 let mut func = reading(&mut names, Some(int));
1761
1762 assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).asked, 1);
1763
1764 let printed = print_func(&module, &func, &names);
1765 let entry = plane.entry(Some(int));
1766 assert_eq!(module[entry], MetaNode::Plane(PlaneNode::Type(int)));
1767 // Four bytes, which the payload does not say and the type of the value read does, and the
1768 // check is in front of the read rather than after it.
1769 let wanted = format!("check_type %1, %0, size 4, align 4, tbaa !{}\n", entry.index());
1770 assert!(printed.contains(&wanted), "{printed}");
1771 let asked = printed.find(&wanted).expect("the check is there");
1772 let read = printed.find("load.i32").expect("and so is the read");
1773 assert!(asked < read, "{printed}");
1774
1775 if let Err(errors) = verify_func(&module, &func, &names) {
1776 panic!("that was expected to be believed: {errors:#?}");
1777 }
1778 }
1779
1780 #[test]
1781 fn a_read_the_front_end_named_no_type_for_asks_nothing() {
1782 // An aggregate, an array, or anything else reached by address. The plane's untyped entry
1783 // means bytes nothing has stored through, which is a different statement from the front
1784 // end not having said what the access is through, and asking with it would refuse every
1785 // read of a structure whose members were stored through their own types.
1786 let mut names = Interner::new();
1787 let (module, plane, _) = typed(&mut names, "copy.c");
1788 let mut func = reading(&mut names, None);
1789
1790 assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).asked, 0);
1791 let printed = print_func(&module, &func, &names);
1792 assert!(!printed.contains("check_type"), "{printed}");
1793 }
1794
1795 /// A function that writes through its parameter as an `int`, naming that type on the access.
1796 fn writing(names: &mut Interner, node: Option<Meta>) -> Func {
1797 let i32_ = Type::int(32);
1798 let mut func =
1799 Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i32_]));
1800 let entry = func.create_block();
1801 let p = func.append_param(entry, Type::PTR);
1802 let v = func.append_param(entry, i32_);
1803 let info = MemInfo {
1804 size: 0,
1805 align: 4,
1806 order: MemOrder::NotAtomic,
1807 tbaa: node,
1808 owns: 0,
1809 restrict: Restrict::NONE,
1810 };
1811 let mut b = Builder::new(&mut func, entry);
1812 let args = b.func().push_values(&[v, p]);
1813 let extra = Extra::Mem(b.func().add_mem(info));
1814 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1815 b.ret(&[]);
1816 func
1817 }
1818
1819 #[test]
1820 fn a_store_asks_the_plane_too_once_the_build_has_said_the_member_matters() {
1821 // Row S4, which is a write that leaves one member and lands in the next. Read literally a
1822 // store like that is a program retyping storage it owns, which C 6.5 permits, so the
1823 // question is only put when somebody asked for it to be put.
1824 let mut names = Interner::new();
1825 let (module, plane, int) = typed(&mut names, "member.c");
1826 let mut func = writing(&mut names, Some(int));
1827
1828 let counts = insert(&mut func, &plane, 8, Subobject::Members, Promise::Off, Races::Off);
1829 assert_eq!((counts.asked, counts.judged), (1, 1));
1830
1831 let printed = print_func(&module, &func, &names);
1832 let entry = plane.entry(Some(int));
1833 let wanted = format!("check_type %2, %0, size 4, align 4, tbaa !{}\n", entry.index());
1834 assert!(printed.contains(&wanted), "{printed}");
1835 // In front of the store, because the bytes say what they said before it runs, and the
1836 // recording this pass makes afterwards is what would make the answer yes.
1837 let asked = printed.find(&wanted).expect("the check is there");
1838 let wrote = printed.find("store %1").expect("and so is the store");
1839 let recorded = printed.find("meta_type").expect("and so is the recording");
1840 assert!(asked < wrote && wrote < recorded, "{printed}");
1841
1842 if let Err(errors) = verify_func(&module, &func, &names) {
1843 panic!("that was expected to be believed: {errors:#?}");
1844 }
1845 }
1846
1847 #[test]
1848 fn a_store_the_front_end_named_no_type_for_asks_nothing_whatever_the_build_asked() {
1849 // The same reason a read of one does not. An access with no aliasing node is one the front
1850 // end did not say the type of, which is not the same as bytes nothing has been stored
1851 // through, and the plane has no way to tell the question apart from the answer.
1852 let mut names = Interner::new();
1853 let (module, plane, _) = typed(&mut names, "aggregate.c");
1854 let mut func = writing(&mut names, None);
1855
1856 assert_eq!(
1857 insert(&mut func, &plane, 8, Subobject::Members, Promise::Off, Races::Off).asked,
1858 0
1859 );
1860 let printed = print_func(&module, &func, &names);
1861 assert!(!printed.contains("check_type"), "{printed}");
1862 }
1863
1864 #[test]
1865 fn a_store_answers_the_question_rather_than_asking_it() {
1866 // The plane covers storage the allocator reported, which is the storage C gives no declared
1867 // type, and the effective type of one of those is whatever the last store set. So a store
1868 // cannot disagree with the plane unless the build asked it to, and a check in front of one
1869 // by default would refuse the reuse of a buffer that the standard permits.
1870 let mut names = Interner::new();
1871 let (module, plane, int) = typed(&mut names, "write.c");
1872 let i32_ = Type::int(32);
1873 let mut func =
1874 Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i32_]));
1875 let entry = func.create_block();
1876 let p = func.append_param(entry, Type::PTR);
1877 let v = func.append_param(entry, i32_);
1878 let info = MemInfo {
1879 size: 0,
1880 align: 4,
1881 order: MemOrder::NotAtomic,
1882 tbaa: Some(int),
1883 owns: 0,
1884 restrict: Restrict::NONE,
1885 };
1886 let mut b = Builder::new(&mut func, entry);
1887 let args = b.func().push_values(&[v, p]);
1888 let extra = Extra::Mem(b.func().add_mem(info));
1889 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1890 b.ret(&[]);
1891
1892 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
1893 assert_eq!((counts.judged, counts.asked), (1, 0));
1894 let printed = print_func(&module, &func, &names);
1895 assert!(!printed.contains("check_type"), "{printed}");
1896 }
1897
1898 #[test]
1899 fn a_store_records_the_type_it_stored_through() {
1900 // The judgement of C 6.5, which is the half of the type plane the compiler makes rather
1901 // than asks. The access names a type, so the entry the store records is that type rather
1902 // than the distinguished value a store that names nothing records.
1903 let mut names = Interner::new();
1904 let (module, plane, int) = typed(&mut names, "typed.c");
1905
1906 let i32_ = Type::int(32);
1907 let mut func =
1908 Func::new(names.intern("record"), Signature::new().with_params(&[Type::PTR, i32_]));
1909 let entry = func.create_block();
1910 let p = func.append_param(entry, Type::PTR);
1911 let v = func.append_param(entry, i32_);
1912 let info = MemInfo {
1913 size: 0,
1914 align: 4,
1915 order: MemOrder::NotAtomic,
1916 tbaa: Some(int),
1917 owns: 0,
1918 restrict: Restrict::NONE,
1919 };
1920 let mut b = Builder::new(&mut func, entry);
1921 let args = b.func().push_values(&[v, p]);
1922 let extra = Extra::Mem(b.func().add_mem(info));
1923 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1924 b.ret(&[]);
1925
1926 assert_eq!(
1927 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).judged,
1928 1
1929 );
1930
1931 let printed = print_func(&module, &func, &names);
1932 // Four bytes, which the payload does not say and the type of the value stored does.
1933 assert!(printed.contains("%3 = iconst.i64 4\n"), "{printed}");
1934 // The entry for `int`, which is the node the plane made for the node the access named.
1935 let entry = plane.entry(Some(int));
1936 assert_eq!(module[entry], MetaNode::Plane(PlaneNode::Type(int)));
1937 let wanted = format!("meta_type %0, %3, tbaa !{}\n", entry.index());
1938 assert!(printed.contains(&wanted), "{printed}");
1939
1940 if let Err(errors) = verify_func(&module, &func, &names) {
1941 panic!("that was expected to be believed: {errors:#?}");
1942 }
1943 }
1944
1945 #[test]
1946 fn a_store_records_that_the_bytes_it_wrote_hold_something() {
1947 // The init plane's half of the same store. One bit per byte and nothing else, so the write
1948 // carries a range and no type, and the range is the width of the value stored rather than
1949 // anything the payload says. A store of eight bytes makes eight bytes readable however it
1950 // came to be written.
1951 let mut names = Interner::new();
1952 let (module, plane) = planed(&mut names, "wrote.c");
1953
1954 let i64_ = Type::int(64);
1955 let mut func =
1956 Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
1957 let entry = func.create_block();
1958 let p = func.append_param(entry, Type::PTR);
1959 let v = func.append_param(entry, i64_);
1960 let info = MemInfo {
1961 size: 0,
1962 align: 8,
1963 order: MemOrder::NotAtomic,
1964 tbaa: None,
1965 owns: 0,
1966 restrict: Restrict::NONE,
1967 };
1968 let mut b = Builder::new(&mut func, entry);
1969 let args = b.func().push_values(&[v, p]);
1970 let extra = Extra::Mem(b.func().add_mem(info));
1971 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1972 b.ret(&[]);
1973
1974 assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).wrote, 1);
1975
1976 let printed = print_func(&module, &func, &names);
1977 assert!(printed.contains("%4 = iconst.i64 8\n meta_init %0, %4\n"), "{printed}");
1978
1979 if let Err(errors) = verify_func(&module, &func, &names) {
1980 panic!("that was expected to be believed: {errors:#?}");
1981 }
1982 }
1983
1984 #[test]
1985 fn a_store_of_a_pointer_records_which_thread_wrote_it_and_a_store_of_a_number_does_not() {
1986 // Section 9.5's recording half, and the thinning that is the whole reason it is affordable.
1987 // The plane holds one stamp per eight bytes because eight bytes is what a pointer comes in,
1988 // so a granule two threads share is one holding no pointer and one no race class asks
1989 // about. Recording every store would put those two threads in the plane as each other's
1990 // strangers, which is a report about a program doing nothing wrong.
1991 let mut names = Interner::new();
1992 let (module, plane) = planed(&mut names, "stamp.c");
1993
1994 let i64_ = Type::int(64);
1995 let mut func = Func::new(
1996 names.intern("stamp"),
1997 Signature::new().with_params(&[Type::PTR, Type::PTR, i64_]),
1998 );
1999 let entry = func.create_block();
2000 let p = func.append_param(entry, Type::PTR);
2001 let q = func.append_param(entry, Type::PTR);
2002 let v = func.append_param(entry, i64_);
2003 let info = MemInfo {
2004 size: 0,
2005 align: 8,
2006 order: MemOrder::NotAtomic,
2007 tbaa: None,
2008 owns: 0,
2009 restrict: Restrict::NONE,
2010 };
2011 let mut b = Builder::new(&mut func, entry);
2012 let extra = Extra::Mem(b.func().add_mem(info));
2013 let args = b.func().push_values(&[q, p]);
2014 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
2015 let args = b.func().push_values(&[v, p]);
2016 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
2017 b.ret(&[]);
2018
2019 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
2020 assert_eq!(counts.wrote, 2, "the init plane takes both, since both wrote bytes");
2021 assert_eq!(counts.stamped, 1, "and the epoch plane takes the one that wrote a pointer");
2022 assert_eq!(counts.watched, 1, "which is also the one that asks what was there before");
2023
2024 let printed = print_func(&module, &func, &names);
2025 assert_eq!(printed.matches("meta_epoch").count(), 1, "{printed}");
2026 assert!(printed.contains("meta_epoch %0, %"), "{printed}");
2027
2028 // In front of the store and the recording behind it, which is the order the reading half
2029 // depends on: the recording overwrites the stamp the check reads, so a check on the other
2030 // side of the store would be asking about the write it was called for.
2031 assert_eq!(printed.matches("check_race").count(), 1, "{printed}");
2032 let asked = printed.find("check_race").expect("the check is in there");
2033 let stamp = printed.find("meta_epoch").expect("so is the recording");
2034 assert!(asked < stamp, "{printed}");
2035
2036 if let Err(errors) = verify_func(&module, &func, &names) {
2037 panic!("that was expected to be believed: {errors:#?}");
2038 }
2039 }
2040
2041 #[test]
2042 fn nothing_records_into_the_epoch_plane_unless_the_build_asked_for_it() {
2043 // The default, and the reason it is the default is not cost. Every ordering the monitor has
2044 // was carried by an edge somebody interposed, and the atomics are not a call, so until the
2045 // compiler emits those edges a program that hands a pointer between threads through one
2046 // would be reported for doing nothing wrong. This is the one plane where instrumentation
2047 // nobody wrote costs a false report rather than a missed one.
2048 let mut names = Interner::new();
2049 let (module, plane) = planed(&mut names, "quiet.c");
2050
2051 let mut func =
2052 Func::new(names.intern("quiet"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
2053 let entry = func.create_block();
2054 let p = func.append_param(entry, Type::PTR);
2055 let q = func.append_param(entry, Type::PTR);
2056 let info = MemInfo {
2057 size: 0,
2058 align: 8,
2059 order: MemOrder::NotAtomic,
2060 tbaa: None,
2061 owns: 0,
2062 restrict: Restrict::NONE,
2063 };
2064 let mut b = Builder::new(&mut func, entry);
2065 let args = b.func().push_values(&[q, p]);
2066 let extra = Extra::Mem(b.func().add_mem(info));
2067 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
2068 b.ret(&[]);
2069
2070 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2071 assert_eq!((counts.stamped, counts.watched), (0, 0));
2072 let printed = print_func(&module, &func, &names);
2073 assert!(!printed.contains("meta_epoch"), "{printed}");
2074 assert!(!printed.contains("check_race"), "{printed}");
2075 }
2076
2077 #[test]
2078 fn a_read_of_a_pointer_asks_about_races_only_in_the_mode_that_reports_them() {
2079 // The one thing separating the two modes. A store asking is C3, two threads writing the
2080 // same slot, and a read asking is C2, the pointer word race, which section 9.5 lists apart
2081 // from the rest because it is reported in its own right. Tier E carries `metadata` and not
2082 // that, so a build wanting every race a read can see asks for it by name.
2083 let mut names = Interner::new();
2084 let (module, plane) = planed(&mut names, "read.c");
2085
2086 let mut quiet = one_pointer_read(&mut names);
2087 let counts = insert(&mut quiet, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
2088 assert_eq!(counts.watched, 0);
2089 assert!(!print_func(&module, &quiet, &names).contains("check_race"));
2090
2091 let mut asking = one_pointer_read(&mut names);
2092 let counts = insert(&mut asking, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
2093 assert_eq!(counts.watched, 1);
2094
2095 // Over the target's pointer width rather than over one byte, which is what #953 was about
2096 // and is the reason the width is a parameter of this pass at all.
2097 let printed = print_func(&module, &asking, &names);
2098 assert!(printed.contains("check_race %1, %0, size 8, align 1"), "{printed}");
2099
2100 if let Err(errors) = verify_func(&module, &asking, &names) {
2101 panic!("that was expected to be believed: {errors:#?}");
2102 }
2103 }
2104
2105 #[test]
2106 fn a_read_of_a_number_asks_nothing_even_in_the_mode_that_watches_reads() {
2107 // The same thinning the recording makes, from the other side. A granule two threads both
2108 // reach is a granule holding no pointer, so nothing ever stamped it and a check over it
2109 // would be a load of the plane that can only answer no.
2110 let mut names = Interner::new();
2111 let (module, plane) = planed(&mut names, "number.c");
2112
2113 let mut func = one_number_read(&mut names);
2114 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
2115 assert_eq!(counts.watched, 0);
2116 assert!(!print_func(&module, &func, &names).contains("check_race"));
2117 }
2118
2119 /// An atomic store, an atomic load and an atomic read-modify-write in one function.
2120 ///
2121 /// Each takes the ordering it is given, so one builder covers every case the edges have an
2122 /// opinion about: which side a marker lands on, and whether one lands at all.
2123 fn three_atomics(names: &mut Interner, store: MemOrder, load: MemOrder, rmw: MemOrder) -> Func {
2124 let i64_ = Type::int(64);
2125 let mut func =
2126 Func::new(names.intern("atomics"), Signature::new().with_params(&[Type::PTR, i64_]));
2127 let entry = func.create_block();
2128 let p = func.append_param(entry, Type::PTR);
2129 let v = func.append_param(entry, i64_);
2130 let info = MemInfo {
2131 size: 8,
2132 align: 8,
2133 order: MemOrder::NotAtomic,
2134 tbaa: None,
2135 owns: 0,
2136 restrict: Restrict::NONE,
2137 };
2138
2139 let mut b = Builder::new(&mut func, entry);
2140 let extra = Extra::Mem(b.func().add_mem(MemInfo { order: store, ..info }));
2141 let args = b.func().push_values(&[v, p]);
2142 b.inst(InstData { args, extra, ..InstData::new(Opcode::AtomicStore) }, &[]);
2143
2144 let extra = Extra::Mem(b.func().add_mem(MemInfo { order: load, ..info }));
2145 let args = b.func().push_values(&[p]);
2146 b.inst(InstData { args, extra, ..InstData::new(Opcode::AtomicLoad) }, &[i64_]);
2147
2148 let at = b.func().add_mem(MemInfo { order: rmw, ..info });
2149 let args = b.func().push_values(&[p, v]);
2150 let extra = Extra::Rmw(RmwOp::Add, at);
2151 b.inst(InstData { args, extra, ..InstData::new(Opcode::AtomicRmw) }, &[i64_]);
2152 b.ret(&[]);
2153 func
2154 }
2155
2156 #[test]
2157 fn a_release_publishes_in_front_of_its_atomic_and_an_acquire_takes_after_it() {
2158 // The edges of section 9.5 that are not a call and so have nowhere to be interposed. Which
2159 // side a marker lands on is which side the ordering is on: a release publishes everything
2160 // the thread has already done, so the clock has to be written down while that is still
2161 // true, and an acquire takes an ordering from what the atomic just read, which is not there
2162 // to be taken until the atomic has run.
2163 let mut names = Interner::new();
2164 let (module, plane) = planed(&mut names, "edges.c");
2165
2166 let mut func =
2167 three_atomics(&mut names, MemOrder::Release, MemOrder::Acquire, MemOrder::Relaxed);
2168 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
2169 assert_eq!(counts.edged, 2, "the release and the acquire, and not the relaxed one");
2170
2171 let printed = print_func(&module, &func, &names);
2172 assert_eq!(printed.matches("meta_release").count(), 1, "{printed}");
2173 assert_eq!(printed.matches("meta_acquire").count(), 1, "{printed}");
2174 assert!(printed.contains("meta_release %0\n atomic_store"), "{printed}");
2175 assert!(printed.contains("= atomic_load"), "{printed}");
2176 let read = printed.find("atomic_load").expect("the load is in there");
2177 let took = printed.find("meta_acquire").expect("and so is the edge it takes");
2178 assert!(read < took, "{printed}");
2179
2180 if let Err(errors) = verify_func(&module, &func, &names) {
2181 panic!("that was expected to be believed: {errors:#?}");
2182 }
2183 }
2184
2185 #[test]
2186 fn a_read_modify_write_that_orders_both_ways_carries_both_halves_of_an_edge() {
2187 // `acq_rel` and `seq_cst` publish and take, which is what makes a lock built out of one
2188 // compare and exchange a lock this can follow. One marker each side, since the two are
2189 // about different moments and collapsing them would put the publication after the read.
2190 let mut names = Interner::new();
2191 let (module, plane) = planed(&mut names, "both.c");
2192
2193 let mut func =
2194 three_atomics(&mut names, MemOrder::Relaxed, MemOrder::Relaxed, MemOrder::AcqRel);
2195 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
2196 assert_eq!(counts.edged, 2, "one of each, around the one atomic that orders anything");
2197
2198 let printed = print_func(&module, &func, &names);
2199 let published = printed.find("meta_release").expect("the publishing half is in there");
2200 let changed = printed.find("atomic_rmw").expect("so is the atomic");
2201 let took = printed.find("meta_acquire").expect("and so is the taking half");
2202 assert!(published < changed && changed < took, "{printed}");
2203
2204 if let Err(errors) = verify_func(&module, &func, &names) {
2205 panic!("that was expected to be believed: {errors:#?}");
2206 }
2207 }
2208
2209 #[test]
2210 fn a_relaxed_atomic_is_no_edge_and_neither_is_any_atomic_in_a_build_that_is_not_watching() {
2211 // Relaxed is atomic and is not an ordering. It says the word does not tear and it says
2212 // nothing about what happened either side of it, so an edge taken there would be an
2213 // ordering that does not exist, and inventing one hides the races it covers up.
2214 let mut names = Interner::new();
2215 let (module, plane) = planed(&mut names, "relaxed.c");
2216
2217 let mut loose =
2218 three_atomics(&mut names, MemOrder::Relaxed, MemOrder::Relaxed, MemOrder::Relaxed);
2219 let counts = insert(&mut loose, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
2220 assert_eq!(counts.edged, 0);
2221 let printed = print_func(&module, &loose, &names);
2222 assert!(
2223 !printed.contains("meta_release") && !printed.contains("meta_acquire"),
2224 "{printed}"
2225 );
2226
2227 // And nothing at all without the flag, which is where every other part of this plane is.
2228 let mut off =
2229 three_atomics(&mut names, MemOrder::SeqCst, MemOrder::SeqCst, MemOrder::SeqCst);
2230 let counts = insert(&mut off, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2231 assert_eq!(counts.edged, 0);
2232 let printed = print_func(&module, &off, &names);
2233 assert!(
2234 !printed.contains("meta_release") && !printed.contains("meta_acquire"),
2235 "{printed}"
2236 );
2237 }
2238
2239 /// One fence with the ordering it is given, and nothing else in the function.
2240 fn one_fence(names: &mut Interner, order: MemOrder) -> Func {
2241 let mut func = Func::new(names.intern("fenced"), Signature::new());
2242 let entry = func.create_block();
2243 let mut b = Builder::new(&mut func, entry);
2244 b.inst(InstData { extra: Extra::Order(order), ..InstData::new(Opcode::Fence) }, &[]);
2245 b.ret(&[]);
2246 func
2247 }
2248
2249 #[test]
2250 fn a_fence_carries_the_same_edge_as_an_atomic_with_no_object_to_key_it_on() {
2251 // The other way a C program orders two threads without calling anything. A fence orders
2252 // against every other thread rather than against one object, so the markers take no
2253 // operands: there is no address in a fence that could be the key, and the relaxed atomic
2254 // beside it in the source is not the key either, since what the fence orders is everything
2255 // the thread did rather than that one word.
2256 let mut names = Interner::new();
2257 let (module, plane) = planed(&mut names, "fence.c");
2258
2259 let mut func = one_fence(&mut names, MemOrder::SeqCst);
2260 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
2261 assert_eq!(counts.edged, 2, "seq_cst publishes and takes, so one of each");
2262
2263 let printed = print_func(&module, &func, &names);
2264 assert!(
2265 printed.contains(
2266 "meta_fence_release
2267 fence"
2268 ),
2269 "{printed}"
2270 );
2271 assert!(
2272 printed.contains(
2273 "fence seq_cst
2274 meta_fence_acquire"
2275 ),
2276 "{printed}"
2277 );
2278
2279 if let Err(errors) = verify_func(&module, &func, &names) {
2280 panic!("that was expected to be believed: {errors:#?}");
2281 }
2282 }
2283
2284 #[test]
2285 fn a_release_fence_publishes_and_an_acquire_fence_takes_and_neither_does_the_other() {
2286 // The halves apart, which is the shape a fence is usually written in: a release fence
2287 // after the record is filled and an acquire fence before it is read.
2288 let mut names = Interner::new();
2289 let (module, plane) = planed(&mut names, "halves.c");
2290
2291 let mut publishing = one_fence(&mut names, MemOrder::Release);
2292 let counts =
2293 insert(&mut publishing, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
2294 assert_eq!(counts.edged, 1);
2295 let printed = print_func(&module, &publishing, &names);
2296 assert!(printed.contains("meta_fence_release"), "{printed}");
2297 assert!(!printed.contains("meta_fence_acquire"), "{printed}");
2298
2299 let mut taking = one_fence(&mut names, MemOrder::Acquire);
2300 let counts = insert(&mut taking, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
2301 assert_eq!(counts.edged, 1);
2302 let printed = print_func(&module, &taking, &names);
2303 assert!(printed.contains("meta_fence_acquire"), "{printed}");
2304 assert!(!printed.contains("meta_fence_release"), "{printed}");
2305
2306 // And nothing at all without the flag, the same as every other part of this plane.
2307 let mut off = one_fence(&mut names, MemOrder::SeqCst);
2308 let counts = insert(&mut off, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2309 assert_eq!(counts.edged, 0);
2310 assert!(!print_func(&module, &off, &names).contains("meta_fence"));
2311 }
2312
2313 #[test]
2314 fn a_store_that_owns_the_padding_after_it_records_that_too() {
2315 // `-fsafety-init=nopadding`, which by the time it gets here is a number on the store and
2316 // nothing else. A `char` member with three bytes of padding behind it owns four, so the
2317 // record it is in comes out whole once the other member is written and the ordinary reads
2318 // of one, which are a `memcmp` or a hash or a `write`, are not refused. Working out what
2319 // the padding is takes a record's layout, which the front end has and this pass does not,
2320 // and that is why the number arrives rather than the mode.
2321 let mut names = Interner::new();
2322 let (module, plane) = planed(&mut names, "owns.c");
2323
2324 let byte = Type::int(8);
2325 let mut func =
2326 Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, byte]));
2327 let entry = func.create_block();
2328 let p = func.append_param(entry, Type::PTR);
2329 let v = func.append_param(entry, byte);
2330 let info = MemInfo {
2331 size: 0,
2332 align: 1,
2333 order: MemOrder::NotAtomic,
2334 tbaa: None,
2335 owns: 4,
2336 restrict: Restrict::NONE,
2337 };
2338 let mut b = Builder::new(&mut func, entry);
2339 let args = b.func().push_values(&[v, p]);
2340 let extra = Extra::Mem(b.func().add_mem(info));
2341 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
2342 b.ret(&[]);
2343
2344 assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).wrote, 1);
2345
2346 let printed = print_func(&module, &func, &names);
2347 // Four rather than the one byte the store wrote.
2348 assert!(printed.contains("%4 = iconst.i64 4\n meta_init %0, %4\n"), "{printed}");
2349 // And the bounds check is still about the one byte the store touches, since the padding
2350 // is what a store records and not what it writes.
2351 assert!(printed.contains("check_bounds %2, %0, size 1"), "{printed}");
2352
2353 if let Err(errors) = verify_func(&module, &func, &names) {
2354 panic!("that was expected to be believed: {errors:#?}");
2355 }
2356 }
2357
2358 #[test]
2359 fn a_read_asks_whether_anything_ever_wrote_the_bytes_it_is_about_to_read() {
2360 // Document 03's Y6. The question carries the access's width and no type, because the plane
2361 // holds one bit per byte and the bit says whether anything was stored there at all.
2362 let mut names = Interner::new();
2363 let (module, plane) = planed(&mut names, "ask.c");
2364 let mut func = reading(&mut names, None);
2365
2366 assert_eq!(
2367 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).filled,
2368 1
2369 );
2370
2371 let printed = print_func(&module, &func, &names);
2372 assert!(printed.contains("check_init %1, %0, size 4, align 4\n"), "{printed}");
2373
2374 if let Err(errors) = verify_func(&module, &func, &names) {
2375 panic!("that was expected to be believed: {errors:#?}");
2376 }
2377 }
2378
2379 #[test]
2380 fn a_read_the_front_end_named_no_type_for_still_asks_the_init_plane() {
2381 // The one place the two questions a read asks come apart. A read with no type on it has
2382 // nothing to ask the type plane, because the question there is which type the bytes hold,
2383 // and it has the same thing to ask the init plane as any other read, because the question
2384 // there is about the bytes rather than about the access.
2385 let mut names = Interner::new();
2386 let (_module, plane) = planed(&mut names, "untyped.c");
2387 let mut func = reading(&mut names, None);
2388
2389 let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2390 assert_eq!(counts.asked, 0);
2391 assert_eq!(counts.filled, 1);
2392 }
2393
2394 #[test]
2395 fn a_store_asks_the_init_plane_nothing() {
2396 // A store writes the bytes it is about to write, so whether anything wrote them before is
2397 // not a question about it. Asking would refuse the first write to every fresh instance,
2398 // which is every program.
2399 let mut names = Interner::new();
2400 let (module, plane) = planed(&mut names, "store.c");
2401
2402 let i64_ = Type::int(64);
2403 let mut func =
2404 Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
2405 let entry = func.create_block();
2406 let p = func.append_param(entry, Type::PTR);
2407 let v = func.append_param(entry, i64_);
2408 let info = MemInfo {
2409 size: 8,
2410 align: 8,
2411 order: MemOrder::NotAtomic,
2412 tbaa: None,
2413 owns: 0,
2414 restrict: Restrict::NONE,
2415 };
2416 let mut b = Builder::new(&mut func, entry);
2417 let args = b.func().push_values(&[v, p]);
2418 let extra = Extra::Mem(b.func().add_mem(info));
2419 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
2420 b.ret(&[]);
2421
2422 assert_eq!(
2423 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).filled,
2424 0
2425 );
2426
2427 let printed = print_func(&module, &func, &names);
2428 assert!(!printed.contains("check_init"), "{printed}");
2429 }
2430
2431 #[test]
2432 fn a_read_tells_the_init_plane_nothing() {
2433 // A read is a question and not a judgement. Whether the bytes it read hold anything is
2434 // what the plane already says, and a read that wrote the plane would make every read of
2435 // storage nothing ever wrote look like a read of storage something did.
2436 let mut names = Interner::new();
2437 let (module, plane) = planed(&mut names, "read.c");
2438 let mut func = reading(&mut names, None);
2439
2440 assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off).wrote, 0);
2441
2442 let printed = print_func(&module, &func, &names);
2443 assert!(!printed.contains("meta_init"), "{printed}");
2444 }
2445
2446 /// A function that copies a fixed number of bytes from one of its parameters to the other.
2447 fn one_copy(names: &mut Interner, opcode: Opcode) -> Func {
2448 let mut func =
2449 Func::new(names.intern("move"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
2450 let entry = func.create_block();
2451 let to = func.append_param(entry, Type::PTR);
2452 let from = func.append_param(entry, Type::PTR);
2453
2454 let info = MemInfo {
2455 size: 24,
2456 align: 8,
2457 order: MemOrder::NotAtomic,
2458 tbaa: None,
2459 owns: 0,
2460 restrict: Restrict::NONE,
2461 };
2462 let mut b = Builder::new(&mut func, entry);
2463 let args = b.func().push_values(&[to, from]);
2464 let extra = Extra::Mem(b.func().add_mem(info));
2465 b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
2466 b.ret(&[]);
2467 func
2468 }
2469
2470 #[test]
2471 fn a_copy_carries_whatever_the_bytes_it_read_said() {
2472 // The other half of the judgement C 6.5 describes. A copy does not store through a type, so
2473 // there is nothing here for the compiler to name: what the copied bytes are is whatever the
2474 // bytes they came from were, and the plane over the source is the only place that is
2475 // written down. Without this the destination would go on saying whatever was there before.
2476 let mut names = Interner::new();
2477 let mut func = one_copy(&mut names, Opcode::Memcpy);
2478 let (module, plane) = planed(&mut names, "move.c");
2479 assert_eq!(
2480 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
2481 Counts { checked: 2, live: 2, carried: 1, moved: 1, relocated: 1, ..Counts::default() }
2482 );
2483
2484 assert_eq!(
2485 print_func(&module, &func, &names),
2486 // The four checks in front and the three copies behind, which is where a store's two
2487 // and a store's judgement go for the same reasons.
2488 "func @move(ptr, ptr), linkage(external) {\n\
2489 block0(%0: ptr, %1: ptr):\n \
2490 %2 = cap_of %1\n \
2491 %3 = cap_of %0\n \
2492 check_bounds %3, %0, size 24, align 8\n \
2493 check_live %3, %0\n \
2494 check_bounds %2, %1, size 24, align 8\n \
2495 check_live %2, %1\n \
2496 memcpy %0, %1, size 24, align 8\n \
2497 %4 = iconst.i64 24\n \
2498 meta_type_copy %0, %1, %4\n \
2499 %5 = iconst.i64 24\n \
2500 meta_init_copy %0, %1, %5\n \
2501 %6 = iconst.i64 24\n \
2502 cap_copy %0, %1, %6\n \
2503 return\n\
2504 }\n"
2505 );
2506
2507 if let Err(errors) = verify_func(&module, &func, &names) {
2508 panic!("that was expected to be believed: {errors:#?}");
2509 }
2510 }
2511
2512 #[test]
2513 fn a_copy_whose_ranges_may_overlap_is_carried_the_same_way() {
2514 // `memmove` is `memcpy` with the overlap allowed, and the overlap is the runtime's problem
2515 // rather than this pass's: a copy writes no plane entries of its own, so the entries over
2516 // the source are the same ones whichever end the bytes were moved from.
2517 let mut names = Interner::new();
2518 let mut func = one_copy(&mut names, Opcode::Memmove);
2519 let (module, plane) = planed(&mut names, "overlap.c");
2520 assert_eq!(
2521 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
2522 Counts { checked: 2, live: 2, carried: 1, moved: 1, relocated: 1, ..Counts::default() }
2523 );
2524
2525 let printed = print_func(&module, &func, &names);
2526 assert!(printed.contains("check_bounds %3, %0, size 24, align 8\n"), "{printed}");
2527 assert!(printed.contains("check_bounds %2, %1, size 24, align 8\n"), "{printed}");
2528 assert!(printed.contains("meta_type_copy %0, %1, %4\n"), "{printed}");
2529 assert!(printed.contains("meta_init_copy %0, %1, %5\n"), "{printed}");
2530 assert!(printed.contains("cap_copy %0, %1, %6\n"), "{printed}");
2531 }
2532
2533 #[test]
2534 fn a_copy_of_a_length_the_program_works_out_carries_it_with_the_length_it_was_given() {
2535 // The three plane writes are the same three, and the count is the copy's own operand
2536 // rather than a constant made beside it, because there is no constant to make. They go in
2537 // after the copy, which is where the constant ones go in too.
2538 let mut names = Interner::new();
2539 let mut func = Func::new(
2540 names.intern("move"),
2541 Signature::new().with_params(&[Type::PTR, Type::PTR, Type::int(64)]),
2542 );
2543 let entry = func.create_block();
2544 let to = func.append_param(entry, Type::PTR);
2545 let from = func.append_param(entry, Type::PTR);
2546 let length = func.append_param(entry, Type::int(64));
2547
2548 let info = MemInfo {
2549 size: 0,
2550 align: 8,
2551 order: MemOrder::NotAtomic,
2552 tbaa: None,
2553 owns: 0,
2554 restrict: Restrict::NONE,
2555 };
2556 let mut b = Builder::new(&mut func, entry);
2557 let args = b.func().push_values(&[to, from, length]);
2558 let extra = Extra::Mem(b.func().add_mem(info));
2559 b.inst(InstData { args, extra, ..InstData::new(Opcode::Memcpy) }, &[]);
2560 b.ret(&[]);
2561
2562 let (module, plane) = planed(&mut names, "move.c");
2563 assert_eq!(
2564 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
2565 Counts { checked: 2, live: 2, carried: 1, moved: 1, relocated: 1, ..Counts::default() }
2566 );
2567
2568 assert_eq!(
2569 print_func(&module, &func, &names),
2570 "func @move(ptr, ptr, i64), linkage(external) {\n\
2571 block0(%0: ptr, %1: ptr, %2: i64):\n \
2572 %3 = cap_of %1\n \
2573 %4 = cap_of %0\n \
2574 check_bounds %4, %0, %2, align 8\n \
2575 check_live %4, %0\n \
2576 check_bounds %3, %1, %2, align 8\n \
2577 check_live %3, %1\n \
2578 memcpy %0, %1, %2, align 8\n \
2579 meta_type_copy %0, %1, %2\n \
2580 meta_init_copy %0, %1, %2\n \
2581 cap_copy %0, %1, %2\n \
2582 return\n\
2583 }\n"
2584 );
2585
2586 if let Err(errors) = verify_func(&module, &func, &names) {
2587 panic!("that was expected to be believed: {errors:#?}");
2588 }
2589 }
2590
2591 #[test]
2592 fn a_fill_is_checked_over_the_bytes_it_covers_and_records_that_it_wrote_them() {
2593 // Issue 1552. A fill went through this pass and came out with nothing at all in front of
2594 // it or behind it, so a `struct S s = {0};` was a write nothing bounded and a zeroed
2595 // object the init plane went on calling unwritten. The count is in the payload here, so
2596 // the bounds check reads it there and the two plane writes read a constant made beside
2597 // the fill.
2598 let mut names = Interner::new();
2599 let mut func = Func::new(
2600 names.intern("zero"),
2601 Signature::new().with_params(&[Type::PTR, Type::int(8)]),
2602 );
2603 let entry = func.create_block();
2604 let to = func.append_param(entry, Type::PTR);
2605 let byte = func.append_param(entry, Type::int(8));
2606
2607 let info = MemInfo {
2608 size: 24,
2609 align: 4,
2610 order: MemOrder::NotAtomic,
2611 tbaa: None,
2612 owns: 0,
2613 restrict: Restrict::NONE,
2614 };
2615 let mut b = Builder::new(&mut func, entry);
2616 let args = b.func().push_values(&[to, byte]);
2617 let extra = Extra::Mem(b.func().add_mem(info));
2618 b.inst(InstData { args, extra, ..InstData::new(Opcode::Memset) }, &[]);
2619 b.ret(&[]);
2620
2621 let (module, plane) = planed(&mut names, "zero.c");
2622 assert_eq!(
2623 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
2624 Counts { checked: 1, live: 1, wrote: 1, judged: 1, ..Counts::default() }
2625 );
2626
2627 assert_eq!(
2628 print_func(&module, &func, &names),
2629 "func @zero(ptr, i8), linkage(external) {\n\
2630 block0(%0: ptr, %1: i8):\n \
2631 %2 = cap_of %0\n \
2632 check_bounds %2, %0, size 24, align 4\n \
2633 check_live %2, %0\n \
2634 memset %0, %1, size 24, align 4\n \
2635 %3 = iconst.i64 24\n \
2636 meta_type %0, %3, tbaa !1\n \
2637 meta_init %0, %3\n \
2638 return\n\
2639 }\n"
2640 );
2641
2642 if let Err(errors) = verify_func(&module, &func, &names) {
2643 panic!("that was expected to be believed: {errors:#?}");
2644 }
2645 }
2646
2647 #[test]
2648 fn a_fill_of_a_length_the_program_works_out_is_checked_over_that_length() {
2649 // The other form, and the reason the bounds check in front of a fill is not the one
2650 // [`check`] puts in front of an access: there is no number in the payload to read, so the
2651 // count travels as the third operand `check_bounds` has for a length nobody knew when the
2652 // access was parsed, and the two plane writes read the fill's own operand.
2653 let mut names = Interner::new();
2654 let mut func = Func::new(
2655 names.intern("zero"),
2656 Signature::new().with_params(&[Type::PTR, Type::int(8), Type::int(64)]),
2657 );
2658 let entry = func.create_block();
2659 let to = func.append_param(entry, Type::PTR);
2660 let byte = func.append_param(entry, Type::int(8));
2661 let length = func.append_param(entry, Type::int(64));
2662
2663 let info = MemInfo {
2664 size: 0,
2665 align: 1,
2666 order: MemOrder::NotAtomic,
2667 tbaa: None,
2668 owns: 0,
2669 restrict: Restrict::NONE,
2670 };
2671 let mut b = Builder::new(&mut func, entry);
2672 let args = b.func().push_values(&[to, byte, length]);
2673 let extra = Extra::Mem(b.func().add_mem(info));
2674 b.inst(InstData { args, extra, ..InstData::new(Opcode::Memset) }, &[]);
2675 b.ret(&[]);
2676
2677 let (module, plane) = planed(&mut names, "zero.c");
2678 assert_eq!(
2679 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
2680 Counts { checked: 1, live: 1, wrote: 1, judged: 1, ..Counts::default() }
2681 );
2682
2683 assert_eq!(
2684 print_func(&module, &func, &names),
2685 "func @zero(ptr, i8, i64), linkage(external) {\n\
2686 block0(%0: ptr, %1: i8, %2: i64):\n \
2687 %3 = cap_of %0\n \
2688 check_bounds %3, %0, %2, align 1\n \
2689 check_live %3, %0\n \
2690 memset %0, %1, %2, align 1\n \
2691 meta_type %0, %2, tbaa !1\n \
2692 meta_init %0, %2\n \
2693 return\n\
2694 }\n"
2695 );
2696
2697 if let Err(errors) = verify_func(&module, &func, &names) {
2698 panic!("that was expected to be believed: {errors:#?}");
2699 }
2700 }
2701
2702 #[test]
2703 fn a_walk_over_elements_hands_the_check_the_width_of_one() {
2704 // The low end of judgement J2's window is one element below the object, so the check has
2705 // to be told how wide an element is. C computed a byte offset before the arithmetic
2706 // happened, so the width is not in the `ptr_add`, and what is in it is the multiply the
2707 // frontend left behind. This pass runs before the optimizer, so that shape is still there.
2708 let mut names = Interner::new();
2709 let mut func = Func::new(
2710 names.intern("walk"),
2711 Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
2712 );
2713 let entry = func.create_block();
2714 let p = func.append_param(entry, Type::PTR);
2715 let n = func.append_param(entry, Type::int(64));
2716
2717 let mut b = Builder::new(&mut func, entry);
2718 let width = b.iconst(Type::int(64), 24);
2719 let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
2720 let args = b.func().push_values(&[p, bytes]);
2721 let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
2722 b.ret(&[moved]);
2723
2724 let (module, plane) = planed(&mut names, "walk.c");
2725 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2726
2727 assert_eq!(
2728 print_func(&module, &func, &names),
2729 "func @walk(ptr, i64) -> ptr, linkage(external) {\n\
2730 block0(%0: ptr, %1: i64):\n \
2731 %2 = cap_of %0\n \
2732 %3 = iconst.i64 24\n \
2733 %4 = mul.nsw %1, %3\n \
2734 %5 = ptr_add %0, %4\n \
2735 check_deriv %2, %0, %5, %3\n \
2736 return %5\n\
2737 }\n"
2738 );
2739 }
2740
2741 #[test]
2742 fn a_walk_that_goes_backwards_is_still_a_walk_over_elements() {
2743 // Which is the case the whole widening is for. A walk backwards negates the byte offset
2744 // rather than the width, so the multiply is one instruction further down and the width is
2745 // the same one. Missing it here would mean `&a[-1]` getting a one byte window and being
2746 // refused, which is the report this change exists to stop.
2747 let mut names = Interner::new();
2748 let mut func = Func::new(
2749 names.intern("back"),
2750 Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
2751 );
2752 let entry = func.create_block();
2753 let p = func.append_param(entry, Type::PTR);
2754 let n = func.append_param(entry, Type::int(64));
2755
2756 let mut b = Builder::new(&mut func, entry);
2757 let width = b.iconst(Type::int(64), 24);
2758 let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
2759 let zero = b.iconst(Type::int(64), 0);
2760 let back = b.binary(Opcode::Sub, zero, bytes, Flags::NONE);
2761 let args = b.func().push_values(&[p, back]);
2762 let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
2763 b.ret(&[moved]);
2764
2765 let (module, plane) = planed(&mut names, "back.c");
2766 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2767
2768 let printed = print_func(&module, &func, &names);
2769 assert!(printed.contains("check_deriv %2, %0, %7, %3\n"), "{printed}");
2770 }
2771
2772 #[test]
2773 fn a_pointer_computed_from_another_pointer_is_checked_where_it_is_computed() {
2774 // Judgement J2. The pointer that walked off its object is caught at the arithmetic, not
2775 // at whatever line eventually reads through it, which is what lets the report name the
2776 // loop that ran too far. Note where the check sits: after the ptr_add, because it is
2777 // handed the pointer the ptr_add produced.
2778 let mut names = Interner::new();
2779 let mut func = Func::new(
2780 names.intern("walk"),
2781 Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
2782 );
2783 let entry = func.create_block();
2784 let p = func.append_param(entry, Type::PTR);
2785 let n = func.append_param(entry, Type::int(64));
2786
2787 let mut b = Builder::new(&mut func, entry);
2788 let args = b.func().push_values(&[p, n]);
2789 let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
2790 b.ret(&[moved]);
2791
2792 let (module, plane) = planed(&mut names, "walk.c");
2793 assert_eq!(
2794 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
2795 Counts { derived: 1, ..Counts::default() }
2796 );
2797
2798 assert_eq!(
2799 print_func(&module, &func, &names),
2800 // The stride is one, because the offset here is a block parameter and nothing about
2801 // it says what it is a count of. That is the answer a shape this pass does not
2802 // recognise gets, and it is the strict reading of C.
2803 "func @walk(ptr, i64) -> ptr, linkage(external) {\n\
2804 block0(%0: ptr, %1: i64):\n \
2805 %2 = cap_of %0\n \
2806 %3 = iconst.i64 1\n \
2807 %4 = ptr_add %0, %1\n \
2808 check_deriv %2, %0, %4, %3\n \
2809 return %4\n\
2810 }\n"
2811 );
2812
2813 if let Err(errors) = verify_func(&module, &func, &names) {
2814 panic!("that was expected to be believed: {errors:#?}");
2815 }
2816 }
2817
2818 #[test]
2819 fn a_walk_and_the_access_through_it_read_the_capability_of_what_it_walked_off() {
2820 // One capability for the whole walk, and it is the base's rather than the derived
2821 // pointer's. That is the cheap answer and it is also the right one: asking about an
2822 // interior pointer means recovering whichever object the plane says that address is in,
2823 // and for a pointer that has already run off the end of its own object that is somebody
2824 // else's, which is a bounds check that passes where it should refuse.
2825 let mut names = Interner::new();
2826 let i32_ = Type::int(32);
2827 let mut func = Func::new(
2828 names.intern("through"),
2829 Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[i32_]),
2830 );
2831 let entry = func.create_block();
2832 let p = func.append_param(entry, Type::PTR);
2833 let n = func.append_param(entry, Type::int(64));
2834
2835 let info = MemInfo {
2836 size: 4,
2837 align: 4,
2838 order: MemOrder::NotAtomic,
2839 tbaa: None,
2840 owns: 0,
2841 restrict: Restrict::NONE,
2842 };
2843 let mut b = Builder::new(&mut func, entry);
2844 let args = b.func().push_values(&[p, n]);
2845 let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
2846 let args = b.func().push_values(&[moved]);
2847 let extra = Extra::Mem(b.func().add_mem(info));
2848 let read = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
2849 b.ret(&[read]);
2850
2851 let (module, plane) = planed(&mut names, "through.c");
2852 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2853
2854 let printed = print_func(&module, &func, &names);
2855 assert_eq!(printed.matches("cap_of").count(), 1, "{printed}");
2856 assert!(printed.contains("check_deriv %2, %0, %4, %3\n"), "{printed}");
2857 assert!(printed.contains("check_bounds %2, %4, size 4, align 4\n"), "{printed}");
2858 }
2859
2860 #[test]
2861 fn a_pointer_read_out_of_memory_takes_its_capability_at_the_read() {
2862 // Out of the slot beside the word it came from, which is a `cap_load` and not a `cap_of`.
2863 // Reading the pointer and reading its capability is one event, so the producer sits behind
2864 // the load the way the fallback used to, and what changed is which producer it is rather
2865 // than where it goes.
2866 //
2867 // The container's capability is the first operand, because the slot is found from the
2868 // object the word lives in, and the one the outer access already took is the one used. So
2869 // this function asks the plane once, for the parameter, and the pointer it reads through
2870 // that parameter costs a slot read instead of a second walk.
2871 let mut names = Interner::new();
2872 let i32_ = Type::int(32);
2873 let mut func = Func::new(
2874 names.intern("indirect"),
2875 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
2876 );
2877 let entry = func.create_block();
2878 let p = func.append_param(entry, Type::PTR);
2879
2880 let mut b = Builder::new(&mut func, entry);
2881 let args = b.func().push_values(&[p]);
2882 let info = MemInfo {
2883 size: 0,
2884 align: 8,
2885 order: MemOrder::NotAtomic,
2886 tbaa: None,
2887 owns: 0,
2888 restrict: Restrict::NONE,
2889 };
2890 let extra = Extra::Mem(b.func().add_mem(info));
2891 let held = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR);
2892 let args = b.func().push_values(&[held]);
2893 let info = MemInfo { size: 4, align: 4, ..info };
2894 let extra = Extra::Mem(b.func().add_mem(info));
2895 let read = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
2896 b.ret(&[read]);
2897
2898 let (module, plane) = planed(&mut names, "indirect.c");
2899 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2900
2901 let printed = print_func(&module, &func, &names);
2902 assert!(
2903 printed.contains("%2 = load %0, align 8\n %3 = cap_load %1, %0, %2\n"),
2904 "{printed}"
2905 );
2906 assert!(printed.contains("check_bounds %3, %2, size 4, align 4\n"), "{printed}");
2907 assert_eq!(printed.matches("cap_of").count(), 1, "{printed}");
2908 }
2909
2910 #[test]
2911 fn what_it_produces_is_a_function_the_verifier_believes() {
2912 // The point of inserting checks as IR is that everything downstream may treat them as
2913 // IR, which is only true if the result is a module the verifier accepts.
2914 let mut names = Interner::new();
2915 let mut func = one_of_each(&mut names);
2916 let (module, plane) = planed(&mut names, "both.c");
2917 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
2918
2919 if let Err(errors) = verify_func(&module, &func, &names) {
2920 panic!("that was expected to be believed: {errors:#?}");
2921 }
2922 }
2923
2924 #[test]
2925 fn every_definition_in_a_module_is_walked_and_the_declarations_are_not() {
2926 let mut names = Interner::new();
2927 let one = one_of_each(&mut names);
2928 let mut two = one_of_each(&mut names);
2929 two.name = names.intern("other");
2930 // A declaration of a function defined somewhere else. There is no body to put a check in
2931 // and reaching for one would be a crash rather than a wrong answer.
2932 let declared = Func::new(
2933 names.intern("elsewhere"),
2934 Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::int(32)]),
2935 );
2936
2937 let mut module = Module::new(names.intern("two.c"), &target());
2938 module.add_func(one);
2939 module.add_func(two);
2940 module.add_func(declared);
2941
2942 assert_eq!(
2943 run(&mut module, Subobject::Off, Promise::Off, Races::Off),
2944 Counts { checked: 4, live: 4, judged: 2, wrote: 2, filled: 2, ..Counts::default() }
2945 );
2946 if let Err(errors) = rucc_ir::verify(&module, &names) {
2947 panic!("that was expected to be believed: {errors:#?}");
2948 }
2949 }
2950
2951 #[test]
2952 fn a_function_with_no_accesses_is_left_alone() {
2953 let mut names = Interner::new();
2954 let i32_ = Type::int(32);
2955 let mut func = Func::new(names.intern("nothing"), Signature::new().with_returns(&[i32_]));
2956 let entry = func.create_block();
2957 let mut b = Builder::new(&mut func, entry);
2958 let zero = b.iconst(i32_, 0);
2959 b.ret(&[zero]);
2960
2961 let (_module, plane) = planed(&mut names, "nothing.c");
2962 let before = func.counts();
2963 assert_eq!(
2964 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
2965 Counts::default()
2966 );
2967 assert_eq!(func.counts(), before);
2968 }
2969}