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;
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 J8, which is what a `restrict` check decides.
75const RESTRICT: u8 = 8;
76
77/// One descriptor, as much of it as this pass knows.
78///
79/// The `pc` field of the runtime's descriptor is not here. Filling it means a relocation against
80/// the enclosing function plus the offset of the call, which the IR cannot express and which
81/// nothing needs while the reporter has the address the check was given, so those eight bytes are
82/// written as zero.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct Descriptor {
85    /// Which judgement the check decides.
86    pub judgement: u8,
87    /// Which row of document 03's tables the failure is, which nothing decides yet.
88    pub class: u8,
89    /// How many bytes the access covers, saturating, and zero where the check is not about an
90    /// access of a known width.
91    pub size: u16,
92}
93
94/// Turns every check in a module into a call, and gives the module the descriptors they name.
95///
96/// The number of descriptors, which is the number of checks that survived the optimizer. That is
97/// the numerator of everything document 13 measures and it is not recoverable afterwards, since by
98/// this point a discharged check is simply not there.
99///
100/// Whether this runs at all is `-fsafety=`, and the driver decides it, for the reason
101/// [`crate::run`] gives.
102pub fn lower(module: &mut Module, names: &mut Interner) -> usize {
103    // `size_t`, taken from the module rather than written as sixty four, so that the argument is
104    // the one the runtime's own declaration of the entry point has.
105    let word = Type::int(module.datalayout.pointer_bits);
106    // The descriptors are collected rather than added as they are found, because a function is
107    // borrowed out of the module while its checks are being rewritten. What the rewrite needs is
108    // the name of the descriptor it is about, and a name is the position in this list, so both ends
109    // agree without either holding the module.
110    let mut written: Vec<Descriptor> = Vec::new();
111    // The same reason the descriptors are collected: a plane write names a node of the module's
112    // metadata table and the module is not reachable while one of its functions is borrowed out of
113    // it. The table is a handful of nodes, so it is read once here rather than per instruction.
114    let numbers = plane::numbers(module, names);
115    for id in module.funcs() {
116        if module[id].is_declaration() {
117            continue;
118        }
119        calls(&mut module[id], names, word, &numbers, &mut written);
120    }
121    for (index, row) in written.iter().enumerate() {
122        emit(module, names, index, *row);
123    }
124    written.len()
125}
126
127/// Rewrites every check in one function, and takes the capabilities out afterwards.
128fn calls(
129    func: &mut Func,
130    names: &mut Interner,
131    word: Type,
132    numbers: &HashMap<Meta, u32>,
133    table: &mut Vec<Descriptor>,
134) {
135    let insts: Vec<Inst> =
136        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
137    for &inst in &insts {
138        match func[inst].opcode {
139            Opcode::CheckBounds => bounds(func, names, word, table, inst),
140            Opcode::CheckLive => live(func, names, table, inst),
141            Opcode::CheckDeriv => deriv(func, names, word, table, inst),
142            Opcode::CheckType => typed(func, names, word, numbers, table, inst),
143            Opcode::CheckInit => began(func, names, word, table, inst),
144            Opcode::CheckRestrictRead => promised(func, names, word, table, inst, false),
145            Opcode::CheckRestrictWrite => promised(func, names, word, table, inst, true),
146            Opcode::RestrictEnter => opened(func, names, inst),
147            Opcode::RestrictLeave => closed(func, names, inst),
148            Opcode::MetaType => judgement(func, names, word, numbers, inst),
149            Opcode::MetaTypeCopy => carriage(func, names, word, inst),
150            Opcode::MetaInit => written(func, names, word, inst),
151            Opcode::MetaInitCopy => carried(func, names, word, inst),
152            Opcode::CapExtent => extent(func, names, word, inst, "__rucc_extent"),
153            Opcode::CapExtentBack => extent(func, names, word, inst, "__rucc_extent_back"),
154            _ => {}
155        }
156    }
157    // Every `cap_of` in the function was put there to feed a check, and no check reads one any
158    // more. They are removed rather than left for the optimizer because the optimizer has already
159    // run, and a `cap` is a type the back end has never been taught.
160    for &inst in &insts {
161        if func[inst].opcode == Opcode::CapOf {
162            func.remove_inst(inst);
163        }
164    }
165}
166
167/// `check_bounds` becomes `__rucc_check_bounds(pointer, size, align, descriptor)`.
168///
169/// The size is the payload's for the check the front end wrote and the third operand's for the
170/// hoisted check of section 7.4, which is about a range the program worked out rather than about
171/// one access. The descriptor says zero bytes for that one, which is the field's own reading of a
172/// check that is not about an access of a known width, because the width is not known here either.
173///
174/// The alignment is the payload's too, and it is the whole of judgement J1's `addr mod align = 0`
175/// conjunct: the runtime tests it and nothing else here does. A hoisted check passes one, which
176/// says the range it is about assumes nothing, because the range is a span of bytes a loop will
177/// walk rather than one access and the accesses inside it carry their own.
178fn bounds(
179    func: &mut Func,
180    names: &mut Interner,
181    word: Type,
182    table: &mut Vec<Descriptor>,
183    inst: Inst,
184) {
185    let args = &func[func[inst].args];
186    let (Some(&pointer), computed) = (args.get(1), args.get(2).copied()) else { return };
187    let Extra::Mem(mem) = func[inst].extra else { return };
188    let size = func[mem].size;
189
190    let row = Descriptor {
191        judgement: ACCESS,
192        class: 0,
193        // Saturating, so that a report about a structure copy larger than a descriptor can hold
194        // says sixty five thousand rather than whatever the low sixteen bits happened to be.
195        size: if computed.is_some() { 0 } else { u16::try_from(size).unwrap_or(u16::MAX) },
196    };
197    let desc = record(func, names, table, inst, row);
198    let bytes = match computed {
199        Some(value) => fitted(func, inst, value, word),
200        None => konst(func, inst, Imm::int(i128::from(size), word), word),
201    };
202    let claim = if computed.is_some() { 1 } else { i128::from(func[mem].align) };
203    let align = konst(func, inst, Imm::int(claim, word), word);
204    let params = &[Type::PTR, word, word, Type::PTR];
205    call(func, names, inst, "__rucc_check_bounds", params, &[], &[pointer, bytes, align, desc]);
206}
207
208/// The same number in the width the runtime's own declaration asks for.
209///
210/// A pass that works out how many bytes a loop covers has no target to ask, so it writes the count
211/// in the width its arithmetic was in, and on a target whose `size_t` is narrower or wider than that
212/// the call would be handed the wrong type. Zero extension rather than sign, because the number is a
213/// count of bytes and a negative one is not a thing the caller can have meant.
214fn fitted(func: &mut Func, inst: Inst, value: Value, word: Type) -> Value {
215    let ty = func[value].ty;
216    if ty == word {
217        return value;
218    }
219    let opcode = if ty.bits() > word.bits() { Opcode::Trunc } else { Opcode::ZExt };
220    let span = func.span(inst);
221    let args = func.push_values(&[value]);
222    let made = func.create_inst(InstData { args, ..InstData::new(opcode) }, &[word], span);
223    func.insert_before(made, inst);
224    func[made].results().next().expect("a cast created with one result has one")
225}
226
227/// `check_live` becomes `__rucc_check_live(pointer, descriptor)`.
228fn live(func: &mut Func, names: &mut Interner, table: &mut Vec<Descriptor>, inst: Inst) {
229    let [_capability, pointer] = func[func[inst].args] else { return };
230    // No size. The check carries no payload, because whether anybody owns an address is a question
231    // about the address rather than about how many bytes are read through it.
232    let row = Descriptor { judgement: ACCESS, class: 0, size: 0 };
233    let desc = record(func, names, table, inst, row);
234    call(func, names, inst, "__rucc_check_live", &[Type::PTR, Type::PTR], &[], &[pointer, desc]);
235}
236
237/// `check_deriv` becomes `__rucc_check_deriv(base, derived, stride, descriptor)`.
238///
239/// The stride goes through as a value rather than into the descriptor, because a walk over a
240/// variable length array steps by a width the program computes and a descriptor is constant data.
241fn deriv(
242    func: &mut Func,
243    names: &mut Interner,
244    word: Type,
245    table: &mut Vec<Descriptor>,
246    inst: Inst,
247) {
248    let [_capability, base, derived, stride] = func[func[inst].args] else { return };
249    let row = Descriptor { judgement: DERIVE, class: 0, size: 0 };
250    let desc = record(func, names, table, inst, row);
251    let params = &[Type::PTR, Type::PTR, word, Type::PTR];
252    call(func, names, inst, "__rucc_check_deriv", params, &[], &[base, derived, stride, desc]);
253}
254
255/// `check_type` becomes `__rucc_check_type(pointer, size, type, descriptor)`.
256///
257/// The one check with a type number on it, and the number is the same one [`judgement`] passes for
258/// the same reason: what the store wrote and what the read asks about have to be written in one
259/// vocabulary or they cannot be compared.
260///
261/// The descriptor says J1 rather than a judgement of its own. The type plane is one of the planes
262/// document 04 section 4.4's first judgement names, so a read the plane refused is an access the
263/// planes did not permit, which is the sentence the reporter already prints.
264fn typed(
265    func: &mut Func,
266    names: &mut Interner,
267    word: Type,
268    numbers: &HashMap<Meta, u32>,
269    table: &mut Vec<Descriptor>,
270    inst: Inst,
271) {
272    let [_capability, pointer] = func[func[inst].args] else { return };
273    let Extra::Mem(mem) = func[inst].extra else { return };
274    let size = func[mem].size;
275    let Some(node) = func[mem].tbaa else { return };
276    let Some(&number) = numbers.get(&node) else { return };
277
278    let row = Descriptor {
279        judgement: ACCESS,
280        class: 0,
281        // Saturating, for the reason [`bounds`] gives about a report of a width that does not fit.
282        size: u16::try_from(size).unwrap_or(u16::MAX),
283    };
284    let desc = record(func, names, table, inst, row);
285    let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
286    let small = Type::int(32);
287    let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
288    let params = &[Type::PTR, word, small, Type::PTR];
289    call(func, names, inst, "__rucc_check_type", params, &[], &[pointer, bytes, ty, desc]);
290}
291
292/// `check_init` becomes `__rucc_check_init(pointer, size, descriptor)`.
293///
294/// No type number, because the plane it asks holds no types: one bit per byte, and the bit says
295/// whether anything was ever stored there. So the call is the shape [`bounds`] has rather than the
296/// shape [`typed`] has, and the size is the access's own width for the reason given there.
297///
298/// The descriptor says J1, as the type plane's does. The init plane is one of the planes document
299/// 04 section 4.4's first judgement names, so a read the plane refused is an access the planes did
300/// not permit, and that is already the sentence the reporter prints.
301fn began(
302    func: &mut Func,
303    names: &mut Interner,
304    word: Type,
305    table: &mut Vec<Descriptor>,
306    inst: Inst,
307) {
308    let [_capability, pointer] = func[func[inst].args] else { return };
309    let Extra::Mem(mem) = func[inst].extra else { return };
310    let size = func[mem].size;
311
312    let row = Descriptor {
313        judgement: ACCESS,
314        class: 0,
315        // Saturating, for the reason [`bounds`] gives about a report of a width that does not fit.
316        size: u16::try_from(size).unwrap_or(u16::MAX),
317    };
318    let desc = record(func, names, table, inst, row);
319    let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
320    let params = &[Type::PTR, word, Type::PTR];
321    call(func, names, inst, "__rucc_check_init", params, &[], &[pointer, bytes, desc]);
322}
323
324/// The two numbers in the one word the runtime reads them out of.
325///
326/// `rucc_safe_rt::restrict::tag` is the other half of this and the two have to agree, so the
327/// packing is written down in both places and tested in both. The clique is the high half and the
328/// base is the low one, which puts the number that identifies the scope where a reader of a hex
329/// dump will see it first.
330fn tag(clique: u16, base: u16) -> u32 {
331    (u32::from(clique) << 16) | u32::from(base)
332}
333
334/// `check_restrict_read` and `check_restrict_write` become
335/// `__rucc_check_restrict(pointer, size, tag, write, descriptor)`.
336///
337/// One function for both, because which of them it was is the fourth argument and nothing else.
338/// The runtime needs to know whether the access wrote because two reads of one byte through two
339/// `restrict` pointers are not a violation of anything: the contract is about modification, so the
340/// pair is refused only when at least one half of it wrote.
341///
342/// The scope is not an argument. The runtime finds it from the clique in the tag, walking the
343/// blocks this thread is inside until it reaches the innermost one with that clique, which is what
344/// makes a recursive function's second activation ask about its own promise and not its caller's.
345fn promised(
346    func: &mut Func,
347    names: &mut Interner,
348    word: Type,
349    table: &mut Vec<Descriptor>,
350    inst: Inst,
351    write: bool,
352) {
353    let [pointer] = func[func[inst].args] else { return };
354    let Extra::Mem(mem) = func[inst].extra else { return };
355    let size = func[mem].size;
356    let named = func[mem].restrict;
357
358    let row = Descriptor {
359        judgement: RESTRICT,
360        class: 0,
361        // Saturating, for the reason [`bounds`] gives about a report of a width that does not fit.
362        size: u16::try_from(size).unwrap_or(u16::MAX),
363    };
364    let desc = record(func, names, table, inst, row);
365    let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
366    let small = Type::int(32);
367    let which =
368        konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
369    let wrote = konst(func, inst, Imm::int(i128::from(u8::from(write)), small), small);
370    let params = &[Type::PTR, word, small, small, Type::PTR];
371    let args = &[pointer, bytes, which, wrote, desc];
372    call(func, names, inst, "__rucc_check_restrict", params, &[], args);
373}
374
375/// `restrict_enter` becomes `__rucc_restrict_enter(scope, tag)`.
376///
377/// No descriptor, for the reason [`judgement`] gives about a plane write: opening a block refuses
378/// nothing, so there is no failure to describe. The base half of the tag is how many pointers the
379/// block declares rather than which of them this is, since the runtime has to know how much of the
380/// slot to clear before the block starts recording into it.
381fn opened(func: &mut Func, names: &mut Interner, inst: Inst) {
382    let [scope] = func[func[inst].args] else { return };
383    let Extra::Mem(mem) = func[inst].extra else { return };
384    let named = func[mem].restrict;
385    let small = Type::int(32);
386    let which =
387        konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
388    call(func, names, inst, "__rucc_restrict_enter", &[Type::PTR, small], &[], &[scope, which]);
389}
390
391/// `restrict_leave` becomes `__rucc_restrict_leave(scope)`.
392///
393/// The slot alone, and no numbers, because closing a block is a matter of putting back whatever it
394/// was inside and the slot already says what that was.
395fn closed(func: &mut Func, names: &mut Interner, inst: Inst) {
396    let [scope] = func[func[inst].args] else { return };
397    call(func, names, inst, "__rucc_restrict_leave", &[Type::PTR], &[], &[scope]);
398}
399
400/// `meta_type` becomes `__rucc_meta_type(pointer, size, type)`.
401///
402/// No descriptor, and it is the only thing here with a payload that has none. A plane write refuses
403/// nothing and reports nothing: it records the fact that the check of the same name will later ask
404/// about, so there is no failure for a descriptor to describe.
405///
406/// The type is a number rather than a node, and `crate::plane` is where the number comes from and
407/// why it is a hash of the type's name. It travels in thirty two bits because the plane holds
408/// thirty two bits per byte of program memory, which is document 05 section 5.2.3's measurement and
409/// not a choice made here.
410fn judgement(
411    func: &mut Func,
412    names: &mut Interner,
413    word: Type,
414    numbers: &HashMap<Meta, u32>,
415    inst: Inst,
416) {
417    let [pointer, length] = func[func[inst].args] else { return };
418    let Extra::Node(node) = func[inst].extra else { return };
419    let Some(&number) = numbers.get(&node) else { return };
420    let bytes = fitted(func, inst, length, word);
421    let small = Type::int(32);
422    let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
423    let params = &[Type::PTR, word, small];
424    call(func, names, inst, "__rucc_meta_type", params, &[], &[pointer, bytes, ty]);
425}
426
427/// `meta_type_copy` becomes `__rucc_meta_type_copy(destination, source, length)`.
428///
429/// No descriptor and no type number, for the two reasons [`judgement`] gives: a plane write refuses
430/// nothing, and what the copied bytes are is not something the compiler knows. The runtime reads the
431/// entries over the source and writes them over the destination, so the type travels without
432/// anybody here having to name it.
433fn carriage(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
434    let [to, from, length] = func[func[inst].args] else { return };
435    let bytes = fitted(func, inst, length, word);
436    let params = &[Type::PTR, Type::PTR, word];
437    call(func, names, inst, "__rucc_meta_type_copy", params, &[], &[to, from, bytes]);
438}
439
440/// `meta_init` becomes `__rucc_meta_init(pointer, size)`.
441///
442/// No descriptor, for the reason [`judgement`] has none, and no type either. The init plane holds
443/// one bit per byte and the bit says whether anything was ever stored there, so a range is the whole
444/// of what a store has to say about it and there is nothing else to pass.
445fn written(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
446    let [pointer, length] = func[func[inst].args] else { return };
447    let bytes = fitted(func, inst, length, word);
448    let params = &[Type::PTR, word];
449    call(func, names, inst, "__rucc_meta_init", params, &[], &[pointer, bytes]);
450}
451
452/// `meta_init_copy` becomes `__rucc_meta_init_copy(destination, source, length)`.
453///
454/// The same shape as [`carriage`] and for the same reason: a copy writes no values of its own, so
455/// whether a destination byte holds anything is whether the byte it came from did, and the plane
456/// over the source is the only place that is written down.
457fn carried(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
458    let [to, from, length] = func[func[inst].args] else { return };
459    let bytes = fitted(func, inst, length, word);
460    let params = &[Type::PTR, Type::PTR, word];
461    call(func, names, inst, "__rucc_meta_init_copy", params, &[], &[to, from, bytes]);
462}
463
464/// `cap_extent` becomes `__rucc_extent(pointer, want)`, and `cap_extent_back` the backward one.
465///
466/// No descriptor, and these two are the only ones of these that have none. The other four are
467/// judgements and a judgement that refuses has to say what it refused. These decide nothing: they
468/// are the question section 7.4 asks before a loop so that the loop can be split, once for a walk
469/// that goes up and once for a walk that goes down, the answer is a number, and there is no failure
470/// to describe.
471///
472/// One function for both because the two differ in the name they call and in nothing else. The
473/// operands are the same three, the result is the same count, and the width the count comes back in
474/// is handled the same way.
475fn extent(func: &mut Func, names: &mut Interner, word: Type, inst: Inst, called: &str) {
476    let [_capability, address, want] = func[func[inst].args] else { return };
477    let asked = fitted(func, inst, want, word);
478    let result = func[inst].results().next().expect("an extent query produces one value");
479    let ty = func[result].ty;
480    let params = &[Type::PTR, word];
481    if ty == word {
482        call(func, names, inst, called, params, &[word], &[address, asked]);
483        return;
484    }
485    // The count came out in a width that is not the target's, for the reason [`fitted`] gives about
486    // the operand going the other way. The call is made beside the instruction in the width the
487    // runtime declares and the instruction itself becomes the conversion back, so that everything
488    // reading its result still reads a value of the type it had.
489    let made = calling(func, names, called, params, &[word], &[address, asked]);
490    let holder = func.create_inst(made, &[word], func.span(inst));
491    func.insert_before(holder, inst);
492    let got = func[holder].results().next().expect("a call returning one value produces one");
493    let opcode = if word.bits() > ty.bits() { Opcode::Trunc } else { Opcode::ZExt };
494    let args = func.push_values(&[got]);
495    func[inst] = InstData { args, ..InstData::new(opcode) };
496}
497
498/// Writes a descriptor down and gives back the address the call passes.
499///
500/// The `global_addr` goes in front of the check rather than at the top of the function, because the
501/// back end turns it into one `lea` off the instruction pointer and putting it beside its use is
502/// what keeps the value from being live across everything in between.
503fn record(
504    func: &mut Func,
505    names: &mut Interner,
506    table: &mut Vec<Descriptor>,
507    inst: Inst,
508    row: Descriptor,
509) -> Value {
510    let name = names.intern(&label(table.len()));
511    table.push(row);
512    let span = func.span(inst);
513    let data = InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) };
514    let made = func.create_inst(data, &[Type::PTR], span);
515    func.insert_before(made, inst);
516    func[made].results().next().expect("an address created with one result has one")
517}
518
519/// What the descriptor in position `index` is called.
520fn label(index: usize) -> String {
521    format!("{DESCRIPTOR}_{index}")
522}
523
524/// Puts an integer constant in front of `inst` and gives back what it produced.
525fn konst(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
526    let span = func.span(inst);
527    let extra = Extra::Imm(func.add_imm(imm));
528    let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
529    func.insert_before(made, inst);
530    func[made].results().next().expect("a constant created with one result has one")
531}
532
533/// Turns `inst` into a call of `routine` with those arguments, in place.
534///
535/// In place rather than as a new instruction beside it, because the check is already where it has
536/// to be: in front of the access for the two access checks and behind the arithmetic for the
537/// derivation one. Moving it would be a chance to get that wrong.
538fn call(
539    func: &mut Func,
540    names: &mut Interner,
541    inst: Inst,
542    routine: &str,
543    params: &[Type],
544    returns: &[Type],
545    args: &[Value],
546) {
547    let made = calling(func, names, routine, params, returns, args);
548    let data = &mut func[inst];
549    data.opcode = made.opcode;
550    data.args = made.args;
551    data.extra = made.extra;
552    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
553}
554
555/// A call to `routine` with those arguments, not yet anywhere.
556///
557/// Separate from [`call`] because the extent query is the one rewrite that sometimes needs the call
558/// beside the instruction rather than in place of it, and building the signature and the callee is
559/// the part the two have in common.
560fn calling(
561    func: &mut Func,
562    names: &mut Interner,
563    routine: &str,
564    params: &[Type],
565    returns: &[Type],
566    args: &[Value],
567) -> InstData {
568    let sig = func.add_signature(Signature::new().with_params(params).with_returns(returns));
569    let callee = names.intern(routine);
570    // Nothing is passed past the last named parameter, so there is nothing for the ABI to say
571    // about the arguments the signature does not name.
572    let varargs = func.push_abis(&[]);
573    let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
574    let args = func.push_values(args);
575    InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) }
576}
577
578/// Adds one descriptor to the module as a variable in the shared section.
579///
580/// Internal, so the linker never has to resolve the name and two objects in a link do not collide
581/// over it. Constant, because nothing writes a descriptor after the compiler has. Eight byte
582/// aligned and sixteen bytes long, because the runtime reads it as a `#[repr(C)]` structure with a
583/// `u64` in it, and because that is what makes the section as a whole a packed array of them.
584fn emit(module: &mut Module, names: &mut Interner, index: usize, row: Descriptor) {
585    let byte = Type::int(8);
586    let half = Type::int(16);
587    let judgement = module.add_imm(Imm::int(i128::from(row.judgement), byte));
588    let class = module.add_imm(Imm::int(i128::from(row.class), byte));
589    let size = module.add_imm(Imm::int(i128::from(row.size), half));
590    let image = [
591        Datum::Scalar { ty: byte, value: judgement },
592        Datum::Scalar { ty: byte, value: class },
593        Datum::Scalar { ty: half, value: size },
594        // Four bytes the C layout puts in front of the `u64`, and then the eight of the program
595        // counter, which nothing fills in yet. Both are zero and both are written out rather than
596        // left off, because the descriptor after this one has to start sixteen bytes along.
597        Datum::Zero(4),
598        Datum::Zero(8),
599    ];
600    let init = module.push_data(&image);
601    let mut global = Global::new(names.intern(&label(index)), WIDTH, 8);
602    global.linkage = Linkage::Internal;
603    global.constant = true;
604    global.section = Some(names.intern(SECTION));
605    global.init = Some(init);
606    module.add_global(global);
607}
608
609#[cfg(test)]
610mod tests {
611    use rucc_ir::{
612        Builder, MemInfo, MemOrder, MetaNode, Restrict, TbaaNode, print_func, verify_func,
613    };
614    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
615
616    use super::*;
617    use crate::{Plane, Promise, Subobject, insert};
618
619    fn target() -> TargetInfo {
620        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
621    }
622
623    /// A module holding one function that loads through its parameter, with checks already in.
624    fn checked(names: &mut Interner) -> Module {
625        let i32_ = Type::int(32);
626        let mut func = Func::new(
627            names.intern("read"),
628            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
629        );
630        let entry = func.create_block();
631        let p = func.append_param(entry, Type::PTR);
632
633        let info = MemInfo {
634            size: 4,
635            align: 4,
636            order: MemOrder::NotAtomic,
637            tbaa: None,
638            owns: 0,
639            restrict: Restrict::NONE,
640        };
641        let mut b = Builder::new(&mut func, entry);
642        let args = b.func().push_values(&[p]);
643        let extra = Extra::Mem(b.func().add_mem(info));
644        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
645        b.ret(&[loaded]);
646
647        insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
648        let mut module = Module::new(names.intern("read.c"), &target());
649        module.add_func(func);
650        module
651    }
652
653    /// The same function [`checked`] builds, over an access that may assume nothing about where
654    /// it starts, which is what a member of a packed record is.
655    fn unaligned(names: &mut Interner) -> Module {
656        let i32_ = Type::int(32);
657        let mut func = Func::new(
658            names.intern("read"),
659            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
660        );
661        let entry = func.create_block();
662        let p = func.append_param(entry, Type::PTR);
663
664        let info = MemInfo {
665            size: 4,
666            align: 1,
667            order: MemOrder::NotAtomic,
668            tbaa: None,
669            owns: 0,
670            restrict: Restrict::NONE,
671        };
672        let mut b = Builder::new(&mut func, entry);
673        let args = b.func().push_values(&[p]);
674        let extra = Extra::Mem(b.func().add_mem(info));
675        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
676        b.ret(&[loaded]);
677
678        insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
679        let mut module = Module::new(names.intern("read.c"), &target());
680        module.add_func(func);
681        module
682    }
683
684    /// A plane for a function that stores nothing, and the numbering that goes with it.
685    ///
686    /// Every function in these tests reads or derives and none of them stores, so there is nothing
687    /// to record and the entries are never named. What the two are for is that [`crate::insert`]
688    /// and [`calls`] take them whether or not the function has a store in it.
689    fn planeless(names: &mut Interner) -> (Plane, HashMap<Meta, u32>) {
690        let mut module = Module::new(names.intern("planeless.c"), &target());
691        let plane = Plane::build(&mut module);
692        let numbers = plane::numbers(&module, names);
693        (plane, numbers)
694    }
695
696    /// A module with one function that copies a fixed number of bytes, with the plane write in.
697    fn copied(names: &mut Interner) -> Module {
698        let mut func =
699            Func::new(names.intern("move"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
700        let entry = func.create_block();
701        let to = func.append_param(entry, Type::PTR);
702        let from = func.append_param(entry, Type::PTR);
703
704        let info = MemInfo {
705            size: 24,
706            align: 8,
707            order: MemOrder::NotAtomic,
708            tbaa: None,
709            owns: 0,
710            restrict: Restrict::NONE,
711        };
712        let mut b = Builder::new(&mut func, entry);
713        let args = b.func().push_values(&[to, from]);
714        let extra = Extra::Mem(b.func().add_mem(info));
715        b.inst(InstData { args, extra, ..InstData::new(Opcode::Memcpy) }, &[]);
716        b.ret(&[]);
717
718        insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
719        let mut module = Module::new(names.intern("move.c"), &target());
720        module.add_func(func);
721        module
722    }
723
724    /// A module with one function that stores through its parameter, with the plane writes in.
725    ///
726    /// The plane is this module's rather than [`planeless`]'s, because the store records into it
727    /// and a judgement naming an entry another module holds is a judgement [`judgement`] leaves
728    /// alone.
729    fn stored(names: &mut Interner) -> Module {
730        let mut module = Module::new(names.intern("write.c"), &target());
731        let plane = Plane::build(&mut module);
732
733        let i64_ = Type::int(64);
734        let mut func =
735            Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
736        let entry = func.create_block();
737        let p = func.append_param(entry, Type::PTR);
738        let v = func.append_param(entry, i64_);
739
740        let info = MemInfo {
741            size: 8,
742            align: 8,
743            order: MemOrder::NotAtomic,
744            tbaa: None,
745            owns: 0,
746            restrict: Restrict::NONE,
747        };
748        let mut b = Builder::new(&mut func, entry);
749        let args = b.func().push_values(&[v, p]);
750        let extra = Extra::Mem(b.func().add_mem(info));
751        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
752        b.ret(&[]);
753
754        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
755        module.add_func(func);
756        module
757    }
758
759    /// A module with one function that reads through its parameter as an `int`, checks in.
760    ///
761    /// The aliasing node is built by hand rather than by the front end, since this crate cannot
762    /// depend on the one that builds the tree. What matters is the shape: a root and one type under
763    /// it, which is what `rucc_lower::aliasing` produces for a translation unit that reads an `int`.
764    fn asking_the_plane(names: &mut Interner) -> Module {
765        let mut module = Module::new(names.intern("read.c"), &target());
766        let root = names.intern("char");
767        let root =
768            module.add_meta(MetaNode::Tbaa(TbaaNode { name: root, parent: None, offset: 0 }));
769        let int = names.intern("int");
770        let int =
771            module.add_meta(MetaNode::Tbaa(TbaaNode { name: int, parent: Some(root), offset: 0 }));
772        let plane = Plane::build(&mut module);
773
774        let i32_ = Type::int(32);
775        let mut func = Func::new(
776            names.intern("read"),
777            Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
778        );
779        let entry = func.create_block();
780        let p = func.append_param(entry, Type::PTR);
781        let info = MemInfo {
782            size: 4,
783            align: 4,
784            order: MemOrder::NotAtomic,
785            tbaa: Some(int),
786            owns: 0,
787            restrict: Restrict::NONE,
788        };
789        let mut b = Builder::new(&mut func, entry);
790        let args = b.func().push_values(&[p]);
791        let extra = Extra::Mem(b.func().add_mem(info));
792        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
793        b.ret(&[loaded]);
794
795        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
796        module.add_func(func);
797        module
798    }
799
800    /// One instruction that says something and produces nothing, with a payload or without one.
801    fn marker(b: &mut Builder<'_>, opcode: Opcode, info: Option<MemInfo>, on: &[Value]) {
802        let args = b.func().push_values(on);
803        let extra = match info {
804            Some(info) => Extra::Mem(b.func().add_mem(info)),
805            None => Extra::None,
806        };
807        b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
808    }
809
810    /// A module with one function that reaches two objects through two `restrict` pointers.
811    ///
812    /// Built by hand rather than by [`insert`], because nothing puts these in yet: the pass that
813    /// does is the other half of this and it is not written. What this file is about is the calls,
814    /// so what the function has to be is the shape the verifier believes.
815    fn promising(names: &mut Interner) -> Module {
816        let i32_ = Type::int(32);
817        let mut func = Func::new(
818            names.intern("kernel"),
819            Signature::new().with_params(&[Type::PTR, Type::PTR]),
820        );
821        let entry = func.create_block();
822        let to = func.append_param(entry, Type::PTR);
823        let from = func.append_param(entry, Type::PTR);
824
825        let empty = MemInfo {
826            size: 0,
827            align: 1,
828            order: MemOrder::NotAtomic,
829            tbaa: None,
830            owns: 0,
831            restrict: Restrict::NONE,
832        };
833        // The slot the block keeps its record in, whose size is `rucc_safe_rt::restrict::Scope`.
834        let slot = MemInfo { size: 112, align: 8, ..empty };
835        let mut b = Builder::new(&mut func, entry);
836        let extra = Extra::Mem(b.func().add_mem(slot));
837        let scope = b.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR);
838
839        // Two bases of one clique, which is what a function with two `restrict` parameters gets.
840        let read =
841            MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 2 }, ..empty };
842        let writ =
843            MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 1 }, ..empty };
844        let opening = MemInfo { restrict: Restrict { clique: 1, base: 2 }, ..slot };
845        marker(&mut b, Opcode::RestrictEnter, Some(opening), &[scope]);
846        marker(&mut b, Opcode::CheckRestrictRead, Some(read), &[from]);
847        let args = b.func().push_values(&[from]);
848        let extra = Extra::Mem(b.func().add_mem(read));
849        let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
850        marker(&mut b, Opcode::CheckRestrictWrite, Some(writ), &[to]);
851        let args = b.func().push_values(&[loaded, to]);
852        let extra = Extra::Mem(b.func().add_mem(writ));
853        b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
854        marker(&mut b, Opcode::RestrictLeave, None, &[scope]);
855        b.ret(&[]);
856
857        let mut module = Module::new(names.intern("kernel.c"), &target());
858        module.add_func(func);
859        module
860    }
861
862    #[test]
863    fn a_restrict_check_becomes_the_call_that_says_which_pointer_reached_where() {
864        // Two descriptors, one per check, because each of them is a judgement that can refuse and a
865        // judgement that refuses has to say what it refused. The two markers have none, for the
866        // reason a plane write has none: opening and closing a block decides nothing.
867        let mut names = Interner::new();
868        let mut module = promising(&mut names);
869        assert_eq!(lower(&mut module, &mut names), 2);
870
871        let id = module.funcs().next().expect("the module has one function");
872        assert_eq!(
873            print_func(&module, &module[id], &names),
874            "func @kernel(ptr, ptr), linkage(external) {\n\
875             block0(%0: ptr, %1: ptr):\n    \
876             %2 = alloca, size 112, align 8\n    \
877             %3 = iconst.i32 65538\n    \
878             call @__rucc_restrict_enter(%2, %3) : (ptr, i32)\n    \
879             %4 = global_addr @__rucc_safety_desc_0\n    \
880             %5 = iconst.i64 4\n    \
881             %6 = iconst.i32 65538\n    \
882             %7 = iconst.i32 0\n    \
883             call @__rucc_check_restrict(%1, %5, %6, %7, %4) : (ptr, i64, i32, i32, ptr)\n    \
884             %8 = load.i32 %1, size 4, align 4, restrict(1, 2)\n    \
885             %9 = global_addr @__rucc_safety_desc_1\n    \
886             %10 = iconst.i64 4\n    \
887             %11 = iconst.i32 65537\n    \
888             %12 = iconst.i32 1\n    \
889             call @__rucc_check_restrict(%0, %10, %11, %12, %9) : (ptr, i64, i32, i32, ptr)\n    \
890             store %8 -> %0, size 4, align 4, restrict(1, 1)\n    \
891             call @__rucc_restrict_leave(%2) : (ptr)\n    \
892             return\n\
893             }\n"
894        );
895
896        if let Err(errors) = verify_func(&module, &module[id], &names) {
897            panic!("that was expected to be believed: {errors:#?}");
898        }
899    }
900
901    #[test]
902    fn the_judgement_a_restrict_check_names_is_the_one_about_the_pair() {
903        // J8 rather than J1. Document 04 section 4.6 keeps this judgement out of J1 on purpose,
904        // because a single access is never the violation: what is refused is a pair of them, and
905        // the reporter prints a different sentence for it.
906        let mut names = Interner::new();
907        let mut module = promising(&mut names);
908        lower(&mut module, &mut names);
909
910        let rows: Vec<u8> = module
911            .globals()
912            .map(|id| {
913                let init = module[id].init.expect("a descriptor is a definition");
914                match module[init][0] {
915                    Datum::Scalar { value, .. } => {
916                        u8::try_from(module[value].bits()).expect("a judgement is one byte")
917                    }
918                    _ => panic!("a descriptor starts with its judgement"),
919                }
920            })
921            .collect();
922        assert_eq!(rows, [RESTRICT, RESTRICT]);
923    }
924
925    #[test]
926    fn the_two_numbers_are_packed_the_way_the_runtime_unpacks_them() {
927        // The other half of this is `rucc_safe_rt::restrict::tag`, and the two agree by both being
928        // written down rather than by one calling the other, since this crate does not depend on
929        // the runtime. A clique in the low half and a base in the high one would be read as a
930        // scope nobody opened, which the runtime would pass and nobody would notice.
931        assert_eq!(tag(1, 2), 0x0001_0002);
932        assert_eq!(tag(0xffff, 0xffff), u32::MAX);
933        assert_eq!(tag(0, 0), 0);
934    }
935
936    #[test]
937    fn a_read_of_the_plane_becomes_the_call_that_carries_the_type_asked_about() {
938        // Four rows rather than two, because a read now asks two questions of two planes and each
939        // of them is a judgement that has to say what it refused. The type travels as the same
940        // number a store of the same type would have recorded, which is the only way the two can be
941        // compared, and the init question carries no type at all.
942        let mut names = Interner::new();
943        let mut module = asking_the_plane(&mut names);
944        assert_eq!(lower(&mut module, &mut names), 4);
945
946        // The printer writes an `i32` immediate as a signed number and the identifier is a hash
947        // that uses the whole width, so what appears is the same bits read the other way round.
948        let number = i32::from_ne_bytes(plane::identifier("int").to_ne_bytes());
949        let id = module.funcs().next().expect("the module has one function");
950        assert_eq!(
951            print_func(&module, &module[id], &names),
952            format!(
953                "func @read(ptr) -> i32, linkage(external) {{\n\
954                 block0(%0: ptr):\n    \
955                 %1 = global_addr @__rucc_safety_desc_0\n    \
956                 %2 = iconst.i64 4\n    \
957                 %3 = iconst.i64 4\n    \
958                 call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n    \
959                 %4 = global_addr @__rucc_safety_desc_1\n    \
960                 call @__rucc_check_live(%0, %4) : (ptr, ptr)\n    \
961                 %5 = global_addr @__rucc_safety_desc_2\n    \
962                 %6 = iconst.i64 4\n    \
963                 %7 = iconst.i32 {number}\n    \
964                 call @__rucc_check_type(%0, %6, %7, %5) : (ptr, i64, i32, ptr)\n    \
965                 %8 = global_addr @__rucc_safety_desc_3\n    \
966                 %9 = iconst.i64 4\n    \
967                 call @__rucc_check_init(%0, %9, %8) : (ptr, i64, ptr)\n    \
968                 %10 = load.i32 %0, size 4, align 4, tbaa !1\n    \
969                 return %10\n\
970                 }}\n"
971            )
972        );
973
974        if let Err(errors) = verify_func(&module, &module[id], &names) {
975            panic!("that was expected to be believed: {errors:#?}");
976        }
977    }
978
979    #[test]
980    fn the_judgement_a_type_check_names_is_the_one_about_the_planes() {
981        // J1 rather than a judgement of its own. Document 04 section 4.4's first judgement is an
982        // access the capability, the planes or the alignment did not permit, and both the type
983        // plane and the init plane are planes, so that is the sentence the reporter should print
984        // for either of them.
985        let mut names = Interner::new();
986        let mut module = asking_the_plane(&mut names);
987        lower(&mut module, &mut names);
988
989        let rows: Vec<u8> = module
990            .globals()
991            .map(|id| {
992                let init = module[id].init.expect("a descriptor is a definition");
993                match module[init][0] {
994                    Datum::Scalar { value, .. } => {
995                        u8::try_from(module[value].bits()).expect("a judgement is one byte")
996                    }
997                    _ => panic!("a descriptor starts with its judgement"),
998                }
999            })
1000            .collect();
1001        assert_eq!(rows, [ACCESS, ACCESS, ACCESS, ACCESS]);
1002    }
1003
1004    #[test]
1005    fn a_store_becomes_the_calls_that_record_what_it_wrote() {
1006        // Two operands and no descriptor for the init plane's call, against three for the type
1007        // plane's. A type number is the one thing the two writes do not have in common: what a
1008        // store stored through is a thing the compiler has to name, and that it stored at all is
1009        // not.
1010        let mut names = Interner::new();
1011        let mut module = stored(&mut names);
1012        assert_eq!(lower(&mut module, &mut names), 2);
1013
1014        let id = module.funcs().next().expect("the module has one function");
1015        let printed = print_func(&module, &module[id], &names);
1016        assert!(
1017            printed.contains("call @__rucc_meta_type(%0, %6, %7) : (ptr, i64, i32)\n"),
1018            "{printed}"
1019        );
1020        assert!(printed.contains("call @__rucc_meta_init(%0, %8) : (ptr, i64)\n"), "{printed}");
1021
1022        if let Err(errors) = verify_func(&module, &module[id], &names) {
1023            panic!("that was expected to be believed: {errors:#?}");
1024        }
1025    }
1026
1027    #[test]
1028    fn a_copy_becomes_the_calls_that_move_the_planes_across() {
1029        // Three operands each and no descriptor. A plane write refuses nothing, and neither what
1030        // the copied bytes are nor whether anything ever wrote them is a thing the compiler knows,
1031        // so there is no type number and no length beyond the range: both calls read the entries
1032        // over the source and write them over the destination.
1033        let mut names = Interner::new();
1034        let mut module = copied(&mut names);
1035        assert_eq!(lower(&mut module, &mut names), 0);
1036
1037        let id = module.funcs().next().expect("the module has one function");
1038        assert_eq!(
1039            print_func(&module, &module[id], &names),
1040            "func @move(ptr, ptr), linkage(external) {\n\
1041             block0(%0: ptr, %1: ptr):\n    \
1042             memcpy %0, %1, size 24, align 8\n    \
1043             %2 = iconst.i64 24\n    \
1044             call @__rucc_meta_type_copy(%0, %1, %2) : (ptr, ptr, i64)\n    \
1045             %3 = iconst.i64 24\n    \
1046             call @__rucc_meta_init_copy(%0, %1, %3) : (ptr, ptr, i64)\n    \
1047             return\n\
1048             }\n"
1049        );
1050
1051        if let Err(errors) = verify_func(&module, &module[id], &names) {
1052            panic!("that was expected to be believed: {errors:#?}");
1053        }
1054    }
1055
1056    /// The alignment is the access's own and not its width.
1057    ///
1058    /// Two numbers that are four apiece in [`checked`] and would look alike if only one of them
1059    /// went through. The access here is four bytes wide and may assume nothing about where it
1060    /// starts, which is what the front end says about a member of a packed record, and what has
1061    /// to arrive at the runtime is four bytes and an alignment of one.
1062    #[test]
1063    fn the_alignment_that_goes_through_is_the_one_the_access_may_assume() {
1064        let mut names = Interner::new();
1065        let mut module = unaligned(&mut names);
1066        assert_eq!(lower(&mut module, &mut names), 3);
1067
1068        let id = module.funcs().next().expect("the module has one function");
1069        let printed = print_func(&module, &module[id], &names);
1070        assert!(printed.contains("%2 = iconst.i64 4\n"), "{printed}");
1071        assert!(printed.contains("%3 = iconst.i64 1\n"), "{printed}");
1072        assert!(
1073            printed.contains("call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n"),
1074            "{printed}"
1075        );
1076    }
1077
1078    #[test]
1079    fn every_check_becomes_a_call_carrying_the_descriptor_it_is_described_by() {
1080        let mut names = Interner::new();
1081        let mut module = checked(&mut names);
1082        assert_eq!(lower(&mut module, &mut names), 3);
1083
1084        let id = module.funcs().next().expect("the module has one function");
1085        assert_eq!(
1086            print_func(&module, &module[id], &names),
1087            "func @read(ptr) -> i32, linkage(external) {\n\
1088             block0(%0: ptr):\n    \
1089             %1 = global_addr @__rucc_safety_desc_0\n    \
1090             %2 = iconst.i64 4\n    \
1091             %3 = iconst.i64 4\n    \
1092             call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n    \
1093             %4 = global_addr @__rucc_safety_desc_1\n    \
1094             call @__rucc_check_live(%0, %4) : (ptr, ptr)\n    \
1095             %5 = global_addr @__rucc_safety_desc_2\n    \
1096             %6 = iconst.i64 4\n    \
1097             call @__rucc_check_init(%0, %6, %5) : (ptr, i64, ptr)\n    \
1098             %7 = load.i32 %0, size 4, align 4\n    \
1099             return %7\n\
1100             }\n"
1101        );
1102    }
1103
1104    #[test]
1105    fn the_capabilities_the_checks_were_reading_are_taken_out() {
1106        // A `cap` is a type nothing in the back end has been taught, so one left behind is a
1107        // compilation that fails rather than a value nobody reads.
1108        let mut names = Interner::new();
1109        let mut module = checked(&mut names);
1110        lower(&mut module, &mut names);
1111
1112        let id = module.funcs().next().expect("the module has one function");
1113        let func = &module[id];
1114        let left: Vec<Opcode> = func
1115            .blocks()
1116            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1117            .map(|inst| func[inst].opcode)
1118            .collect();
1119        assert!(!left.contains(&Opcode::CapOf), "{left:?}");
1120    }
1121
1122    #[test]
1123    fn what_it_produces_is_a_module_the_verifier_believes() {
1124        let mut names = Interner::new();
1125        let mut module = checked(&mut names);
1126        lower(&mut module, &mut names);
1127
1128        let id = module.funcs().next().expect("the module has one function");
1129        if let Err(errors) = verify_func(&module, &module[id], &names) {
1130            panic!("that was expected to be believed: {errors:#?}");
1131        }
1132    }
1133
1134    #[test]
1135    fn the_section_is_one_descriptor_per_check_and_nothing_else() {
1136        // The runtime is handed one address and dereferences it, so what makes the section a table
1137        // is only that every variable in it is the same sixteen bytes long and eight aligned. That
1138        // is what `--emit=safety-summary` will divide by, so it is checked here rather than
1139        // assumed.
1140        let mut names = Interner::new();
1141        let mut module = checked(&mut names);
1142        let rows = lower(&mut module, &mut names);
1143
1144        let globals: Vec<_> = module.globals().collect();
1145        assert_eq!(globals.len(), rows);
1146        for (index, id) in globals.iter().enumerate() {
1147            let desc = &module[*id];
1148            assert_eq!(names.resolve(desc.name), label(index));
1149            assert_eq!(
1150                names.resolve(desc.section.expect("a descriptor names its section")),
1151                SECTION
1152            );
1153            assert_eq!(desc.linkage, Linkage::Internal);
1154            assert!(desc.constant);
1155            assert_eq!(desc.align, 8);
1156            assert_eq!(desc.size, WIDTH);
1157
1158            // The image has to add up to the size, or the descriptor after this one starts in the
1159            // middle of this one.
1160            let init = desc.init.expect("a descriptor is a definition");
1161            let written: u64 = module[init].iter().map(|datum| datum.size(&module)).sum();
1162            assert_eq!(written, WIDTH);
1163        }
1164    }
1165
1166    #[test]
1167    fn the_judgement_a_descriptor_names_is_the_one_the_check_decides() {
1168        // A report that said J1 where the program derived a pointer would send somebody looking
1169        // at the wrong line, so the two rows a derivation produces are checked by hand.
1170        let mut names = Interner::new();
1171        let mut func = Func::new(
1172            names.intern("walk"),
1173            Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
1174        );
1175        let entry = func.create_block();
1176        let p = func.append_param(entry, Type::PTR);
1177        let n = func.append_param(entry, Type::int(64));
1178        let mut b = Builder::new(&mut func, entry);
1179        let args = b.func().push_values(&[p, n]);
1180        let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1181        b.ret(&[moved]);
1182        let (plane, numbers) = planeless(&mut names);
1183        insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
1184
1185        let mut table = Vec::new();
1186        calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1187        assert_eq!(table, [Descriptor { judgement: DERIVE, class: 0, size: 0 }]);
1188    }
1189
1190    #[test]
1191    fn a_check_over_a_length_the_program_worked_out_passes_that_length_along() {
1192        // Section 7.4's hoisted check. The number of bytes is the third operand rather than the
1193        // payload's size, so what the call is handed is the value and not a constant, and the
1194        // descriptor says zero because there is no one width to report.
1195        let mut names = Interner::new();
1196        let mut func = Func::new(
1197            names.intern("sweep"),
1198            Signature::new().with_params(&[Type::PTR, Type::int(64)]),
1199        );
1200        let entry = func.create_block();
1201        let p = func.append_param(entry, Type::PTR);
1202        let n = func.append_param(entry, Type::int(64));
1203        let info = MemInfo {
1204            size: 4,
1205            align: 4,
1206            order: MemOrder::NotAtomic,
1207            tbaa: None,
1208            owns: 0,
1209            restrict: Restrict::NONE,
1210        };
1211        let mut b = Builder::new(&mut func, entry);
1212        let of = b.unary(Opcode::CapOf, p, Type::CAP);
1213        let args = b.func().push_values(&[of, p, n]);
1214        let extra = Extra::Mem(b.func().add_mem(info));
1215        b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1216        b.ret(&[]);
1217
1218        let mut table = Vec::new();
1219        let numbers = planeless(&mut names).1;
1220        calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1221        assert_eq!(table, [Descriptor { judgement: ACCESS, class: 0, size: 0 }]);
1222
1223        let mut module = Module::new(names.intern("sweep.c"), &target());
1224        module.add_func(func);
1225        let id = module.funcs().next().expect("the module has one function");
1226        assert_eq!(
1227            print_func(&module, &module[id], &names),
1228            "func @sweep(ptr, i64), linkage(external) {\n\
1229             block0(%0: ptr, %1: i64):\n    \
1230             %2 = global_addr @__rucc_safety_desc_0\n    \
1231             %3 = iconst.i64 1\n    \
1232             call @__rucc_check_bounds(%0, %1, %3, %2) : (ptr, i64, i64, ptr)\n    \
1233             return\n\
1234             }\n"
1235        );
1236    }
1237
1238    #[test]
1239    fn a_length_wider_than_the_word_is_cut_down_to_it() {
1240        // The pass that works out how many bytes a loop covers has no target to ask, so on a
1241        // thirty two bit target it hands over a number that does not fit the runtime's own
1242        // parameter. What comes out is a truncation rather than a call the verifier refuses.
1243        let mut names = Interner::new();
1244        let mut func = Func::new(
1245            names.intern("sweep"),
1246            Signature::new().with_params(&[Type::PTR, Type::int(64)]),
1247        );
1248        let entry = func.create_block();
1249        let p = func.append_param(entry, Type::PTR);
1250        let n = func.append_param(entry, Type::int(64));
1251        let info = MemInfo {
1252            size: 4,
1253            align: 4,
1254            order: MemOrder::NotAtomic,
1255            tbaa: None,
1256            owns: 0,
1257            restrict: Restrict::NONE,
1258        };
1259        let mut b = Builder::new(&mut func, entry);
1260        let of = b.unary(Opcode::CapOf, p, Type::CAP);
1261        let args = b.func().push_values(&[of, p, n]);
1262        let extra = Extra::Mem(b.func().add_mem(info));
1263        b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1264        b.ret(&[]);
1265
1266        let mut table = Vec::new();
1267        let numbers = planeless(&mut names).1;
1268        calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
1269        let opcodes: Vec<Opcode> = func
1270            .blocks()
1271            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1272            .map(|inst| func[inst].opcode)
1273            .collect();
1274        assert!(opcodes.contains(&Opcode::Trunc), "{opcodes:?}");
1275    }
1276
1277    /// A function that asks how many bytes its parameter covers, in the width the caller names.
1278    ///
1279    /// The result is returned so that something reads it, because a query nobody reads would be
1280    /// removed by any pass that ran and this test is about what the value it produces turns into.
1281    fn asking(names: &mut Interner, ty: Type) -> Func {
1282        let mut func = Func::new(
1283            names.intern("cover"),
1284            Signature::new().with_params(&[Type::PTR, ty]).with_returns(&[ty]),
1285        );
1286        let entry = func.create_block();
1287        let p = func.append_param(entry, Type::PTR);
1288        let want = func.append_param(entry, ty);
1289        let mut b = Builder::new(&mut func, entry);
1290        let of = b.unary(Opcode::CapOf, p, Type::CAP);
1291        let args = b.func().push_values(&[of, p, want]);
1292        let got = b.value(InstData { args, ..InstData::new(Opcode::CapExtent) }, ty);
1293        b.ret(&[got]);
1294        func
1295    }
1296
1297    #[test]
1298    fn the_extent_query_becomes_a_call_that_carries_no_descriptor() {
1299        // The one rewrite here that is not a judgement, so it writes no row and the table stays
1300        // empty. What it is for is section 7.4's split, which needs a number and not a verdict.
1301        let mut names = Interner::new();
1302        let mut func = asking(&mut names, Type::int(64));
1303
1304        let mut table = Vec::new();
1305        let numbers = planeless(&mut names).1;
1306        calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1307        assert!(table.is_empty(), "{table:?}");
1308
1309        let mut module = Module::new(names.intern("cover.c"), &target());
1310        module.add_func(func);
1311        let id = module.funcs().next().expect("the module has one function");
1312        assert_eq!(
1313            print_func(&module, &module[id], &names),
1314            "func @cover(ptr, i64) -> i64, linkage(external) {\n\
1315             block0(%0: ptr, %1: i64):\n    \
1316             %2 = call @__rucc_extent(%0, %1) : (ptr, i64) -> i64\n    \
1317             return %2\n\
1318             }\n"
1319        );
1320        if let Err(errors) = verify_func(&module, &module[id], &names) {
1321            panic!("that was expected to be believed: {errors:#?}");
1322        }
1323    }
1324
1325    #[test]
1326    fn an_extent_asked_for_in_a_width_the_target_does_not_have_is_converted_back() {
1327        // On a thirty two bit target the runtime's own parameter and return are thirty two bits
1328        // wide, and the pass that wrote the arithmetic worked in sixty four. So the limit is cut
1329        // down on the way in and the answer is widened on the way out, and everything reading the
1330        // query still reads a value of the type it had.
1331        let mut names = Interner::new();
1332        let mut func = asking(&mut names, Type::int(64));
1333
1334        let mut table = Vec::new();
1335        let numbers = planeless(&mut names).1;
1336        calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
1337        let opcodes: Vec<Opcode> = func
1338            .blocks()
1339            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1340            .map(|inst| func[inst].opcode)
1341            .collect();
1342        assert!(opcodes.contains(&Opcode::Trunc), "the limit goes in narrowed: {opcodes:?}");
1343        assert!(opcodes.contains(&Opcode::ZExt), "and the answer comes back widened: {opcodes:?}");
1344        assert!(
1345            !opcodes.contains(&Opcode::CapExtent),
1346            "with nothing left of the query: {opcodes:?}"
1347        );
1348    }
1349
1350    #[test]
1351    fn a_module_with_nothing_to_check_gets_no_section_at_all() {
1352        // An object with an empty section in it is an object that says the compiler had something
1353        // to say and did not say it.
1354        let mut names = Interner::new();
1355        let mut module = Module::new(names.intern("empty.c"), &target());
1356        assert_eq!(lower(&mut module, &mut names), 0);
1357        assert_eq!(module.globals().count(), 0);
1358    }
1359}