Skip to main content

rucc_safety/
lower.rs

1//! Turning the checks that survived the optimizer into something the back end can generate.
2//!
3//! Design: `spec/safe-memory/06-instrumentation.md` sections 6.3.1 and 6.5.
4//!
5//! [`crate::insert`] puts checks in before the optimizer runs, which is the whole argument of this
6//! crate. This is the other end of that: after the optimizer has discharged what it could prove,
7//! every check still standing becomes a call to `rucc-safe-rt`, and each call carries the address of
8//! a descriptor this pass writes into the object.
9//!
10//! # Why the checks are calls and not compares
11//!
12//! Section 6.3.1 wants a compare and a branch in the checked function with only the trap out of
13//! line, and that is not what this emits. The reason is in `rucc-safe-rt`'s `check` module and is
14//! the same one: the inline form needs the four word capability of document 05 section 5.2.1 live
15//! in registers at the check, and it needs the aux plane to recover one for a pointer that came out
16//! of memory. The capability representation is milestone S2 and the aux plane is S5. Handing the
17//! runtime an address is what can be written today.
18//!
19//! It is slow, and S1's exit criterion asks for the overhead to be measured rather than for it to
20//! be small. S4 is the milestone that makes it small, and it needs a number to improve on.
21//!
22//! # Why it runs after the optimizer
23//!
24//! Because a descriptor for a check that was deleted is data nothing will ever name. Running here
25//! means there is one for exactly the checks a program will actually run, which is also what makes
26//! the count a number worth reporting. The cost is that `--emit=ir` shows `check_bounds` rather than
27//! the call, which is the right way round: the IR a person reads should say what the compiler
28//! decided, not how it spelled it.
29//!
30//! # Why a check is handed an address and not a number
31//!
32//! Each descriptor is its own sixteen byte variable, internal and constant, and they all go in a
33//! section called `.rucc_safety_desc`, so the section is still the contiguous table
34//! `rucc_safe_rt::fail::Descriptor` describes and its length still divided by sixteen is still the
35//! number of checks in the object.
36//!
37//! What a check passes is the descriptor's address rather than its index, and that is the whole
38//! reason the descriptors are separate variables. An index is an index into *this object's* rows:
39//! link two instrumented objects together and the sections concatenate while both sets of indices
40//! still start at zero, so a runtime that read the section by index would report the wrong check.
41//! The other way out is for every object to contribute a base the runtime adds, which is a table of
42//! tables and a startup constructor to build it. An address needs neither. It is a relocation the
43//! linker already knows how to do, it costs the same one instruction the index cost, and the
44//! reporter reads it by dereferencing it.
45
46use std::collections::{HashMap, HashSet};
47
48use rucc_base::Interner;
49use rucc_ir::{
50    CallInfo, Datum, Extra, Flags, Func, Global, Imm, Inst, InstData, Linkage, Meta, Module,
51    Opcode, Signature, Type, Value,
52};
53
54use crate::plane;
55
56/// How wide one descriptor is, which `rucc_safe_rt::fail::Descriptor` fixes.
57pub const WIDTH: u64 = 16;
58
59/// The section the descriptors go in, which is how a reader finds all of them at once.
60pub const SECTION: &str = ".rucc_safety_desc";
61
62/// What each descriptor's name starts with, before the number that makes it unique.
63///
64/// Nothing outside the object ever resolves one, since every reference to one is inside the object
65/// that defines it. The name exists because a relocation needs a symbol to be against.
66const DESCRIPTOR: &str = "__rucc_safety_desc";
67
68/// Judgement J1 of document 04 section 4.4, which is what an access check decides.
69const ACCESS: u8 = 1;
70
71/// Judgement J2, which is what a derivation check decides.
72const DERIVE: u8 = 2;
73
74/// Judgement J6, which is what the check in front of a free decides.
75///
76/// Numbered apart from J1 because what it is about is the free rather than an access, which is the
77/// distinction document 04 section 4.4 draws between the two and which the reporter's wording for
78/// each of them already reads as.
79const FREE: u8 = 6;
80
81/// Judgement J8, which is what a `restrict` check decides.
82const RESTRICT: u8 = 8;
83
84/// Judgement J9, which is what a race check decides.
85///
86/// Numbered apart from J1 the way J8 is, and document 04 section 4.5 gives the reason: it is a
87/// relation between two operations rather than a property of one, so a report that said an access
88/// was not permitted would be describing the wrong thing.
89const RACE: u8 = 9;
90
91/// One descriptor, as much of it as this pass knows.
92///
93/// The `pc` field of the runtime's descriptor is not here. Filling it means a relocation against
94/// the enclosing function plus the offset of the call, which the IR cannot express and which
95/// nothing needs while the reporter has the address the check was given, so those eight bytes are
96/// written as zero.
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct Descriptor {
99    /// Which judgement the check decides.
100    pub judgement: u8,
101    /// Which row of document 03's tables the failure is, which nothing decides yet.
102    pub class: u8,
103    /// How many bytes the access covers, saturating, and zero where the check is not about an
104    /// access of a known width.
105    pub size: u16,
106}
107
108/// Turns every check in a module into a call, and gives the module the descriptors they name.
109///
110/// The number of descriptors, which is the number of checks that survived the optimizer. That is
111/// the numerator of everything document 13 measures and it is not recoverable afterwards, since by
112/// this point a discharged check is simply not there.
113///
114/// Whether this runs at all is `-fsafety=`, and the driver decides it, for the reason
115/// [`crate::run`] gives.
116pub fn lower(module: &mut Module, names: &mut Interner) -> usize {
117    // `size_t`, taken from the module rather than written as sixty four, so that the argument is
118    // the one the runtime's own declaration of the entry point has.
119    let word = Type::int(module.datalayout.pointer_bits);
120    // The descriptors are collected rather than added as they are found, because a function is
121    // borrowed out of the module while its checks are being rewritten. What the rewrite needs is
122    // the name of the descriptor it is about, and a name is the position in this list, so both ends
123    // agree without either holding the module.
124    let mut written: Vec<Descriptor> = Vec::new();
125    // The same reason the descriptors are collected: a plane write names a node of the module's
126    // metadata table and the module is not reachable while one of its functions is borrowed out of
127    // it. The table is a handful of nodes, so it is read once here rather than per instruction.
128    let numbers = plane::numbers(module, names);
129    for id in module.funcs() {
130        if module[id].is_declaration() {
131            continue;
132        }
133        calls(&mut module[id], names, word, &numbers, &mut written);
134    }
135    for (index, row) in written.iter().enumerate() {
136        emit(module, names, index, *row);
137    }
138    written.len()
139}
140
141/// Rewrites every check in one function, and takes the capabilities out afterwards.
142fn calls(
143    func: &mut Func,
144    names: &mut Interner,
145    word: Type,
146    numbers: &HashMap<Meta, u32>,
147    table: &mut Vec<Descriptor>,
148) {
149    let insts: Vec<Inst> =
150        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
151    let pairs = pairs(func, &insts);
152    let fused: HashSet<Inst> = pairs.values().copied().collect();
153    for &inst in &insts {
154        // The init check of a pair is lowered by its type check, which asks both questions in one
155        // call, so there is nothing left here for it to be.
156        if fused.contains(&inst) {
157            continue;
158        }
159        match func[inst].opcode {
160            Opcode::CheckBounds => bounds(func, names, word, table, inst),
161            Opcode::CheckLive => live(func, names, table, inst),
162            Opcode::CheckFree => freed(func, names, table, inst),
163            Opcode::CheckDeriv => deriv(func, names, word, table, inst),
164            Opcode::CheckType => {
165                typed(func, names, word, numbers, table, inst, pairs.get(&inst).copied());
166            }
167            Opcode::CheckInit => began(func, names, word, table, inst),
168            Opcode::CheckRace => raced(func, names, word, table, inst),
169            Opcode::CheckRestrictRead => promised(func, names, word, table, inst, false),
170            Opcode::CheckRestrictWrite => promised(func, names, word, table, inst, true),
171            Opcode::RestrictEnter => opened(func, names, inst),
172            Opcode::RestrictLeave => closed(func, names, inst),
173            Opcode::MetaType => judgement(func, names, word, numbers, inst),
174            Opcode::MetaTypeCopy => carriage(func, names, word, inst),
175            Opcode::MetaInit => written(func, names, word, inst),
176            Opcode::MetaInitCopy => carried(func, names, word, inst),
177            Opcode::CapCopy => relocated(func, names, word, inst),
178            Opcode::MetaEpoch => stamped(func, names, word, inst),
179            Opcode::MetaRelease => published(func, names, inst),
180            Opcode::MetaAcquire => taken(func, names, inst),
181            Opcode::MetaFenceRelease => published_everywhere(func, names, inst),
182            Opcode::MetaFenceAcquire => taken_everywhere(func, names, inst),
183            Opcode::CapExtent => extent(func, names, word, inst, "__rucc_extent"),
184            Opcode::CapExtentBack => extent(func, names, word, inst, "__rucc_extent_back"),
185            Opcode::SafeRegionBegin | Opcode::SafeRegionEnd => declared(func, inst),
186            _ => {}
187        }
188    }
189    // Every `cap_of` in the function was put there to feed a check, and no check reads one any
190    // more, so almost all of what this does is take them out again. The rest of it is putting the
191    // capabilities that are left somewhere the back end can keep them, which is [`crate::slot`].
192    crate::slot::frames(func, names, word);
193}
194
195/// `check_bounds` becomes `__rucc_check_bounds(pointer, size, align, capability, descriptor)`.
196///
197/// The size is the payload's for the check the front end wrote and the third operand's for the
198/// hoisted check of section 7.4, which is about a range the program worked out rather than about
199/// one access. The descriptor says zero bytes for that one, which is the field's own reading of a
200/// check that is not about an access of a known width, because the width is not known here either.
201///
202/// The alignment is the payload's too, and it is the whole of judgement J1's `addr mod align = 0`
203/// conjunct: the runtime tests it and nothing else here does. A hoisted check passes one, which
204/// says the range it is about assumes nothing, because the range is a span of bytes a loop will
205/// walk rather than one access and the accesses inside it carry their own.
206///
207/// The capability is the first operand, which this used to throw away the way [`live`] used to, and
208/// handing it over is box 6 of tamnd/rucc#1241. It lets the runtime permit the commonest access in a
209/// program with a subtraction and a compare over two words the caller has already got, where it used
210/// to want a region lookup and two plane loads, and it costs nothing to produce, because the
211/// lifetime check beside this one is loading the same capability for the version anyway. That is the
212/// sense in which the issue says this is where the version compare pays for itself. The runtime only
213/// ever permits on it and never refuses on it, and `rucc_safe_rt::check::bounds` is where that
214/// restraint is argued.
215///
216/// Fourth rather than first, which is the other order from the lifetime check. The three in front of
217/// it are the ones the access is about and they were here first, so leaving them alone keeps them in
218/// the registers the caller would have used, and the descriptor stays last the way every check here
219/// has it.
220fn bounds(
221    func: &mut Func,
222    names: &mut Interner,
223    word: Type,
224    table: &mut Vec<Descriptor>,
225    inst: Inst,
226) {
227    let args = &func[func[inst].args];
228    let (Some(&capability), Some(&pointer), computed) =
229        (args.first(), args.get(1), args.get(2).copied())
230    else {
231        return;
232    };
233    let Extra::Mem(mem) = func[inst].extra else { return };
234    let size = func[mem].size;
235
236    let row = Descriptor {
237        judgement: ACCESS,
238        class: 0,
239        // Saturating, so that a report about a structure copy larger than a descriptor can hold
240        // says sixty five thousand rather than whatever the low sixteen bits happened to be.
241        size: if computed.is_some() { 0 } else { u16::try_from(size).unwrap_or(u16::MAX) },
242    };
243    let desc = record(func, names, table, inst, row);
244    let bytes = match computed {
245        Some(value) => fitted(func, inst, value, word),
246        None => konst(func, inst, Imm::int(i128::from(size), word), word),
247    };
248    let claim = if computed.is_some() { 1 } else { i128::from(func[mem].align) };
249    let align = konst(func, inst, Imm::int(claim, word), word);
250    let params = &[Type::PTR, word, word, Type::PTR, Type::PTR];
251    let args = &[pointer, bytes, align, capability, desc];
252    call(func, names, inst, "__rucc_check_bounds", params, &[], args);
253}
254
255/// The same number in the width the runtime's own declaration asks for.
256///
257/// A pass that works out how many bytes a loop covers has no target to ask, so it writes the count
258/// in the width its arithmetic was in, and on a target whose `size_t` is narrower or wider than that
259/// the call would be handed the wrong type. Zero extension rather than sign, because the number is a
260/// count of bytes and a negative one is not a thing the caller can have meant.
261///
262/// `crate::slot` uses it for the same reason about a different pair of numbers: an offset and a
263/// length that narrow a capability are written in whatever width the front end's arithmetic was in,
264/// and the runtime declares both as `size_t`.
265pub(crate) fn fitted(func: &mut Func, inst: Inst, value: Value, word: Type) -> Value {
266    let ty = func[value].ty;
267    if ty == word {
268        return value;
269    }
270    let opcode = if ty.bits() > word.bits() { Opcode::Trunc } else { Opcode::ZExt };
271    let span = func.span(inst);
272    let args = func.push_values(&[value]);
273    let made = func.create_inst(InstData { args, ..InstData::new(opcode) }, &[word], span);
274    func.insert_before(made, inst);
275    func[made].results().next().expect("a cast created with one result has one")
276}
277
278/// Whether an instruction that reads a capability is still reading one after this pass has run.
279///
280/// Three checks of the six now: [`live`], [`freed`] and [`bounds`]. The other three still become a
281/// call that takes an address, so a capability whose only reader is one of them is dead the moment
282/// the rewrite happens and [`crate::slot`]'s prune takes it out. That is a statement about how far
283/// tamnd/rucc#1241 has got rather than about the design, and what is left of it is the type check,
284/// the initialization check and the race check, none of which asks anything a capability answers.
285///
286/// It is a predicate somebody outside can ask rather than something left implied by the arm it
287/// belongs to, and [`crate::origin::existing`] is the somebody, on behalf of [`crate::handover`].
288/// What that pass hands to a callee has to be a capability the caller is paying for anyway, and a
289/// producer about to be pruned is not one: handing it over is a reader, a reader keeps it alive, and
290/// what it keeps alive is a walk of the lifetime plane nobody was doing. On the SQLite amalgamation
291/// that is nine hundred walks and eight per cent of the text, bought for nothing.
292///
293/// The three that are not checks are here because they read a capability, come through this pass
294/// untouched, and are not producers themselves, so nothing else decides whether they count. A
295/// producer that reads one is a `cap_narrow`, and whether that keeps its operand alive depends on
296/// whether anything keeps the narrow alive, which is a fixpoint rather than an answer this can give.
297///
298/// A whitelist and not a blacklist, because the two ways of being wrong are not the same size.
299/// Leaving something out means a capability that could have travelled does not, which is a handover
300/// missed. Putting something in wrongly means a dead producer resurrected, which is the regression
301/// this exists to prevent.
302pub(crate) fn keeps(opcode: Opcode) -> bool {
303    matches!(
304        opcode,
305        Opcode::CheckBounds
306            | Opcode::CheckLive
307            | Opcode::CheckFree
308            | Opcode::CapStore
309            | Opcode::CapYield
310            | Opcode::CapPublish
311    )
312}
313
314/// `check_live` becomes `__rucc_check_live(capability, pointer, descriptor)`.
315///
316/// The first operand is the one this used to throw away, and handing it over is the whole of what
317/// turns the lock and key rule of `spec/safe-memory/08-temporal-safety.md` section 8.3 on in
318/// generated code. Without it the runtime asks whether anybody owns the address, which misses every
319/// access through a stale pointer to a block the allocator has since handed out again. With it the
320/// runtime compares the version the capability was taken at against the version the plane holds,
321/// and that is the access a busy program's use after free is.
322///
323/// It is a `*const Cap` by the time the back end sees it, because [`crate::slot`] gives a capability
324/// four words of frame and passes the address of them, which is what the runtime's own declaration
325/// of every entry point that takes one asks for.
326fn live(func: &mut Func, names: &mut Interner, table: &mut Vec<Descriptor>, inst: Inst) {
327    let [capability, pointer] = func[func[inst].args] else { return };
328    // No size. The check carries no payload, because whether anybody owns an address is a question
329    // about the address rather than about how many bytes are read through it.
330    let row = Descriptor { judgement: ACCESS, class: 0, size: 0 };
331    let desc = record(func, names, table, inst, row);
332    let params = &[Type::PTR; 3];
333    call(func, names, inst, "__rucc_check_live", params, &[], &[capability, pointer, desc]);
334}
335
336/// `check_free` becomes `__rucc_check_free(capability, pointer, descriptor)`.
337///
338/// The same three arguments [`live`] passes and in the same order, because the two checks ask the
339/// version question in the same words. What separates them is the descriptor: this one says J6, so a
340/// refusal reads as a free of something that was not allocated, or not by that allocator, rather
341/// than as an access the planes did not permit. That is the sentence the person whose program
342/// stopped wants, since the line it stopped at is a `free` and nothing was being read.
343///
344/// Two entry points rather than one with a judgement argument, for the reason the descriptor exists
345/// at all: which judgement a check decides is constant data the object file already carries, and
346/// passing it would be putting a number in a register on every free to say something that never
347/// changes. It also keeps the runtime's two answers apart, and they are not the same answer. The
348/// lifetime check refuses an address nobody owns and this one leaves that to the allocator, which
349/// has the header in front of it and more to say.
350fn freed(func: &mut Func, names: &mut Interner, table: &mut Vec<Descriptor>, inst: Inst) {
351    let [capability, pointer] = func[func[inst].args] else { return };
352    // No size, for the reason [`live`] has none. How many bytes are at the address is the header's
353    // business and the free is not an access.
354    let row = Descriptor { judgement: FREE, class: 0, size: 0 };
355    let desc = record(func, names, table, inst, row);
356    let params = &[Type::PTR; 3];
357    call(func, names, inst, "__rucc_check_free", params, &[], &[capability, pointer, desc]);
358}
359
360/// `check_deriv` becomes `__rucc_check_deriv(base, derived, stride, descriptor)`.
361///
362/// The stride goes through as a value rather than into the descriptor, because a walk over a
363/// variable length array steps by a width the program computes and a descriptor is constant data.
364fn deriv(
365    func: &mut Func,
366    names: &mut Interner,
367    word: Type,
368    table: &mut Vec<Descriptor>,
369    inst: Inst,
370) {
371    let [_capability, base, derived, stride] = func[func[inst].args] else { return };
372    let row = Descriptor { judgement: DERIVE, class: 0, size: 0 };
373    let desc = record(func, names, table, inst, row);
374    let params = &[Type::PTR, Type::PTR, word, Type::PTR];
375    call(func, names, inst, "__rucc_check_deriv", params, &[], &[base, derived, stride, desc]);
376}
377
378/// Which type checks have an init check beside them that belongs to the same read.
379///
380/// The pair is what tamnd/rucc#1617's fifth box asks about. `rucc_safety::access_checks` writes a
381/// type check and an init check in front of every read, they take the same address and the same
382/// width, they carry the same descriptor row, and they are the same function in the runtime up to
383/// which plane it ends at. So a read that needs both finds the region twice, and finding the region
384/// is the expensive half of either one.
385///
386/// What is not assumed is that the two are still a pair. `crate::discharge` takes one out without
387/// the other often enough that the second half of this file's work has to check rather than trust,
388/// and a type check fused with an init check belonging to some later read would be an init check
389/// asked earlier than the program asks it, which is a refusal of a correct program.
390fn pairs(func: &Func, insts: &[Inst]) -> HashMap<Inst, Inst> {
391    let mut found = HashMap::new();
392    for &inst in insts {
393        if func[inst].opcode != Opcode::CheckType {
394            continue;
395        }
396        if let Some(partner) = partner(func, inst) {
397            found.insert(inst, partner);
398        }
399    }
400    found
401}
402
403/// The init check that belongs to the same read as this type check, if there is one.
404///
405/// Same address, same width, same block, and nothing between the two that writes memory or ends
406/// the block. Another check of either kind in between ends the search rather than being walked
407/// past, because a second one is a second read and the pair is one read's.
408fn partner(func: &Func, check: Inst) -> Option<Inst> {
409    let [_capability, pointer] = func[func[check].args] else { return None };
410    let Extra::Mem(mem) = func[check].extra else { return None };
411    let size = func[mem].size;
412    let block = func.block_of(check)?;
413    let mut after = false;
414    for inst in func.insts(block) {
415        if inst == check {
416            after = true;
417            continue;
418        }
419        if !after {
420            continue;
421        }
422        match func[inst].opcode {
423            Opcode::CheckInit => {
424                let [_capability, other] = func[func[inst].args] else { return None };
425                let Extra::Mem(at) = func[inst].extra else { return None };
426                return (other == pointer && func[at].size == size).then_some(inst);
427            }
428            Opcode::CheckType => return None,
429            opcode if opcode.writes_memory() || opcode.is_terminator() => return None,
430            _ => {}
431        }
432    }
433    None
434}
435
436/// `check_type` becomes `__rucc_check_type(pointer, size, type, descriptor)`.
437///
438/// The one check with a type number on it, and the number is the same one [`judgement`] passes for
439/// the same reason: what the store wrote and what the read asks about have to be written in one
440/// vocabulary or they cannot be compared.
441///
442/// The descriptor says J1 rather than a judgement of its own. The type plane is one of the planes
443/// document 04 section 4.4's first judgement names, so a read the plane refused is an access the
444/// planes did not permit, which is the sentence the reporter already prints.
445///
446/// With an init check beside it that [`partner`] recognised as the same read's, it becomes
447/// `__rucc_check_typed_init` instead, with the same four arguments, and that check is taken out.
448/// The two rows are identical, so the one descriptor recorded here serves both, and the runtime
449/// asks the type plane first, which is the order the two calls ran in.
450///
451/// A third operand is how many bytes to ask about, which is [`bounds`]'s arrangement and is what
452/// `rucc_opt::hoist` writes when one check stands for a loop's worth. The size in the row goes to
453/// zero there for the reason given in [`bounds`]: what a report would name is the width of an access
454/// the program wrote, and a check covering a whole walk is not one of those.
455fn typed(
456    func: &mut Func,
457    names: &mut Interner,
458    word: Type,
459    numbers: &HashMap<Meta, u32>,
460    table: &mut Vec<Descriptor>,
461    inst: Inst,
462    partner: Option<Inst>,
463) {
464    let args = &func[func[inst].args];
465    let (Some(&_capability), Some(&pointer), computed) =
466        (args.first(), args.get(1), args.get(2).copied())
467    else {
468        return;
469    };
470    let Extra::Mem(mem) = func[inst].extra else { return };
471    let size = func[mem].size;
472    let Some(node) = func[mem].tbaa else { return };
473    let Some(&number) = numbers.get(&node) else { return };
474
475    let row = Descriptor {
476        judgement: ACCESS,
477        class: 0,
478        // Saturating, for the reason [`bounds`] gives about a report of a width that does not fit.
479        size: if computed.is_some() { 0 } else { u16::try_from(size).unwrap_or(u16::MAX) },
480    };
481    let desc = record(func, names, table, inst, row);
482    let bytes = match computed {
483        Some(value) => fitted(func, inst, value, word),
484        None => konst(func, inst, Imm::int(i128::from(size), word), word),
485    };
486    let small = Type::int(32);
487    let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
488    let params = &[Type::PTR, word, small, Type::PTR];
489    let routine = match partner {
490        Some(init) => {
491            func.remove_inst(init);
492            "__rucc_check_typed_init"
493        }
494        None => "__rucc_check_type",
495    };
496    call(func, names, inst, routine, params, &[], &[pointer, bytes, ty, desc]);
497}
498
499/// `check_init` becomes `__rucc_check_init(pointer, size, descriptor)`.
500///
501/// No type number, because the plane it asks holds no types: one bit per byte, and the bit says
502/// whether anything was ever stored there. So the call is the shape [`bounds`] has rather than the
503/// shape [`typed`] has, and the size is the access's own width for the reason given there.
504///
505/// The descriptor says J1, as the type plane's does. The init plane is one of the planes document
506/// 04 section 4.4's first judgement names, so a read the plane refused is an access the planes did
507/// not permit, and that is already the sentence the reporter prints.
508///
509/// A third operand is how many bytes to ask about, as on [`typed`] and [`bounds`].
510fn began(
511    func: &mut Func,
512    names: &mut Interner,
513    word: Type,
514    table: &mut Vec<Descriptor>,
515    inst: Inst,
516) {
517    let args = &func[func[inst].args];
518    let (Some(&_capability), Some(&pointer), computed) =
519        (args.first(), args.get(1), args.get(2).copied())
520    else {
521        return;
522    };
523    let Extra::Mem(mem) = func[inst].extra else { return };
524    let size = func[mem].size;
525
526    let row = Descriptor {
527        judgement: ACCESS,
528        class: 0,
529        // Saturating, for the reason [`bounds`] gives about a report of a width that does not fit.
530        size: if computed.is_some() { 0 } else { u16::try_from(size).unwrap_or(u16::MAX) },
531    };
532    let desc = record(func, names, table, inst, row);
533    let bytes = match computed {
534        Some(value) => fitted(func, inst, value, word),
535        None => konst(func, inst, Imm::int(i128::from(size), word), word),
536    };
537    let params = &[Type::PTR, word, Type::PTR];
538    call(func, names, inst, "__rucc_check_init", params, &[], &[pointer, bytes, desc]);
539}
540
541/// `check_race` becomes `__rucc_check_race(pointer, size, descriptor)`.
542///
543/// The same shape [`began`] has, because the plane it asks holds one stamp per granule rather than
544/// anything about a type, so what the runtime needs is a range and nothing else. The size is the
545/// access's own width, which covers the case of an access that straddles two granules: either of
546/// them carrying a stranger's stamp is a race this access is in.
547///
548/// The descriptor says J9 rather than J1. The reporter reads the number out of the row it was
549/// handed, so this is the whole of what makes a race read as a race, and the line naming both
550/// threads is added by the runtime from the two stamps rather than from anything here.
551fn raced(
552    func: &mut Func,
553    names: &mut Interner,
554    word: Type,
555    table: &mut Vec<Descriptor>,
556    inst: Inst,
557) {
558    let [_capability, pointer] = func[func[inst].args] else { return };
559    let Extra::Mem(mem) = func[inst].extra else { return };
560    let size = func[mem].size;
561
562    let row = Descriptor {
563        judgement: RACE,
564        class: 0,
565        // Saturating, for the reason [`bounds`] gives about a report of a width that does not fit.
566        size: u16::try_from(size).unwrap_or(u16::MAX),
567    };
568    let desc = record(func, names, table, inst, row);
569    let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
570    let params = &[Type::PTR, word, Type::PTR];
571    call(func, names, inst, "__rucc_check_race", params, &[], &[pointer, bytes, desc]);
572}
573
574/// The two numbers in the one word the runtime reads them out of.
575///
576/// `rucc_safe_rt::restrict::tag` is the other half of this and the two have to agree, so the
577/// packing is written down in both places and tested in both. The clique is the high half and the
578/// base is the low one, which puts the number that identifies the scope where a reader of a hex
579/// dump will see it first.
580fn tag(clique: u16, base: u16) -> u32 {
581    (u32::from(clique) << 16) | u32::from(base)
582}
583
584/// `check_restrict_read` and `check_restrict_write` become
585/// `__rucc_check_restrict(pointer, size, tag, write, descriptor)`.
586///
587/// One function for both, because which of them it was is the fourth argument and nothing else.
588/// The runtime needs to know whether the access wrote because two reads of one byte through two
589/// `restrict` pointers are not a violation of anything: the contract is about modification, so the
590/// pair is refused only when at least one half of it wrote.
591///
592/// The scope is not an argument. The runtime finds it from the clique in the tag, walking the
593/// blocks this thread is inside until it reaches the innermost one with that clique, which is what
594/// makes a recursive function's second activation ask about its own promise and not its caller's.
595fn promised(
596    func: &mut Func,
597    names: &mut Interner,
598    word: Type,
599    table: &mut Vec<Descriptor>,
600    inst: Inst,
601    write: bool,
602) {
603    let [pointer] = func[func[inst].args] else { return };
604    let Extra::Mem(mem) = func[inst].extra else { return };
605    let size = func[mem].size;
606    let named = func[mem].restrict;
607
608    let row = Descriptor {
609        judgement: RESTRICT,
610        class: 0,
611        // Saturating, for the reason [`bounds`] gives about a report of a width that does not fit.
612        size: u16::try_from(size).unwrap_or(u16::MAX),
613    };
614    let desc = record(func, names, table, inst, row);
615    let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
616    let small = Type::int(32);
617    let which =
618        konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
619    let wrote = konst(func, inst, Imm::int(i128::from(u8::from(write)), small), small);
620    let params = &[Type::PTR, word, small, small, Type::PTR];
621    let args = &[pointer, bytes, which, wrote, desc];
622    call(func, names, inst, "__rucc_check_restrict", params, &[], args);
623}
624
625/// `restrict_enter` becomes `__rucc_restrict_enter(scope, tag)`.
626///
627/// No descriptor, for the reason [`judgement`] gives about a plane write: opening a block refuses
628/// nothing, so there is no failure to describe. The base half of the tag is how many pointers the
629/// block declares rather than which of them this is, since the runtime has to know how much of the
630/// slot to clear before the block starts recording into it.
631fn opened(func: &mut Func, names: &mut Interner, inst: Inst) {
632    let [scope] = func[func[inst].args] else { return };
633    let Extra::Mem(mem) = func[inst].extra else { return };
634    let named = func[mem].restrict;
635    let small = Type::int(32);
636    let which =
637        konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
638    call(func, names, inst, "__rucc_restrict_enter", &[Type::PTR, small], &[], &[scope, which]);
639}
640
641/// `restrict_leave` becomes `__rucc_restrict_leave(scope)`.
642///
643/// The slot alone, and no numbers, because closing a block is a matter of putting back whatever it
644/// was inside and the slot already says what that was.
645fn closed(func: &mut Func, names: &mut Interner, inst: Inst) {
646    let [scope] = func[func[inst].args] else { return };
647    call(func, names, inst, "__rucc_restrict_leave", &[Type::PTR], &[], &[scope]);
648}
649
650/// `safe_region_begin` and `safe_region_end` become nothing at all.
651///
652/// The only pair here that lowers to no call, and the reason is that a declared region is a fact
653/// about the build rather than a thing the program does. Everything between the two markers is code
654/// the monitor was told not to judge, so there is no check to emit, no plane to write and no state
655/// for the runtime to keep: the region has already had its effect by the time this runs, which was
656/// to keep the checks from being written in the first place.
657///
658/// What a region does cost is the row `spec/safe-memory/10-boundaries.md` section 10.2 asks for,
659/// and [`crate::summary`] has already taken it. That pass runs on the front end's IR, before the
660/// back end and so before this, which is the order that makes the count possible at all: after this
661/// the object file has no trace that a region was ever declared.
662fn declared(func: &mut Func, inst: Inst) {
663    func.remove_inst(inst);
664}
665
666/// `meta_type` becomes `__rucc_meta_type(pointer, size, type)`.
667///
668/// No descriptor, and it is the only thing here with a payload that has none. A plane write refuses
669/// nothing and reports nothing: it records the fact that the check of the same name will later ask
670/// about, so there is no failure for a descriptor to describe.
671///
672/// The type is a number rather than a node, and `crate::plane` is where the number comes from and
673/// why it is a hash of the type's name. It travels in thirty two bits because the plane holds
674/// thirty two bits per byte of program memory, which is document 05 section 5.2.3's measurement and
675/// not a choice made here.
676fn judgement(
677    func: &mut Func,
678    names: &mut Interner,
679    word: Type,
680    numbers: &HashMap<Meta, u32>,
681    inst: Inst,
682) {
683    let [pointer, length] = func[func[inst].args] else { return };
684    let Extra::Node(node) = func[inst].extra else { return };
685    let Some(&number) = numbers.get(&node) else { return };
686    let bytes = fitted(func, inst, length, word);
687    let small = Type::int(32);
688    let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
689    let params = &[Type::PTR, word, small];
690    call(func, names, inst, "__rucc_meta_type", params, &[], &[pointer, bytes, ty]);
691}
692
693/// `meta_type_copy` becomes `__rucc_meta_type_copy(destination, source, length)`.
694///
695/// No descriptor and no type number, for the two reasons [`judgement`] gives: a plane write refuses
696/// nothing, and what the copied bytes are is not something the compiler knows. The runtime reads the
697/// entries over the source and writes them over the destination, so the type travels without
698/// anybody here having to name it.
699fn carriage(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
700    let [to, from, length] = func[func[inst].args] else { return };
701    let bytes = fitted(func, inst, length, word);
702    let params = &[Type::PTR, Type::PTR, word];
703    call(func, names, inst, "__rucc_meta_type_copy", params, &[], &[to, from, bytes]);
704}
705
706/// `meta_init` becomes `__rucc_meta_init(pointer, size)`.
707///
708/// No descriptor, for the reason [`judgement`] has none, and no type either. The init plane holds
709/// one bit per byte and the bit says whether anything was ever stored there, so a range is the whole
710/// of what a store has to say about it and there is nothing else to pass.
711fn written(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
712    let [pointer, length] = func[func[inst].args] else { return };
713    let bytes = fitted(func, inst, length, word);
714    let params = &[Type::PTR, word];
715    call(func, names, inst, "__rucc_meta_init", params, &[], &[pointer, bytes]);
716}
717
718/// `meta_init_copy` becomes `__rucc_meta_init_copy(destination, source, length)`.
719///
720/// The same shape as [`carriage`] and for the same reason: a copy writes no values of its own, so
721/// whether a destination byte holds anything is whether the byte it came from did, and the plane
722/// over the source is the only place that is written down.
723fn carried(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
724    let [to, from, length] = func[func[inst].args] else { return };
725    let bytes = fitted(func, inst, length, word);
726    let params = &[Type::PTR, Type::PTR, word];
727    call(func, names, inst, "__rucc_meta_init_copy", params, &[], &[to, from, bytes]);
728}
729
730/// `cap_copy` becomes `__rucc_cap_copy(destination, source, length)`.
731///
732/// The same shape as [`carried`] again, and the same argument for why it names nothing else: the
733/// capability of a pointer in memory is in the slot beside it, so the slots over the source are
734/// where the answer already is and the runtime moves them across. It is the one of the three that
735/// is not a plane write, and the call it becomes is the one `__rucc_wrap_memcpy` has always made.
736fn relocated(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
737    let [to, from, length] = func[func[inst].args] else { return };
738    let bytes = fitted(func, inst, length, word);
739    let params = &[Type::PTR, Type::PTR, word];
740    call(func, names, inst, "__rucc_cap_copy", params, &[], &[to, from, bytes]);
741}
742
743/// `meta_epoch` becomes `__rucc_meta_epoch(pointer, length)`.
744///
745/// The same shape as [`written`], and carrying no thread and no count for the reason the opcode
746/// gives: which thread is running and how far it has counted are facts about the moment the program
747/// gets here, so the runtime reads them and nothing this pass could pass in would be either.
748fn stamped(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
749    let [pointer, length] = func[func[inst].args] else { return };
750    let bytes = fitted(func, inst, length, word);
751    let params = &[Type::PTR, word];
752    call(func, names, inst, "__rucc_meta_epoch", params, &[], &[pointer, bytes]);
753}
754
755/// `meta_release` becomes `__rucc_meta_release(object)`.
756///
757/// One operand and no length, because an edge is about everything the thread did rather than about
758/// a range of bytes, and no descriptor, because publishing a clock decides nothing and so has
759/// nothing to report. The runtime entry point is the same `sync::released` the `pthread` wrappers
760/// call, keyed on the address the same way, so an ordering established through an atomic and one
761/// established through a mutex are the same edge to everything that reads them.
762fn published(func: &mut Func, names: &mut Interner, inst: Inst) {
763    let [object] = func[func[inst].args] else { return };
764    call(func, names, inst, "__rucc_meta_release", &[Type::PTR], &[], &[object]);
765}
766
767/// `meta_acquire` becomes `__rucc_meta_acquire(object)`.
768///
769/// The other end of [`published`], and the same shape.
770fn taken(func: &mut Func, names: &mut Interner, inst: Inst) {
771    let [object] = func[func[inst].args] else { return };
772    call(func, names, inst, "__rucc_meta_acquire", &[Type::PTR], &[], &[object]);
773}
774
775/// `meta_fence_release` becomes `__rucc_meta_fence_release()`.
776///
777/// No operands at all, which is the whole difference between a fence and an atomic here. A fence
778/// orders against every other thread rather than against one object, so there is no address to pass
779/// and the runtime keeps one cell for every fence in the program instead of a table keyed by one.
780fn published_everywhere(func: &mut Func, names: &mut Interner, inst: Inst) {
781    call(func, names, inst, "__rucc_meta_fence_release", &[], &[], &[]);
782}
783
784/// `meta_fence_acquire` becomes `__rucc_meta_fence_acquire()`.
785///
786/// The other end of [`published_everywhere`], and the same shape.
787fn taken_everywhere(func: &mut Func, names: &mut Interner, inst: Inst) {
788    call(func, names, inst, "__rucc_meta_fence_acquire", &[], &[], &[]);
789}
790
791/// `cap_extent` becomes `__rucc_extent(pointer, want)`, and `cap_extent_back` the backward one.
792///
793/// No descriptor, and these two are the only ones of these that have none. The other four are
794/// judgements and a judgement that refuses has to say what it refused. These decide nothing: they
795/// are the question section 7.4 asks before a loop so that the loop can be split, once for a walk
796/// that goes up and once for a walk that goes down, the answer is a number, and there is no failure
797/// to describe.
798///
799/// One function for both because the two differ in the name they call and in nothing else. The
800/// operands are the same three, the result is the same count, and the width the count comes back in
801/// is handled the same way.
802fn extent(func: &mut Func, names: &mut Interner, word: Type, inst: Inst, called: &str) {
803    let [_capability, address, want] = func[func[inst].args] else { return };
804    let asked = fitted(func, inst, want, word);
805    let result = func[inst].results().next().expect("an extent query produces one value");
806    let ty = func[result].ty;
807    let params = &[Type::PTR, word];
808    if ty == word {
809        call(func, names, inst, called, params, &[word], &[address, asked]);
810        return;
811    }
812    // The count came out in a width that is not the target's, for the reason [`fitted`] gives about
813    // the operand going the other way. The call is made beside the instruction in the width the
814    // runtime declares and the instruction itself becomes the conversion back, so that everything
815    // reading its result still reads a value of the type it had.
816    let made = calling(func, names, called, params, &[word], &[address, asked]);
817    let holder = func.create_inst(made, &[word], func.span(inst));
818    func.insert_before(holder, inst);
819    let got = func[holder].results().next().expect("a call returning one value produces one");
820    let opcode = if word.bits() > ty.bits() { Opcode::Trunc } else { Opcode::ZExt };
821    let args = func.push_values(&[got]);
822    func[inst] = InstData { args, ..InstData::new(opcode) };
823}
824
825/// Writes a descriptor down and gives back the address the call passes.
826///
827/// The `global_addr` goes in front of the check rather than at the top of the function, because the
828/// back end turns it into one `lea` off the instruction pointer and putting it beside its use is
829/// what keeps the value from being live across everything in between.
830fn record(
831    func: &mut Func,
832    names: &mut Interner,
833    table: &mut Vec<Descriptor>,
834    inst: Inst,
835    row: Descriptor,
836) -> Value {
837    let name = names.intern(&label(table.len()));
838    table.push(row);
839    let span = func.span(inst);
840    let data = InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) };
841    let made = func.create_inst(data, &[Type::PTR], span);
842    func.insert_before(made, inst);
843    func[made].results().next().expect("an address created with one result has one")
844}
845
846/// What the descriptor in position `index` is called.
847fn label(index: usize) -> String {
848    format!("{DESCRIPTOR}_{index}")
849}
850
851/// Puts an integer constant in front of `inst` and gives back what it produced.
852fn konst(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
853    let span = func.span(inst);
854    let extra = Extra::Imm(func.add_imm(imm));
855    let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
856    func.insert_before(made, inst);
857    func[made].results().next().expect("a constant created with one result has one")
858}
859
860/// Turns `inst` into a call of `routine` with those arguments, in place.
861///
862/// In place rather than as a new instruction beside it, because the check is already where it has
863/// to be: in front of the access for the two access checks and behind the arithmetic for the
864/// derivation one. Moving it would be a chance to get that wrong.
865pub(crate) fn call(
866    func: &mut Func,
867    names: &mut Interner,
868    inst: Inst,
869    routine: &str,
870    params: &[Type],
871    returns: &[Type],
872    args: &[Value],
873) {
874    let made = calling(func, names, routine, params, returns, args);
875    let data = &mut func[inst];
876    data.opcode = made.opcode;
877    data.args = made.args;
878    data.extra = made.extra;
879    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
880}
881
882/// A call to `routine` with those arguments, not yet anywhere.
883///
884/// Separate from [`call`] because two rewrites need the call beside the instruction rather than in
885/// place of it, and building the signature and the callee is the part all of them have in common.
886/// The extent query is one, when the count comes back in a width that is not the instruction's, and
887/// `crate::slot`'s allocation capability is the other, because there the instruction gives back a
888/// value and the call does not.
889pub(crate) fn calling(
890    func: &mut Func,
891    names: &mut Interner,
892    routine: &str,
893    params: &[Type],
894    returns: &[Type],
895    args: &[Value],
896) -> InstData {
897    let sig = func.add_signature(Signature::new().with_params(params).with_returns(returns));
898    let callee = names.intern(routine);
899    // Nothing is passed past the last named parameter, so there is nothing for the ABI to say
900    // about the arguments the signature does not name.
901    let varargs = func.push_abis(&[]);
902    let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
903    let args = func.push_values(args);
904    InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) }
905}
906
907/// Adds one descriptor to the module as a variable in the shared section.
908///
909/// Internal, so the linker never has to resolve the name and two objects in a link do not collide
910/// over it. Constant, because nothing writes a descriptor after the compiler has. Eight byte
911/// aligned and sixteen bytes long, because the runtime reads it as a `#[repr(C)]` structure with a
912/// `u64` in it, and because that is what makes the section as a whole a packed array of them.
913fn emit(module: &mut Module, names: &mut Interner, index: usize, row: Descriptor) {
914    let byte = Type::int(8);
915    let half = Type::int(16);
916    let judgement = module.add_imm(Imm::int(i128::from(row.judgement), byte));
917    let class = module.add_imm(Imm::int(i128::from(row.class), byte));
918    let size = module.add_imm(Imm::int(i128::from(row.size), half));
919    let image = [
920        Datum::Scalar { ty: byte, value: judgement },
921        Datum::Scalar { ty: byte, value: class },
922        Datum::Scalar { ty: half, value: size },
923        // Four bytes the C layout puts in front of the `u64`, and then the eight of the program
924        // counter, which nothing fills in yet. Both are zero and both are written out rather than
925        // left off, because the descriptor after this one has to start sixteen bytes along.
926        Datum::Zero(4),
927        Datum::Zero(8),
928    ];
929    let init = module.push_data(&image);
930    let mut global = Global::new(names.intern(&label(index)), WIDTH, 8);
931    global.linkage = Linkage::Internal;
932    global.constant = true;
933    global.section = Some(names.intern(SECTION));
934    global.init = Some(init);
935    module.add_global(global);
936}
937
938#[cfg(test)]
939mod tests {
940    use rucc_ir::{
941        Builder, MemInfo, MemOrder, MetaNode, Restrict, RmwOp, TbaaNode, print_func, verify_func,
942    };
943    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
944
945    use super::*;
946    use crate::{Plane, Promise, Races, Subobject, insert};
947
948    fn target() -> TargetInfo {
949        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
950    }
951
952    /// A module holding one function that loads through its parameter, with checks already in.
953    fn checked(names: &mut Interner) -> Module {
954        let i32_ = Type::int(32);
955        let mut func = Func::new(
956            names.intern("read"),
957            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
958        );
959        let entry = func.create_block();
960        let p = func.append_param(entry, Type::PTR);
961
962        let info = MemInfo {
963            size: 4,
964            align: 4,
965            order: MemOrder::NotAtomic,
966            tbaa: None,
967            owns: 0,
968            restrict: Restrict::NONE,
969        };
970        let mut b = Builder::new(&mut func, entry);
971        let args = b.func().push_values(&[p]);
972        let extra = Extra::Mem(b.func().add_mem(info));
973        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
974        b.ret(&[loaded]);
975
976        insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off, Races::Off);
977        let mut module = Module::new(names.intern("read.c"), &target());
978        module.add_func(func);
979        module
980    }
981
982    /// The same function [`checked`] builds, over an access that may assume nothing about where
983    /// it starts, which is what a member of a packed record is.
984    fn unaligned(names: &mut Interner) -> Module {
985        let i32_ = Type::int(32);
986        let mut func = Func::new(
987            names.intern("read"),
988            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
989        );
990        let entry = func.create_block();
991        let p = func.append_param(entry, Type::PTR);
992
993        let info = MemInfo {
994            size: 4,
995            align: 1,
996            order: MemOrder::NotAtomic,
997            tbaa: None,
998            owns: 0,
999            restrict: Restrict::NONE,
1000        };
1001        let mut b = Builder::new(&mut func, entry);
1002        let args = b.func().push_values(&[p]);
1003        let extra = Extra::Mem(b.func().add_mem(info));
1004        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
1005        b.ret(&[loaded]);
1006
1007        insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off, Races::Off);
1008        let mut module = Module::new(names.intern("read.c"), &target());
1009        module.add_func(func);
1010        module
1011    }
1012
1013    /// A plane for a function that stores nothing, and the numbering that goes with it.
1014    ///
1015    /// Every function in these tests reads or derives and none of them stores, so there is nothing
1016    /// to record and the entries are never named. What the two are for is that [`crate::insert`]
1017    /// and [`calls`] take them whether or not the function has a store in it.
1018    fn planeless(names: &mut Interner) -> (Plane, HashMap<Meta, u32>) {
1019        let mut module = Module::new(names.intern("planeless.c"), &target());
1020        let plane = Plane::build(&mut module);
1021        let numbers = plane::numbers(&module, names);
1022        (plane, numbers)
1023    }
1024
1025    /// A module with one function that copies a fixed number of bytes, with the plane write in.
1026    fn copied(names: &mut Interner) -> Module {
1027        let mut func =
1028            Func::new(names.intern("move"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
1029        let entry = func.create_block();
1030        let to = func.append_param(entry, Type::PTR);
1031        let from = func.append_param(entry, Type::PTR);
1032
1033        let info = MemInfo {
1034            size: 24,
1035            align: 8,
1036            order: MemOrder::NotAtomic,
1037            tbaa: None,
1038            owns: 0,
1039            restrict: Restrict::NONE,
1040        };
1041        let mut b = Builder::new(&mut func, entry);
1042        let args = b.func().push_values(&[to, from]);
1043        let extra = Extra::Mem(b.func().add_mem(info));
1044        b.inst(InstData { args, extra, ..InstData::new(Opcode::Memcpy) }, &[]);
1045        b.ret(&[]);
1046
1047        insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off, Races::Off);
1048        let mut module = Module::new(names.intern("move.c"), &target());
1049        module.add_func(func);
1050        module
1051    }
1052
1053    /// A module with one function that stores through its parameter, with the plane writes in.
1054    ///
1055    /// The plane is this module's rather than [`planeless`]'s, because the store records into it
1056    /// and a judgement naming an entry another module holds is a judgement [`judgement`] leaves
1057    /// alone.
1058    fn stored(names: &mut Interner) -> Module {
1059        let mut module = Module::new(names.intern("write.c"), &target());
1060        let plane = Plane::build(&mut module);
1061
1062        let i64_ = Type::int(64);
1063        let mut func =
1064            Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
1065        let entry = func.create_block();
1066        let p = func.append_param(entry, Type::PTR);
1067        let v = func.append_param(entry, i64_);
1068
1069        let info = MemInfo {
1070            size: 8,
1071            align: 8,
1072            order: MemOrder::NotAtomic,
1073            tbaa: None,
1074            owns: 0,
1075            restrict: Restrict::NONE,
1076        };
1077        let mut b = Builder::new(&mut func, entry);
1078        let args = b.func().push_values(&[v, p]);
1079        let extra = Extra::Mem(b.func().add_mem(info));
1080        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1081        b.ret(&[]);
1082
1083        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
1084        module.add_func(func);
1085        module
1086    }
1087
1088    /// A module with one function that stores a pointer through its parameter, checks in.
1089    ///
1090    /// `-fsafety-races=metadata`, so the store carries the epoch plane's write as well as the other
1091    /// two. The value stored is a pointer because that is the only kind of store the epoch plane
1092    /// takes, which [`crate::stamped`] argues.
1093    fn racing(names: &mut Interner) -> Module {
1094        let mut module = Module::new(names.intern("stamp.c"), &target());
1095        let plane = Plane::build(&mut module);
1096
1097        let mut func =
1098            Func::new(names.intern("stamp"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
1099        let entry = func.create_block();
1100        let p = func.append_param(entry, Type::PTR);
1101        let q = func.append_param(entry, Type::PTR);
1102
1103        let info = MemInfo {
1104            size: 8,
1105            align: 8,
1106            order: MemOrder::NotAtomic,
1107            tbaa: None,
1108            owns: 0,
1109            restrict: Restrict::NONE,
1110        };
1111        let mut b = Builder::new(&mut func, entry);
1112        let args = b.func().push_values(&[q, p]);
1113        let extra = Extra::Mem(b.func().add_mem(info));
1114        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1115        b.ret(&[]);
1116
1117        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
1118        module.add_func(func);
1119        module
1120    }
1121
1122    /// A module with one function holding a `seq_cst` atomic store, edges in.
1123    ///
1124    /// Sequentially consistent because it publishes and takes both, so one atomic covers the two
1125    /// markers and the order they came out in is readable off one printed function.
1126    fn ordering(names: &mut Interner) -> Module {
1127        let mut module = Module::new(names.intern("edge.c"), &target());
1128        let plane = Plane::build(&mut module);
1129
1130        let i64_ = Type::int(64);
1131        let mut func =
1132            Func::new(names.intern("publish"), Signature::new().with_params(&[Type::PTR, i64_]));
1133        let entry = func.create_block();
1134        let p = func.append_param(entry, Type::PTR);
1135        let v = func.append_param(entry, i64_);
1136
1137        let info = MemInfo {
1138            size: 8,
1139            align: 8,
1140            order: MemOrder::SeqCst,
1141            tbaa: None,
1142            owns: 0,
1143            restrict: Restrict::NONE,
1144        };
1145        let mut b = Builder::new(&mut func, entry);
1146        let at = b.func().add_mem(info);
1147        let args = b.func().push_values(&[p, v]);
1148        let extra = Extra::Rmw(RmwOp::Add, at);
1149        b.inst(InstData { args, extra, ..InstData::new(Opcode::AtomicRmw) }, &[i64_]);
1150        b.ret(&[]);
1151
1152        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
1153        module.add_func(func);
1154        module
1155    }
1156
1157    #[test]
1158    fn the_two_halves_of_an_edge_become_the_calls_the_interposed_locks_already_make() {
1159        // One operand each, no length and no descriptor. An edge is not about a range of bytes, it
1160        // is about everything the thread did either side of it, and publishing a clock decides
1161        // nothing so there is no row to point at. The runtime entry points are the same two the
1162        // `pthread` wrappers call, keyed on an address the same way, so an ordering established
1163        // through an atomic and one established through a mutex are one table and one clock.
1164        let mut names = Interner::new();
1165        let mut module = ordering(&mut names);
1166        lower(&mut module, &mut names);
1167
1168        let id = module.funcs().next().expect("the module has one function");
1169        let printed = print_func(&module, &module[id], &names);
1170        assert!(printed.contains("call @__rucc_meta_release(%0) : (ptr)\n"), "{printed}");
1171        assert!(printed.contains("call @__rucc_meta_acquire(%0) : (ptr)\n"), "{printed}");
1172
1173        // In front of the atomic and behind it, which is the order the ordering itself is in.
1174        let published = printed.find("__rucc_meta_release").expect("the publishing half lowered");
1175        let changed = printed.find("atomic_rmw").expect("the atomic is still there");
1176        let took = printed.find("__rucc_meta_acquire").expect("and the taking half lowered");
1177        assert!(published < changed && changed < took, "{printed}");
1178
1179        if let Err(errors) = verify_func(&module, &module[id], &names) {
1180            panic!("that was expected to be believed: {errors:#?}");
1181        }
1182    }
1183
1184    /// A module with one function holding a `seq_cst` fence, edges in.
1185    fn barrier(names: &mut Interner) -> Module {
1186        let mut module = Module::new(names.intern("fence.c"), &target());
1187        let plane = Plane::build(&mut module);
1188
1189        let mut func = Func::new(names.intern("barrier"), Signature::new());
1190        let entry = func.create_block();
1191        let mut b = Builder::new(&mut func, entry);
1192        let extra = Extra::Order(MemOrder::SeqCst);
1193        b.inst(InstData { extra, ..InstData::new(Opcode::Fence) }, &[]);
1194        b.ret(&[]);
1195
1196        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
1197        module.add_func(func);
1198        module
1199    }
1200
1201    #[test]
1202    fn the_edge_a_fence_carries_becomes_a_call_that_takes_nothing_at_all() {
1203        // No operand, which is the whole difference. A fence orders against every other thread
1204        // rather than against one object, so there is no address to hand the runtime and it keeps
1205        // one clock for every fence in the program instead of a table keyed by one.
1206        let mut names = Interner::new();
1207        let mut module = barrier(&mut names);
1208        lower(&mut module, &mut names);
1209
1210        let id = module.funcs().next().expect("the module has one function");
1211        let printed = print_func(&module, &module[id], &names);
1212        assert!(printed.contains("call @__rucc_meta_fence_release() : ()\n"), "{printed}");
1213        assert!(printed.contains("call @__rucc_meta_fence_acquire() : ()\n"), "{printed}");
1214
1215        let published = printed.find("fence_release").expect("the publishing half lowered");
1216        let barrier = printed.find("    fence ").expect("the fence is still there");
1217        let took = printed.find("fence_acquire").expect("and the taking half lowered");
1218        assert!(published < barrier && barrier < took, "{printed}");
1219
1220        if let Err(errors) = verify_func(&module, &module[id], &names) {
1221            panic!("that was expected to be believed: {errors:#?}");
1222        }
1223    }
1224
1225    /// A module with one function that reads through its parameter as an `int`, checks in.
1226    ///
1227    /// The aliasing node is built by hand rather than by the front end, since this crate cannot
1228    /// depend on the one that builds the tree. What matters is the shape: a root and one type under
1229    /// it, which is what `rucc_lower::aliasing` produces for a translation unit that reads an `int`.
1230    fn asking_the_plane(names: &mut Interner) -> Module {
1231        let mut module = Module::new(names.intern("read.c"), &target());
1232        let root = names.intern("char");
1233        let root =
1234            module.add_meta(MetaNode::Tbaa(TbaaNode { name: root, parent: None, offset: 0 }));
1235        let int = names.intern("int");
1236        let int =
1237            module.add_meta(MetaNode::Tbaa(TbaaNode { name: int, parent: Some(root), offset: 0 }));
1238        let plane = Plane::build(&mut module);
1239
1240        let i32_ = Type::int(32);
1241        let mut func = Func::new(
1242            names.intern("read"),
1243            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
1244        );
1245        let entry = func.create_block();
1246        let p = func.append_param(entry, Type::PTR);
1247        let info = MemInfo {
1248            size: 4,
1249            align: 4,
1250            order: MemOrder::NotAtomic,
1251            tbaa: Some(int),
1252            owns: 0,
1253            restrict: Restrict::NONE,
1254        };
1255        let mut b = Builder::new(&mut func, entry);
1256        let args = b.func().push_values(&[p]);
1257        let extra = Extra::Mem(b.func().add_mem(info));
1258        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
1259        b.ret(&[loaded]);
1260
1261        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
1262        module.add_func(func);
1263        module
1264    }
1265
1266    /// One instruction that says something and produces nothing, with a payload or without one.
1267    fn marker(b: &mut Builder<'_>, opcode: Opcode, info: Option<MemInfo>, on: &[Value]) {
1268        let args = b.func().push_values(on);
1269        let extra = match info {
1270            Some(info) => Extra::Mem(b.func().add_mem(info)),
1271            None => Extra::None,
1272        };
1273        b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
1274    }
1275
1276    /// A module with one function that reaches two objects through two `restrict` pointers.
1277    ///
1278    /// Built by hand rather than by [`insert`], because nothing puts these in yet: the pass that
1279    /// does is the other half of this and it is not written. What this file is about is the calls,
1280    /// so what the function has to be is the shape the verifier believes.
1281    fn promising(names: &mut Interner) -> Module {
1282        let i32_ = Type::int(32);
1283        let mut func = Func::new(
1284            names.intern("kernel"),
1285            Signature::new().with_params(&[Type::PTR, Type::PTR]),
1286        );
1287        let entry = func.create_block();
1288        let to = func.append_param(entry, Type::PTR);
1289        let from = func.append_param(entry, Type::PTR);
1290
1291        let empty = MemInfo {
1292            size: 0,
1293            align: 1,
1294            order: MemOrder::NotAtomic,
1295            tbaa: None,
1296            owns: 0,
1297            restrict: Restrict::NONE,
1298        };
1299        // The slot the block keeps its record in, whose size is `rucc_safe_rt::restrict::Scope`.
1300        let slot = MemInfo { size: 112, align: 8, ..empty };
1301        let mut b = Builder::new(&mut func, entry);
1302        let extra = Extra::Mem(b.func().add_mem(slot));
1303        let scope = b.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR);
1304
1305        // Two bases of one clique, which is what a function with two `restrict` parameters gets.
1306        let read =
1307            MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 2 }, ..empty };
1308        let writ =
1309            MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 1 }, ..empty };
1310        let opening = MemInfo { restrict: Restrict { clique: 1, base: 2 }, ..slot };
1311        marker(&mut b, Opcode::RestrictEnter, Some(opening), &[scope]);
1312        marker(&mut b, Opcode::CheckRestrictRead, Some(read), &[from]);
1313        let args = b.func().push_values(&[from]);
1314        let extra = Extra::Mem(b.func().add_mem(read));
1315        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
1316        marker(&mut b, Opcode::CheckRestrictWrite, Some(writ), &[to]);
1317        let args = b.func().push_values(&[loaded, to]);
1318        let extra = Extra::Mem(b.func().add_mem(writ));
1319        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
1320        marker(&mut b, Opcode::RestrictLeave, None, &[scope]);
1321        b.ret(&[]);
1322
1323        let mut module = Module::new(names.intern("kernel.c"), &target());
1324        module.add_func(func);
1325        module
1326    }
1327
1328    #[test]
1329    fn a_restrict_check_becomes_the_call_that_says_which_pointer_reached_where() {
1330        // Two descriptors, one per check, because each of them is a judgement that can refuse and a
1331        // judgement that refuses has to say what it refused. The two markers have none, for the
1332        // reason a plane write has none: opening and closing a block decides nothing.
1333        let mut names = Interner::new();
1334        let mut module = promising(&mut names);
1335        assert_eq!(lower(&mut module, &mut names), 2);
1336
1337        let id = module.funcs().next().expect("the module has one function");
1338        assert_eq!(
1339            print_func(&module, &module[id], &names),
1340            "func @kernel(ptr, ptr), linkage(external) {\n\
1341             block0(%0: ptr, %1: ptr):\n    \
1342             %2 = alloca, size 112, align 8\n    \
1343             %3 = iconst.i32 65538\n    \
1344             call @__rucc_restrict_enter(%2, %3) : (ptr, i32)\n    \
1345             %4 = global_addr @__rucc_safety_desc_0\n    \
1346             %5 = iconst.i64 4\n    \
1347             %6 = iconst.i32 65538\n    \
1348             %7 = iconst.i32 0\n    \
1349             call @__rucc_check_restrict(%1, %5, %6, %7, %4) : (ptr, i64, i32, i32, ptr)\n    \
1350             %8 = load.i32 %1, size 4, align 4, restrict(1, 2)\n    \
1351             %9 = global_addr @__rucc_safety_desc_1\n    \
1352             %10 = iconst.i64 4\n    \
1353             %11 = iconst.i32 65537\n    \
1354             %12 = iconst.i32 1\n    \
1355             call @__rucc_check_restrict(%0, %10, %11, %12, %9) : (ptr, i64, i32, i32, ptr)\n    \
1356             store %8 -> %0, size 4, align 4, restrict(1, 1)\n    \
1357             call @__rucc_restrict_leave(%2) : (ptr)\n    \
1358             return\n\
1359             }\n"
1360        );
1361
1362        if let Err(errors) = verify_func(&module, &module[id], &names) {
1363            panic!("that was expected to be believed: {errors:#?}");
1364        }
1365    }
1366
1367    #[test]
1368    fn the_judgement_a_restrict_check_names_is_the_one_about_the_pair() {
1369        // J8 rather than J1. Document 04 section 4.6 keeps this judgement out of J1 on purpose,
1370        // because a single access is never the violation: what is refused is a pair of them, and
1371        // the reporter prints a different sentence for it.
1372        let mut names = Interner::new();
1373        let mut module = promising(&mut names);
1374        lower(&mut module, &mut names);
1375
1376        let rows: Vec<u8> = module
1377            .globals()
1378            .map(|id| {
1379                let init = module[id].init.expect("a descriptor is a definition");
1380                match module[init][0] {
1381                    Datum::Scalar { value, .. } => {
1382                        u8::try_from(module[value].bits()).expect("a judgement is one byte")
1383                    }
1384                    _ => panic!("a descriptor starts with its judgement"),
1385                }
1386            })
1387            .collect();
1388        assert_eq!(rows, [RESTRICT, RESTRICT]);
1389    }
1390
1391    #[test]
1392    fn the_two_numbers_are_packed_the_way_the_runtime_unpacks_them() {
1393        // The other half of this is `rucc_safe_rt::restrict::tag`, and the two agree by both being
1394        // written down rather than by one calling the other, since this crate does not depend on
1395        // the runtime. A clique in the low half and a base in the high one would be read as a
1396        // scope nobody opened, which the runtime would pass and nobody would notice.
1397        assert_eq!(tag(1, 2), 0x0001_0002);
1398        assert_eq!(tag(0xffff, 0xffff), u32::MAX);
1399        assert_eq!(tag(0, 0), 0);
1400    }
1401
1402    #[test]
1403    fn a_read_of_the_plane_becomes_the_call_that_carries_the_type_asked_about() {
1404        // Three rows rather than two, because a read now asks two questions of two planes and the
1405        // pair of them is a judgement that has to say what it refused. The type travels as the same
1406        // number a store of the same type would have recorded, which is the only way the two can be
1407        // compared. There is no fourth row for the init question because the two questions are one
1408        // call here, and a type check's row and an init check's row say the same thing.
1409        let mut names = Interner::new();
1410        let mut module = asking_the_plane(&mut names);
1411        assert_eq!(lower(&mut module, &mut names), 3);
1412
1413        // The printer writes an `i32` immediate as a signed number and the identifier is a hash
1414        // that uses the whole width, so what appears is the same bits read the other way round.
1415        let number = i32::from_ne_bytes(plane::identifier("int").to_ne_bytes());
1416        let id = module.funcs().next().expect("the module has one function");
1417        assert_eq!(
1418            print_func(&module, &module[id], &names),
1419            format!(
1420                "func @read(ptr) -> i32, linkage(external) {{\n\
1421                 block0(%0: ptr):\n    \
1422                 %1 = alloca, size 32, align 8\n    \
1423                 call @__rucc_cap_recover(%1, %0) : (ptr, ptr)\n    \
1424                 %2 = global_addr @__rucc_safety_desc_0\n    \
1425                 %3 = iconst.i64 4\n    \
1426                 %4 = iconst.i64 4\n    \
1427                 call @__rucc_check_bounds(%0, %3, %4, %1, %2) : (ptr, i64, i64, ptr, ptr)\n    \
1428                 %5 = global_addr @__rucc_safety_desc_1\n    \
1429                 call @__rucc_check_live(%1, %0, %5) : (ptr, ptr, ptr)\n    \
1430                 %6 = global_addr @__rucc_safety_desc_2\n    \
1431                 %7 = iconst.i64 4\n    \
1432                 %8 = iconst.i32 {number}\n    \
1433                 call @__rucc_check_typed_init(%0, %7, %8, %6) : (ptr, i64, i32, ptr)\n    \
1434                 %9 = load.i32 %0, size 4, align 4, tbaa !1\n    \
1435                 return %9\n\
1436                 }}\n"
1437            )
1438        );
1439
1440        if let Err(errors) = verify_func(&module, &module[id], &names) {
1441            panic!("that was expected to be believed: {errors:#?}");
1442        }
1443    }
1444
1445    /// What stands between the two plane checks in [`plane_checks`].
1446    enum Between {
1447        /// Nothing at all, which is what one read leaves behind.
1448        Nothing,
1449        /// Nothing, but the init check asks about twice as many bytes.
1450        Wider,
1451        /// A store, which is memory changing between the two questions.
1452        Store,
1453        /// Another type check, which is another read.
1454        Another,
1455    }
1456
1457    /// A function with a type check and an init check over the same four bytes at its parameter,
1458    /// with `between` standing between the two, and the type check.
1459    ///
1460    /// The shape [`crate::access_checks`] writes in front of a read, cut down to the two checks
1461    /// [`partner`] has to decide about. Nothing here is lowered, because what is being tested is
1462    /// the decision rather than the call it leads to.
1463    fn plane_checks(names: &mut Interner, between: Between) -> (Func, Inst) {
1464        let mut func = Func::new(names.intern("read"), Signature::new().with_params(&[Type::PTR]));
1465        let entry = func.create_block();
1466        let p = func.append_param(entry, Type::PTR);
1467        let info = MemInfo {
1468            size: 4,
1469            align: 4,
1470            order: MemOrder::NotAtomic,
1471            tbaa: None,
1472            owns: 0,
1473            restrict: Restrict::NONE,
1474        };
1475
1476        let mut b = Builder::new(&mut func, entry);
1477        let args = b.func().push_values(&[p]);
1478        let cap = b.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
1479        marker(&mut b, Opcode::CheckType, Some(info), &[cap, p]);
1480        let check = b.func().insts(entry).last().expect("the type check was just put in");
1481        match between {
1482            Between::Nothing | Between::Wider => {}
1483            Between::Store => {
1484                let zero = b.iconst(Type::int(32), 0);
1485                marker(&mut b, Opcode::Store, Some(info), &[zero, p]);
1486            }
1487            Between::Another => marker(&mut b, Opcode::CheckType, Some(info), &[cap, p]),
1488        }
1489        let asked = match between {
1490            Between::Wider => MemInfo { size: 8, ..info },
1491            _ => info,
1492        };
1493        marker(&mut b, Opcode::CheckInit, Some(asked), &[cap, p]);
1494        b.ret(&[]);
1495        (func, check)
1496    }
1497
1498    #[test]
1499    fn the_init_check_beside_a_type_check_belongs_to_the_same_read() {
1500        let mut names = Interner::new();
1501        let (func, check) = plane_checks(&mut names, Between::Nothing);
1502        let found = partner(&func, check).expect("the two are one read's");
1503        assert_eq!(func[found].opcode, Opcode::CheckInit);
1504    }
1505
1506    #[test]
1507    fn an_init_check_over_other_bytes_than_the_type_check_asked_about_is_not_its_partner() {
1508        // Same address and a different width is two reads of the same place, and fusing them would
1509        // ask the init question about four bytes the program has not read yet.
1510        let mut names = Interner::new();
1511        let (func, check) = plane_checks(&mut names, Between::Wider);
1512        assert!(partner(&func, check).is_none());
1513    }
1514
1515    #[test]
1516    fn a_store_between_the_two_plane_checks_keeps_them_apart() {
1517        // The two calls happen either side of the store, so the init question is answered against
1518        // the planes the store left rather than the ones the type question saw.
1519        let mut names = Interner::new();
1520        let (func, check) = plane_checks(&mut names, Between::Store);
1521        assert!(partner(&func, check).is_none());
1522    }
1523
1524    #[test]
1525    fn the_init_check_of_a_later_read_is_not_an_earlier_reads_partner() {
1526        // A second type check is a second read, and its init check is the one that follows it. The
1527        // search stops rather than walking past, because pairing across it would move an init
1528        // question earlier than the program asks it.
1529        let mut names = Interner::new();
1530        let (func, check) = plane_checks(&mut names, Between::Another);
1531        assert!(partner(&func, check).is_none());
1532    }
1533
1534    #[test]
1535    fn the_judgement_a_type_check_names_is_the_one_about_the_planes() {
1536        // J1 rather than a judgement of its own. Document 04 section 4.4's first judgement is an
1537        // access the capability, the planes or the alignment did not permit, and both the type
1538        // plane and the init plane are planes, so that is the sentence the reporter should print
1539        // for either of them. That the two say the same thing is also why the one row the fused
1540        // call carries can stand for both of them.
1541        let mut names = Interner::new();
1542        let mut module = asking_the_plane(&mut names);
1543        lower(&mut module, &mut names);
1544
1545        let rows: Vec<u8> = module
1546            .globals()
1547            .map(|id| {
1548                let init = module[id].init.expect("a descriptor is a definition");
1549                match module[init][0] {
1550                    Datum::Scalar { value, .. } => {
1551                        u8::try_from(module[value].bits()).expect("a judgement is one byte")
1552                    }
1553                    _ => panic!("a descriptor starts with its judgement"),
1554                }
1555            })
1556            .collect();
1557        assert_eq!(rows, [ACCESS, ACCESS, ACCESS]);
1558    }
1559
1560    #[test]
1561    fn a_store_becomes_the_calls_that_record_what_it_wrote() {
1562        // Two operands and no descriptor for the init plane's call, against three for the type
1563        // plane's. A type number is the one thing the two writes do not have in common: what a
1564        // store stored through is a thing the compiler has to name, and that it stored at all is
1565        // not.
1566        let mut names = Interner::new();
1567        let mut module = stored(&mut names);
1568        assert_eq!(lower(&mut module, &mut names), 2);
1569
1570        let id = module.funcs().next().expect("the module has one function");
1571        let printed = print_func(&module, &module[id], &names);
1572        assert!(
1573            printed.contains("call @__rucc_meta_type(%0, %7, %8) : (ptr, i64, i32)\n"),
1574            "{printed}"
1575        );
1576        assert!(printed.contains("call @__rucc_meta_init(%0, %9) : (ptr, i64)\n"), "{printed}");
1577
1578        if let Err(errors) = verify_func(&module, &module[id], &names) {
1579            panic!("that was expected to be believed: {errors:#?}");
1580        }
1581    }
1582
1583    #[test]
1584    fn a_store_of_a_pointer_becomes_the_call_that_says_which_thread_wrote_it() {
1585        // Two operands and no descriptor, which is the same shape the init plane's write has. A
1586        // plane write refuses nothing, so there is no row to point at, and neither the thread nor
1587        // its count is something this pass could pass in: both are facts about the moment the
1588        // program reaches the call, so the runtime reads them for itself.
1589        let mut names = Interner::new();
1590        let mut module = racing(&mut names);
1591        lower(&mut module, &mut names);
1592
1593        let id = module.funcs().next().expect("the module has one function");
1594        let printed = print_func(&module, &module[id], &names);
1595        assert!(printed.contains("call @__rucc_meta_epoch(%0, %"), "{printed}");
1596        assert!(printed.contains(") : (ptr, i64)\n"), "{printed}");
1597
1598        if let Err(errors) = verify_func(&module, &module[id], &names) {
1599            panic!("that was expected to be believed: {errors:#?}");
1600        }
1601    }
1602
1603    #[test]
1604    fn a_store_of_a_pointer_also_becomes_the_call_that_asks_who_was_there_first() {
1605        // Three operands and a descriptor, which is the shape the init plane's check has: the plane
1606        // holds stamps rather than types, so a range is the whole of the question. The descriptor
1607        // says J9 rather than J1, and that number is the whole of what makes the report read as a
1608        // race, since the reporter takes the judgement out of the row it was handed.
1609        let mut names = Interner::new();
1610        let mut module = racing(&mut names);
1611        lower(&mut module, &mut names);
1612
1613        let id = module.funcs().next().expect("the module has one function");
1614        let printed = print_func(&module, &module[id], &names);
1615        assert!(printed.contains("call @__rucc_check_race(%0, %"), "{printed}");
1616        assert!(printed.contains(") : (ptr, i64, ptr)\n"), "{printed}");
1617
1618        // In front of the store, and the recording behind it. The recording overwrites the stamp
1619        // the check reads, so the two in the other order would have the check asking about the
1620        // write it was called for.
1621        let asked = printed.find("__rucc_check_race").expect("the check lowered");
1622        let stamp = printed.find("__rucc_meta_epoch").expect("so did the recording");
1623        assert!(asked < stamp, "{printed}");
1624
1625        let rows: Vec<u8> = module
1626            .globals()
1627            .map(|id| {
1628                let init = module[id].init.expect("a descriptor is a definition");
1629                match module[init][0] {
1630                    Datum::Scalar { value, .. } => {
1631                        u8::try_from(module[value].bits()).expect("a judgement is one byte")
1632                    }
1633                    _ => panic!("a descriptor starts with its judgement"),
1634                }
1635            })
1636            .collect();
1637        assert_eq!(rows, [ACCESS, ACCESS, RACE]);
1638
1639        if let Err(errors) = verify_func(&module, &module[id], &names) {
1640            panic!("that was expected to be believed: {errors:#?}");
1641        }
1642    }
1643
1644    #[test]
1645    fn a_copy_becomes_the_calls_that_move_the_planes_across() {
1646        // Three operands each and no descriptor of their own. A plane write refuses nothing, and
1647        // neither what the copied bytes are nor whether anything ever wrote them is a thing the
1648        // compiler knows, so there is no type number and no length beyond the range: all three
1649        // calls read what is over the source and write it over the destination. The third is the
1650        // aux rather than a plane, and it is here because the capability of a pointer inside a
1651        // structure being copied whole travels the same way its type and its init do.
1652        //
1653        // The four descriptors the count reports are the range and lifetime checks in front, one
1654        // per end of the copy, which are the accesses the plane writes are not.
1655        let mut names = Interner::new();
1656        let mut module = copied(&mut names);
1657        assert_eq!(lower(&mut module, &mut names), 4);
1658
1659        let id = module.funcs().next().expect("the module has one function");
1660        assert_eq!(
1661            print_func(&module, &module[id], &names),
1662            "func @move(ptr, ptr), linkage(external) {\n\
1663             block0(%0: ptr, %1: ptr):\n    \
1664             %2 = alloca, size 32, align 8\n    \
1665             %3 = alloca, size 32, align 8\n    \
1666             call @__rucc_cap_recover(%3, %1) : (ptr, ptr)\n    \
1667             call @__rucc_cap_recover(%2, %0) : (ptr, ptr)\n    \
1668             %4 = global_addr @__rucc_safety_desc_0\n    \
1669             %5 = iconst.i64 24\n    \
1670             %6 = iconst.i64 8\n    \
1671             call @__rucc_check_bounds(%0, %5, %6, %2, %4) : (ptr, i64, i64, ptr, ptr)\n    \
1672             %7 = global_addr @__rucc_safety_desc_1\n    \
1673             call @__rucc_check_live(%2, %0, %7) : (ptr, ptr, ptr)\n    \
1674             %8 = global_addr @__rucc_safety_desc_2\n    \
1675             %9 = iconst.i64 24\n    \
1676             %10 = iconst.i64 8\n    \
1677             call @__rucc_check_bounds(%1, %9, %10, %3, %8) : (ptr, i64, i64, ptr, ptr)\n    \
1678             %11 = global_addr @__rucc_safety_desc_3\n    \
1679             call @__rucc_check_live(%3, %1, %11) : (ptr, ptr, ptr)\n    \
1680             memcpy %0, %1, size 24, align 8\n    \
1681             %12 = iconst.i64 24\n    \
1682             call @__rucc_meta_type_copy(%0, %1, %12) : (ptr, ptr, i64)\n    \
1683             %13 = iconst.i64 24\n    \
1684             call @__rucc_meta_init_copy(%0, %1, %13) : (ptr, ptr, i64)\n    \
1685             %14 = iconst.i64 24\n    \
1686             call @__rucc_cap_copy(%0, %1, %14) : (ptr, ptr, i64)\n    \
1687             return\n\
1688             }\n"
1689        );
1690
1691        if let Err(errors) = verify_func(&module, &module[id], &names) {
1692            panic!("that was expected to be believed: {errors:#?}");
1693        }
1694    }
1695
1696    /// The alignment is the access's own and not its width.
1697    ///
1698    /// Two numbers that are four apiece in [`checked`] and would look alike if only one of them
1699    /// went through. The access here is four bytes wide and may assume nothing about where it
1700    /// starts, which is what the front end says about a member of a packed record, and what has
1701    /// to arrive at the runtime is four bytes and an alignment of one.
1702    #[test]
1703    fn the_alignment_that_goes_through_is_the_one_the_access_may_assume() {
1704        let mut names = Interner::new();
1705        let mut module = unaligned(&mut names);
1706        assert_eq!(lower(&mut module, &mut names), 3);
1707
1708        let id = module.funcs().next().expect("the module has one function");
1709        let printed = print_func(&module, &module[id], &names);
1710        assert!(printed.contains("%3 = iconst.i64 4\n"), "{printed}");
1711        assert!(printed.contains("%4 = iconst.i64 1\n"), "{printed}");
1712        assert!(
1713            printed.contains(
1714                "call @__rucc_check_bounds(%0, %3, %4, %1, %2) : (ptr, i64, i64, ptr, ptr)\n"
1715            ),
1716            "{printed}"
1717        );
1718    }
1719
1720    #[test]
1721    fn every_check_becomes_a_call_carrying_the_descriptor_it_is_described_by() {
1722        let mut names = Interner::new();
1723        let mut module = checked(&mut names);
1724        assert_eq!(lower(&mut module, &mut names), 3);
1725
1726        let id = module.funcs().next().expect("the module has one function");
1727        assert_eq!(
1728            print_func(&module, &module[id], &names),
1729            "func @read(ptr) -> i32, linkage(external) {\n\
1730             block0(%0: ptr):\n    \
1731             %1 = alloca, size 32, align 8\n    \
1732             call @__rucc_cap_recover(%1, %0) : (ptr, ptr)\n    \
1733             %2 = global_addr @__rucc_safety_desc_0\n    \
1734             %3 = iconst.i64 4\n    \
1735             %4 = iconst.i64 4\n    \
1736             call @__rucc_check_bounds(%0, %3, %4, %1, %2) : (ptr, i64, i64, ptr, ptr)\n    \
1737             %5 = global_addr @__rucc_safety_desc_1\n    \
1738             call @__rucc_check_live(%1, %0, %5) : (ptr, ptr, ptr)\n    \
1739             %6 = global_addr @__rucc_safety_desc_2\n    \
1740             %7 = iconst.i64 4\n    \
1741             call @__rucc_check_init(%0, %7, %6) : (ptr, i64, ptr)\n    \
1742             %8 = load.i32 %0, size 4, align 4\n    \
1743             return %8\n\
1744             }\n"
1745        );
1746    }
1747
1748    #[test]
1749    fn the_capabilities_the_checks_were_reading_are_taken_out() {
1750        // A `cap` is a type nothing in the back end has been taught, so one left behind is a
1751        // compilation that fails rather than a value nobody reads.
1752        let mut names = Interner::new();
1753        let mut module = checked(&mut names);
1754        lower(&mut module, &mut names);
1755
1756        let id = module.funcs().next().expect("the module has one function");
1757        let func = &module[id];
1758        let left: Vec<Opcode> = func
1759            .blocks()
1760            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1761            .map(|inst| func[inst].opcode)
1762            .collect();
1763        assert!(!left.contains(&Opcode::CapOf), "{left:?}");
1764    }
1765
1766    #[test]
1767    fn what_it_produces_is_a_module_the_verifier_believes() {
1768        let mut names = Interner::new();
1769        let mut module = checked(&mut names);
1770        lower(&mut module, &mut names);
1771
1772        let id = module.funcs().next().expect("the module has one function");
1773        if let Err(errors) = verify_func(&module, &module[id], &names) {
1774            panic!("that was expected to be believed: {errors:#?}");
1775        }
1776    }
1777
1778    #[test]
1779    fn the_section_is_one_descriptor_per_check_and_nothing_else() {
1780        // The runtime is handed one address and dereferences it, so what makes the section a table
1781        // is only that every variable in it is the same sixteen bytes long and eight aligned. That
1782        // is what `--emit=safety-summary` will divide by, so it is checked here rather than
1783        // assumed.
1784        let mut names = Interner::new();
1785        let mut module = checked(&mut names);
1786        let rows = lower(&mut module, &mut names);
1787
1788        let globals: Vec<_> = module.globals().collect();
1789        assert_eq!(globals.len(), rows);
1790        for (index, id) in globals.iter().enumerate() {
1791            let desc = &module[*id];
1792            assert_eq!(names.resolve(desc.name), label(index));
1793            assert_eq!(
1794                names.resolve(desc.section.expect("a descriptor names its section")),
1795                SECTION
1796            );
1797            assert_eq!(desc.linkage, Linkage::Internal);
1798            assert!(desc.constant);
1799            assert_eq!(desc.align, 8);
1800            assert_eq!(desc.size, WIDTH);
1801
1802            // The image has to add up to the size, or the descriptor after this one starts in the
1803            // middle of this one.
1804            let init = desc.init.expect("a descriptor is a definition");
1805            let written: u64 = module[init].iter().map(|datum| datum.size(&module)).sum();
1806            assert_eq!(written, WIDTH);
1807        }
1808    }
1809
1810    #[test]
1811    fn the_judgement_a_descriptor_names_is_the_one_the_check_decides() {
1812        // A report that said J1 where the program derived a pointer would send somebody looking
1813        // at the wrong line, so the two rows a derivation produces are checked by hand.
1814        let mut names = Interner::new();
1815        let mut func = Func::new(
1816            names.intern("walk"),
1817            Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
1818        );
1819        let entry = func.create_block();
1820        let p = func.append_param(entry, Type::PTR);
1821        let n = func.append_param(entry, Type::int(64));
1822        let mut b = Builder::new(&mut func, entry);
1823        let args = b.func().push_values(&[p, n]);
1824        let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1825        b.ret(&[moved]);
1826        let (plane, numbers) = planeless(&mut names);
1827        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
1828
1829        let mut table = Vec::new();
1830        calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1831        assert_eq!(table, [Descriptor { judgement: DERIVE, class: 0, size: 0 }]);
1832    }
1833
1834    #[test]
1835    fn a_check_over_a_length_the_program_worked_out_passes_that_length_along() {
1836        // Section 7.4's hoisted check. The number of bytes is the third operand rather than the
1837        // payload's size, so what the call is handed is the value and not a constant, and the
1838        // descriptor says zero because there is no one width to report.
1839        let mut names = Interner::new();
1840        let mut func = Func::new(
1841            names.intern("sweep"),
1842            Signature::new().with_params(&[Type::PTR, Type::int(64)]),
1843        );
1844        let entry = func.create_block();
1845        let p = func.append_param(entry, Type::PTR);
1846        let n = func.append_param(entry, Type::int(64));
1847        let info = MemInfo {
1848            size: 4,
1849            align: 4,
1850            order: MemOrder::NotAtomic,
1851            tbaa: None,
1852            owns: 0,
1853            restrict: Restrict::NONE,
1854        };
1855        let mut b = Builder::new(&mut func, entry);
1856        let of = b.unary(Opcode::CapOf, p, Type::CAP);
1857        let args = b.func().push_values(&[of, p, n]);
1858        let extra = Extra::Mem(b.func().add_mem(info));
1859        b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1860        b.ret(&[]);
1861
1862        let mut table = Vec::new();
1863        let numbers = planeless(&mut names).1;
1864        calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1865        assert_eq!(table, [Descriptor { judgement: ACCESS, class: 0, size: 0 }]);
1866
1867        let mut module = Module::new(names.intern("sweep.c"), &target());
1868        module.add_func(func);
1869        let id = module.funcs().next().expect("the module has one function");
1870        assert_eq!(
1871            print_func(&module, &module[id], &names),
1872            "func @sweep(ptr, i64), linkage(external) {\n\
1873             block0(%0: ptr, %1: i64):\n    \
1874             %2 = alloca, size 32, align 8\n    \
1875             call @__rucc_cap_recover(%2, %0) : (ptr, ptr)\n    \
1876             %3 = global_addr @__rucc_safety_desc_0\n    \
1877             %4 = iconst.i64 1\n    \
1878             call @__rucc_check_bounds(%0, %1, %4, %2, %3) : (ptr, i64, i64, ptr, ptr)\n    \
1879             return\n\
1880             }\n"
1881        );
1882    }
1883
1884    #[test]
1885    fn a_length_wider_than_the_word_is_cut_down_to_it() {
1886        // The pass that works out how many bytes a loop covers has no target to ask, so on a
1887        // thirty two bit target it hands over a number that does not fit the runtime's own
1888        // parameter. What comes out is a truncation rather than a call the verifier refuses.
1889        let mut names = Interner::new();
1890        let mut func = Func::new(
1891            names.intern("sweep"),
1892            Signature::new().with_params(&[Type::PTR, Type::int(64)]),
1893        );
1894        let entry = func.create_block();
1895        let p = func.append_param(entry, Type::PTR);
1896        let n = func.append_param(entry, Type::int(64));
1897        let info = MemInfo {
1898            size: 4,
1899            align: 4,
1900            order: MemOrder::NotAtomic,
1901            tbaa: None,
1902            owns: 0,
1903            restrict: Restrict::NONE,
1904        };
1905        let mut b = Builder::new(&mut func, entry);
1906        let of = b.unary(Opcode::CapOf, p, Type::CAP);
1907        let args = b.func().push_values(&[of, p, n]);
1908        let extra = Extra::Mem(b.func().add_mem(info));
1909        b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1910        b.ret(&[]);
1911
1912        let mut table = Vec::new();
1913        let numbers = planeless(&mut names).1;
1914        calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
1915        let opcodes: Vec<Opcode> = func
1916            .blocks()
1917            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1918            .map(|inst| func[inst].opcode)
1919            .collect();
1920        assert!(opcodes.contains(&Opcode::Trunc), "{opcodes:?}");
1921    }
1922
1923    /// A function that asks how many bytes its parameter covers, in the width the caller names.
1924    ///
1925    /// The result is returned so that something reads it, because a query nobody reads would be
1926    /// removed by any pass that ran and this test is about what the value it produces turns into.
1927    fn asking(names: &mut Interner, ty: Type) -> Func {
1928        let mut func = Func::new(
1929            names.intern("cover"),
1930            Signature::new().with_params(&[Type::PTR, ty]).with_returns(&[ty]),
1931        );
1932        let entry = func.create_block();
1933        let p = func.append_param(entry, Type::PTR);
1934        let want = func.append_param(entry, ty);
1935        let mut b = Builder::new(&mut func, entry);
1936        let of = b.unary(Opcode::CapOf, p, Type::CAP);
1937        let args = b.func().push_values(&[of, p, want]);
1938        let got = b.value(InstData { args, ..InstData::new(Opcode::CapExtent) }, ty);
1939        b.ret(&[got]);
1940        func
1941    }
1942
1943    #[test]
1944    fn the_extent_query_becomes_a_call_that_carries_no_descriptor() {
1945        // The one rewrite here that is not a judgement, so it writes no row and the table stays
1946        // empty. What it is for is section 7.4's split, which needs a number and not a verdict.
1947        let mut names = Interner::new();
1948        let mut func = asking(&mut names, Type::int(64));
1949
1950        let mut table = Vec::new();
1951        let numbers = planeless(&mut names).1;
1952        calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1953        assert!(table.is_empty(), "{table:?}");
1954
1955        let mut module = Module::new(names.intern("cover.c"), &target());
1956        module.add_func(func);
1957        let id = module.funcs().next().expect("the module has one function");
1958        assert_eq!(
1959            print_func(&module, &module[id], &names),
1960            "func @cover(ptr, i64) -> i64, linkage(external) {\n\
1961             block0(%0: ptr, %1: i64):\n    \
1962             %2 = call @__rucc_extent(%0, %1) : (ptr, i64) -> i64\n    \
1963             return %2\n\
1964             }\n"
1965        );
1966        if let Err(errors) = verify_func(&module, &module[id], &names) {
1967            panic!("that was expected to be believed: {errors:#?}");
1968        }
1969    }
1970
1971    #[test]
1972    fn an_extent_asked_for_in_a_width_the_target_does_not_have_is_converted_back() {
1973        // On a thirty two bit target the runtime's own parameter and return are thirty two bits
1974        // wide, and the pass that wrote the arithmetic worked in sixty four. So the limit is cut
1975        // down on the way in and the answer is widened on the way out, and everything reading the
1976        // query still reads a value of the type it had.
1977        let mut names = Interner::new();
1978        let mut func = asking(&mut names, Type::int(64));
1979
1980        let mut table = Vec::new();
1981        let numbers = planeless(&mut names).1;
1982        calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
1983        let opcodes: Vec<Opcode> = func
1984            .blocks()
1985            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1986            .map(|inst| func[inst].opcode)
1987            .collect();
1988        assert!(opcodes.contains(&Opcode::Trunc), "the limit goes in narrowed: {opcodes:?}");
1989        assert!(opcodes.contains(&Opcode::ZExt), "and the answer comes back widened: {opcodes:?}");
1990        assert!(
1991            !opcodes.contains(&Opcode::CapExtent),
1992            "with nothing left of the query: {opcodes:?}"
1993        );
1994    }
1995
1996    /// A module holding one function that declares a region and does nothing else.
1997    fn exempt(names: &mut Interner) -> Module {
1998        let reason = names.intern("hand written assembly, checked by review");
1999        let mut func = Func::new(names.intern("driver"), Signature::new());
2000        let entry = func.create_block();
2001        let mut b = Builder::new(&mut func, entry);
2002        b.inst(
2003            InstData { extra: Extra::Reason(reason), ..InstData::new(Opcode::SafeRegionBegin) },
2004            &[],
2005        );
2006        b.inst(InstData::new(Opcode::SafeRegionEnd), &[]);
2007        b.ret(&[]);
2008        let mut module = Module::new(names.intern("driver.c"), &target());
2009        module.add_func(func);
2010        module
2011    }
2012
2013    #[test]
2014    fn the_markers_around_a_declared_region_lower_into_nothing_at_all() {
2015        // The only pair here that becomes no call. A region is the reason some code carries no
2016        // checks rather than something the code does, so once `crate::summary` has counted it
2017        // there is nothing left for the back end to be handed.
2018        let mut names = Interner::new();
2019        let mut module = exempt(&mut names);
2020        assert_eq!(lower(&mut module, &mut names), 0);
2021
2022        let id = module.funcs().next().expect("the module has one function");
2023        assert_eq!(
2024            print_func(&module, &module[id], &names),
2025            "func @driver(), linkage(external) {\n\
2026             block0:\n    \
2027             return\n\
2028             }\n"
2029        );
2030
2031        if let Err(errors) = verify_func(&module, &module[id], &names) {
2032            panic!("that was expected to be believed: {errors:#?}");
2033        }
2034    }
2035
2036    #[test]
2037    fn a_region_costs_the_object_file_no_descriptor_either() {
2038        // A descriptor describes a failure and a marker cannot fail, so a build made of nothing but
2039        // declared regions has an empty section and gets none.
2040        let mut names = Interner::new();
2041        let mut module = exempt(&mut names);
2042        lower(&mut module, &mut names);
2043        assert_eq!(module.globals().count(), 0);
2044    }
2045
2046    #[test]
2047    fn a_module_with_nothing_to_check_gets_no_section_at_all() {
2048        // An object with an empty section in it is an object that says the compiler had something
2049        // to say and did not say it.
2050        let mut names = Interner::new();
2051        let mut module = Module::new(names.intern("empty.c"), &target());
2052        assert_eq!(lower(&mut module, &mut names), 0);
2053        assert_eq!(module.globals().count(), 0);
2054    }
2055}