Skip to main content

rucc_opt/
params.rs

1//! How big the object behind a pointer parameter is, worked out from the calls that pass it.
2//!
3//! Design: `spec/safe-memory/07-check-elimination.md` section 7.5, which asks for a summary per
4//! function recording "which pointer parameters are dereferenced and over what range, which are
5//! freed, which escape, and whether the function can free memory at all". `crate::nofree` is the
6//! last of those four. This is the first, read the other way round.
7//!
8//! Section 7.5 writes the dereferenced range as something the callee tells its callers, which is
9//! what makes a call site cheaper. What is here is the callers telling the callee, which is what
10//! makes the callee's own checks cheaper, and the callee is where the checks are. On the SQLite
11//! amalgamation 13284 of the bounds checks the discharge pass keeps are on a pointer that arrived
12//! as a parameter, which is more than the next two sources put together, and a parameter is
13//! exactly the value a function-at-a-time pass can say nothing about.
14//!
15//! # What is claimed
16//!
17//! A function only this module can call, every call to which passes an object with at least so
18//! many bytes left in it, has a parameter with at least so many bytes wherever it is used. The
19//! objects believed are the two whose extent is already written down: a frame slot of the caller,
20//! read off a fixed size `alloca`, and a global this module defines and vouches for, which is
21//! `crate::extents`' table. Both are alive for as long as the call runs, so the answer says a
22//! lifetime as well as an extent and [`Flags::HANDED`] licenses both, in the way
23//! [`Flags::STATIC`] does.
24//!
25//! Only this module can call it means internal linkage and an address this module never takes.
26//! An address is taken by a `global_addr` naming it anywhere in any body, by a relocation in any
27//! global's initial image, and by an alias resolving to it. Any of those and the function is left
28//! alone, because a call through an address is a call site this cannot see and the argument it
29//! passes is one nobody counted.
30//!
31//! # Where the answer goes
32//!
33//! Onto the check, as [`Flags::HANDED`], before the pipeline starts. The reason is
34//! `crate::extents`' reason: what is being said is worked out across functions and a pass is given
35//! one. Writing it on the instruction is also what keeps the claim in one place. A pass reading a
36//! flag cannot accidentally believe half of it.
37//!
38//! # The alignment half
39//!
40//! The same table read for a different question. A function only this module
41//! can call, every call to which passes an address that is a multiple of some number, has a
42//! parameter that is a multiple of that number wherever it is used, and that goes on as
43//! `!aligned(a)`, which is a fact a value carries rather than a flag on a check.
44//!
45//! It is worth the second table because of what the measurement on tamnd/rucc#1385 says. The
46//! largest row the discharge pass counts is a bounds check kept only because nothing in the
47//! function says the address is aligned to what the access assumes, 5881 checks at 1166 functions
48//! on the SQLite amalgamation, and 3701 of those at 985 functions are a pointer the function was
49//! handed. That is the row a fact written from the call sites is for, and it is five sixths of it.
50//!
51//! # Which way the fixed point goes
52//!
53//! Every parameter starts unknown and becomes known only when every call site has an answer, and
54//! the round is repeated until nothing changes. That is the least fixed point, and it is the one
55//! that has to be taken here, because the opposite start would let a fact hold itself up: two
56//! functions that pass each other the parameter they were given would agree on any number at all,
57//! and a self-recursive function would agree with itself. Starting from unknown, neither of them
58//! ever gets an answer, which is a check that stays rather than a check that should not have gone.
59//!
60//! One argument reaching an answer through the caller's own parameter is the case that makes this
61//! worth iterating rather than reading once. A static helper is usually passed what its caller was
62//! passed, and the chain only bottoms out at a frame slot several calls up.
63//!
64//! # What is not here
65//!
66//! Nothing is said about a pointer that arrived from a `load`, from an allocator or from a call,
67//! and nothing is said about a function this module does not define or that anything can reach.
68//! Those are the other rows of the measurement and they need their own work.
69//!
70//! The summary is spent on the checks and thrown away, in the way `crate::nofree`'s is, and for
71//! the same reason: a record that survives the file it was worked out in is what link time
72//! optimization will want and there is no link time optimization yet.
73
74use std::collections::{HashMap, HashSet};
75
76use rucc_base::Symbol;
77use rucc_ir::{
78    Datum, Def, Extra, Facts, Flags, Func, FuncId, Inst, Linkage, Module, Opcode, Pic, Type, Value,
79};
80
81use crate::discharge::{Fact, about, alive, covers, derives, normal, settled};
82use crate::extents::extents;
83
84/// Writes [`Flags::HANDED`] onto every check whose bytes are inside an object its callers hand in,
85/// and `!aligned(a)` onto every parameter its callers all hand an aligned address to.
86///
87/// Gives back how many checks were marked, which is what the pipeline reports. The facts are not
88/// counted in it, since a fact is not a check and a reader comparing the two numbers would be
89/// comparing two different things.
90pub fn annotate(module: &mut Module, pic: Pic) -> usize {
91    let reachable = reachable(module);
92    let closed: Vec<FuncId> = module
93        .funcs()
94        .filter(|&id| {
95            let func = &module[id];
96            !func.is_declaration()
97                && func.linkage == Linkage::Internal
98                && !reachable.contains(&func.name)
99        })
100        .collect();
101    if closed.is_empty() {
102        return 0;
103    }
104    let mut where_defined: HashMap<_, FuncId> = HashMap::new();
105    for &id in &closed {
106        where_defined.insert(module[id].name, id);
107    }
108    let sites = sites(module, &where_defined);
109    let aligns = aligns(module, &closed, &sites);
110    write_aligns(module, &aligns);
111    let globals = extents(module, pic);
112    let handed = handed(module, &closed, &sites, &globals);
113    if handed.is_empty() {
114        return 0;
115    }
116    let mut marked = 0;
117    for id in closed {
118        let Some(sizes) = handed.get(&id) else { continue };
119        let func = &module[id];
120        let Some(entry) = func.entry() else { continue };
121        let object = |base: Value| -> Option<Fact> {
122            let Def::Param { block, index } = func[base].def else { return None };
123            if block != entry {
124                return None;
125            }
126            Some(Fact::whole(base, i128::from(*sizes.get(&index)?)))
127        };
128        let marks: Vec<Inst> = func
129            .blocks()
130            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
131            .filter(|&inst| !func[inst].flags.contains(Flags::HANDED))
132            .filter(|&inst| inside(func, inst, &object))
133            .collect();
134        marked += marks.len();
135        let func = &mut module[id];
136        for inst in marks {
137            func[inst].flags |= Flags::HANDED;
138        }
139    }
140    marked
141}
142
143/// How many bytes each closed function's pointer parameters are known to have.
144///
145/// Keyed by the function and then by the position of the parameter in the entry block, which is
146/// the position of the argument at every call to it. A parameter with no entry is one nothing is
147/// known about, and a function with no entry is one where that is true of all of them.
148fn handed(
149    module: &Module,
150    closed: &[FuncId],
151    sites: &HashMap<FuncId, Vec<(FuncId, Inst)>>,
152    globals: &HashMap<Symbol, u64>,
153) -> HashMap<FuncId, HashMap<u32, u64>> {
154    let mut known: HashMap<FuncId, HashMap<u32, u64>> = HashMap::new();
155    loop {
156        let mut settled = true;
157        for &id in closed {
158            let Some(calls) = sites.get(&id) else { continue };
159            let count = module[id].signature().params.len();
160            let mut sizes = HashMap::new();
161            for index in 0..count {
162                if module[id].signature().params[index].ty != Type::PTR {
163                    continue;
164                }
165                let Some(least) = least(module, calls, index, globals, &known) else { continue };
166                sizes.insert(u32::try_from(index).unwrap_or(u32::MAX), least);
167            }
168            if known.get(&id) != Some(&sizes) {
169                known.insert(id, sizes);
170                settled = false;
171            }
172        }
173        if settled {
174            known.retain(|_, sizes| !sizes.is_empty());
175            return known;
176        }
177    }
178}
179
180/// The fewest bytes any call leaves in the object it passes at that position.
181///
182/// `None` the moment one call cannot be read, because what is wanted holds at every call or it
183/// holds nowhere. A callee nothing in this module calls also answers `None`, since the fewest of
184/// no numbers is not a number and pretending otherwise would say anything at all about a function
185/// that is only reached from outside.
186fn least(
187    module: &Module,
188    calls: &[(FuncId, Inst)],
189    index: usize,
190    globals: &HashMap<Symbol, u64>,
191    known: &HashMap<FuncId, HashMap<u32, u64>>,
192) -> Option<u64> {
193    let mut least = None;
194    for &(caller, inst) in calls {
195        let func = &module[caller];
196        let &value = func[func[inst].args].get(index)?;
197        let left = passed(caller, func, value, globals, known)?;
198        least = Some(least.map_or(left, |so_far: u64| so_far.min(left)));
199    }
200    least
201}
202
203/// How many bytes are left in the object this argument points into.
204///
205/// The walk to a base and a constant is the discharge pass's, so a call passing `&thing.field`
206/// says what is left of `thing` from that field rather than nothing. An offset outside the object
207/// is not an object with a negative amount left, it is a pointer this says nothing about.
208fn passed(
209    caller: FuncId,
210    func: &Func,
211    value: Value,
212    globals: &HashMap<Symbol, u64>,
213    known: &HashMap<FuncId, HashMap<u32, u64>>,
214) -> Option<u64> {
215    let (base, offset) = normal(func, value);
216    let whole = i128::from(object(caller, func, base, globals, known)?);
217    if offset < 0 || offset > whole {
218        return None;
219    }
220    u64::try_from(whole - offset).ok()
221}
222
223/// How big the object a value is, when it is one of the three this believes.
224fn object(
225    caller: FuncId,
226    func: &Func,
227    base: Value,
228    globals: &HashMap<Symbol, u64>,
229    known: &HashMap<FuncId, HashMap<u32, u64>>,
230) -> Option<u64> {
231    match func[base].def {
232        // The caller's own parameter, which is what makes a chain of static helpers worth
233        // following. Empty until a round settles it, so the first round reaches only the calls
234        // that pass an object outright.
235        Def::Param { block, index } => {
236            if func.entry() != Some(block) {
237                return None;
238            }
239            known.get(&caller)?.get(&index).copied()
240        }
241        Def::Result { inst, .. } => match func[inst].opcode {
242            Opcode::Alloca if func[func[inst].args].is_empty() => {
243                let Extra::Mem(info) = func[inst].extra else { return None };
244                Some(func[info].size)
245            }
246            Opcode::GlobalAddr => {
247                let Extra::Symbol(name) = func[inst].extra else { return None };
248                globals.get(&name).copied()
249            }
250            _ => None,
251        },
252    }
253}
254
255/// What each closed function's pointer parameters are known to be aligned to, in bytes.
256///
257/// The extent table above with the question swapped. The fewest bytes any call leaves in the
258/// object it passes becomes the least alignment any call hands in, the walk to an `alloca` or a
259/// global becomes `crate::discharge`'s own alignment walk, and everything else about the shape,
260/// including which functions are closed and which way the fixed point goes, is the same.
261///
262/// # What a fact may not come from
263///
264/// The declared type of the parameter. The alignment conjunct of judgement J1 is there to catch a
265/// cast that moves a pointer off what its new type assumes, which is row S7 of
266/// `spec/safe-memory/16-rows.md`, so a fact saying a `T *` parameter is aligned to what a `T`
267/// needs would assume exactly the thing the check exists to test, and it would do it at every
268/// function boundary in the program at once. What is read instead is what each caller actually
269/// computed, by the same walk the callee would have used had the value not crossed a boundary. A
270/// caller that hands in `(int *)((char *)p + 1)` contributes one, one is thrown away, and the
271/// parameter is left with no fact, which is the row surviving the call rather than being turned
272/// off by it.
273///
274/// # Why the walk is given no graph and the caller's own answers
275///
276/// No graph because a call site is one value in one function and the walk through a join wants a
277/// module-wide fixed point of its own to be worth building one for. The caller's own answers go in
278/// as the map [`settled`] looks at before it looks at a value's shape, so an argument that is the
279/// caller's own parameter reads what the round before worked out and a chain of static helpers
280/// reaches the slot at the top, in the way the extent half does.
281fn aligns(
282    module: &Module,
283    closed: &[FuncId],
284    sites: &HashMap<FuncId, Vec<(FuncId, Inst)>>,
285) -> HashMap<FuncId, HashMap<u32, u32>> {
286    let mut known: HashMap<FuncId, HashMap<u32, u32>> = HashMap::new();
287    loop {
288        let mut stable = true;
289        for &id in closed {
290            let Some(calls) = sites.get(&id) else { continue };
291            let count = module[id].signature().params.len();
292            let mut alignments = HashMap::new();
293            for index in 0..count {
294                if module[id].signature().params[index].ty != Type::PTR {
295                    continue;
296                }
297                let Some(least) = least_align(module, calls, index, &known) else { continue };
298                alignments.insert(u32::try_from(index).unwrap_or(u32::MAX), least);
299            }
300            if known.get(&id) != Some(&alignments) {
301                known.insert(id, alignments);
302                stable = false;
303            }
304        }
305        if stable {
306            known.retain(|_, alignments| !alignments.is_empty());
307            return known;
308        }
309    }
310}
311
312/// The least alignment any call hands in at that position, when every call has one to give.
313///
314/// `None` the moment one call cannot be read, for [`least`]'s reason: what is claimed holds at
315/// every call or it holds nowhere. One is thrown away with the same answer, since every address in
316/// the program is aligned to one byte and a fact that says so answers nothing and costs a line in
317/// every dump. Anything that is not a power of two is thrown away too, which is the saturated
318/// answer the walk gives for a step too wide to hold, and it is what the verifier's rule for this
319/// fact asks for.
320fn least_align(
321    module: &Module,
322    calls: &[(FuncId, Inst)],
323    index: usize,
324    known: &HashMap<FuncId, HashMap<u32, u32>>,
325) -> Option<u32> {
326    let mut least = None;
327    for &(caller, inst) in calls {
328        let func = &module[caller];
329        let &value = func[func[inst].args].get(index)?;
330        let carried = carried(func, caller, known);
331        let found = u32::try_from(settled(func, None, &carried, value)).ok()?;
332        if found <= 1 || !found.is_power_of_two() {
333            return None;
334        }
335        least = Some(least.map_or(found, |so_far: u32| so_far.min(found)));
336    }
337    least
338}
339
340/// What the round before worked out about one caller's own pointer parameters, keyed by value.
341///
342/// The shape [`settled`] wants, which is the answers checks gave, because a fact from a caller of
343/// the caller is as good an answer about that value as a check standing in front of it.
344fn carried(
345    func: &Func,
346    caller: FuncId,
347    known: &HashMap<FuncId, HashMap<u32, u32>>,
348) -> HashMap<Value, u64> {
349    let mut carried = HashMap::new();
350    let (Some(entry), Some(alignments)) = (func.entry(), known.get(&caller)) else {
351        return carried;
352    };
353    for (&index, &align) in alignments {
354        if let Some(&param) = func[entry].params.get(index as usize) {
355            carried.insert(param, u64::from(align));
356        }
357    }
358    carried
359}
360
361/// Puts `!aligned(a)` on the entry parameters the table has an answer for.
362///
363/// Onto the value rather than onto a check, which is the one place this half differs from the
364/// extent half. Section 6.2.4 of `spec/safe-memory/06-instrumentation.md` has `!aligned(a)` as
365/// something a value carries and the IR has carried it since tamnd/rucc#452, and a fact on the
366/// parameter answers every check in the callee that walks back to it rather than only the ones
367/// this pass thought to go looking at.
368///
369/// The larger of what is there and what was worked out, because a fact is a promise and two
370/// promises about one value are both true. Nothing else writes this fact today, so the case is
371/// this pass running twice over one module.
372fn write_aligns(module: &mut Module, table: &HashMap<FuncId, HashMap<u32, u32>>) {
373    for (&id, alignments) in table {
374        let func = &mut module[id];
375        let Some(entry) = func.entry() else { continue };
376        for (&index, &align) in alignments {
377            let Some(&param) = func[entry].params.get(index as usize) else { continue };
378            if func[param].ty != Type::PTR {
379                continue;
380            }
381            let had = func.facts(param);
382            let align = align.max(had.align.unwrap_or(0));
383            func.set_facts(param, Facts { align: Some(align), ..had });
384        }
385    }
386}
387
388/// Every direct call in the module to one of the closed functions, by the function called.
389///
390/// A call whose argument count does not match what the callee takes is left out rather than
391/// counted, since the positions would not line up and a prototype disagreeing with a definition is
392/// something a translation unit can contain. A variadic callee is left out for the same reason
393/// read the other way: a position past the named parameters is not a parameter.
394fn sites(
395    module: &Module,
396    where_defined: &HashMap<Symbol, FuncId>,
397) -> HashMap<FuncId, Vec<(FuncId, Inst)>> {
398    let mut sites: HashMap<FuncId, Vec<(FuncId, Inst)>> = HashMap::new();
399    for id in module.funcs() {
400        let func = &module[id];
401        if func.is_declaration() {
402            continue;
403        }
404        for block in func.blocks() {
405            for inst in func.insts(block) {
406                if !matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall) {
407                    continue;
408                }
409                let Extra::Call(at) = func[inst].extra else { continue };
410                let Some(callee) = func[at].callee else { continue };
411                let Some(&target) = where_defined.get(&callee) else { continue };
412                let signature = module[target].signature();
413                if signature.variadic || signature.params.len() != func[func[inst].args].len() {
414                    continue;
415                }
416                sites.entry(target).or_default().push((id, inst));
417            }
418        }
419    }
420    sites
421}
422
423/// Every function symbol whose address this module hands out.
424///
425/// A `global_addr` in any body, a relocation in any global's initial image, and the target of any
426/// alias. What each of them has in common is that something other than a direct call can reach the
427/// function afterwards, and a call this cannot see is an argument nobody counted.
428fn reachable(module: &Module) -> HashSet<Symbol> {
429    let mut taken = HashSet::new();
430    for id in module.funcs() {
431        let func = &module[id];
432        if func.is_declaration() {
433            continue;
434        }
435        for block in func.blocks() {
436            for inst in func.insts(block) {
437                if func[inst].opcode != Opcode::GlobalAddr {
438                    continue;
439                }
440                if let Extra::Symbol(name) = func[inst].extra {
441                    taken.insert(name);
442                }
443            }
444        }
445    }
446    for id in module.globals() {
447        let Some(init) = module[id].init else { continue };
448        for &datum in &module[init] {
449            if let Datum::Addr(at) | Datum::Away(at) = datum {
450                taken.insert(module[at].symbol);
451            }
452        }
453    }
454    for id in module.aliases() {
455        taken.insert(module[id].target);
456    }
457    taken
458}
459
460/// Whether that instruction is a check every byte of which is inside one object handed in.
461///
462/// The three kinds asked the way `crate::discharge` asks them, which is `crate::extents`' shape
463/// with the object reader passed in rather than fixed.
464fn inside(func: &Func, inst: Inst, object: &impl Fn(Value) -> Option<Fact>) -> bool {
465    match func[inst].opcode {
466        Opcode::CheckBounds => {
467            if func[func[inst].args].len() > 2 {
468                return false;
469            }
470            let Some(asked) = about(func, inst) else { return false };
471            object(asked.base).is_some_and(|whole| covers(&whole, &asked))
472        }
473        Opcode::CheckLive => {
474            let Some(asked) = alive(func, inst) else { return false };
475            object(asked.base).is_some_and(|whole| covers(&whole, &asked))
476        }
477        Opcode::CheckDeriv => {
478            let Some((from, to)) = derives(func, inst) else { return false };
479            object(from.base).is_some_and(|whole| covers(&whole, &from) && covers(&whole, &to))
480        }
481        _ => false,
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use rucc_base::Interner;
488    use rucc_ir::{
489        Builder, Extra, Func, Global, InstData, Linkage, MemInfo, MemOrder, Module, Opcode, Pic,
490        Restrict, Signature, Type, Value,
491    };
492    use rucc_target::{TargetInfo, Triple};
493
494    use super::annotate;
495
496    /// An empty module for a sixty four bit Linux.
497    fn module(names: &mut Interner) -> Module {
498        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
499        Module::new(names.intern("t.c"), &target)
500    }
501
502    /// Puts a static function taking one pointer into the module, with a check over `size` bytes
503    /// at the pointer it was handed.
504    fn callee(names: &mut Interner, module: &mut Module, size: u64) {
505        let name = names.intern("g");
506        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR]));
507        func.linkage = Linkage::Internal;
508        let block = func.create_block();
509        let pointer = func.append_param(block, Type::PTR);
510        let mut build = Builder::new(&mut func, block);
511        check(&mut build, pointer, size);
512        live(&mut build, pointer);
513        build.ret(&[]);
514        module.add_func(func);
515    }
516
517    /// Puts a function `f` into the module whose body calls `g` with whatever the closure builds.
518    fn caller(
519        names: &mut Interner,
520        module: &mut Module,
521        name: &str,
522        argument: impl FnOnce(&mut Builder<'_>) -> Value,
523    ) {
524        let at = names.intern(name);
525        let called = names.intern("g");
526        let mut func = Func::new(at, Signature::new());
527        let block = func.create_block();
528        let mut build = Builder::new(&mut func, block);
529        let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
530        let value = argument(&mut build);
531        build.call(called, signature, &[value]);
532        build.ret(&[]);
533        module.add_func(func);
534    }
535
536    /// A stack slot of `size` bytes.
537    fn local(build: &mut Builder<'_>, size: u64) -> Value {
538        let info = MemInfo {
539            size,
540            align: 8,
541            order: MemOrder::NotAtomic,
542            tbaa: None,
543            owns: 0,
544            restrict: Restrict::NONE,
545        };
546        let extra = Extra::Mem(build.func().add_mem(info));
547        build.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
548    }
549
550    /// Puts `cap_of` and a `check_bounds` over `size` bytes at `pointer` into a block.
551    fn check(build: &mut Builder<'_>, pointer: Value, size: u64) {
552        let args = build.func().push_values(&[pointer]);
553        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
554        let info = MemInfo {
555            size,
556            align: 1,
557            order: MemOrder::NotAtomic,
558            tbaa: None,
559            owns: 0,
560            restrict: Restrict::NONE,
561        };
562        let args = build.func().push_values(&[capability, pointer]);
563        let extra = Extra::Mem(build.func().add_mem(info));
564        build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
565    }
566
567    /// Puts `cap_of` and a `check_live` at `pointer` into a block.
568    fn live(build: &mut Builder<'_>, pointer: Value) {
569        let args = build.func().push_values(&[pointer]);
570        let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
571        let args = build.func().push_values(&[capability, pointer]);
572        build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
573    }
574
575    /// A pointer `bytes` past another one.
576    fn past(build: &mut Builder<'_>, pointer: Value, bytes: i128) -> Value {
577        let offset = build.iconst(Type::int(64), bytes);
578        let args = build.func().push_values(&[pointer, offset]);
579        build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
580    }
581
582    #[test]
583    fn a_check_on_a_parameter_every_call_hands_a_slot_big_enough_is_marked() {
584        let mut names = Interner::new();
585        let mut module = module(&mut names);
586        callee(&mut names, &mut module, 16);
587        caller(&mut names, &mut module, "f", |build| local(build, 32));
588        assert_eq!(
589            annotate(&mut module, Pic::Executable),
590            2,
591            "the bounds check and the lifetime one"
592        );
593    }
594
595    #[test]
596    fn a_call_handing_a_slot_too_small_marks_only_the_lifetime_check() {
597        // Eight bytes are not the sixteen the bounds check reads, and they are the one byte the
598        // lifetime check is about. The extent and the lifetime are separate claims and a slot too
599        // small for the first still settles the second.
600        let mut names = Interner::new();
601        let mut module = module(&mut names);
602        callee(&mut names, &mut module, 16);
603        caller(&mut names, &mut module, "f", |build| local(build, 8));
604        assert_eq!(annotate(&mut module, Pic::Executable), 1);
605    }
606
607    #[test]
608    fn the_fewest_bytes_any_call_hands_is_what_the_parameter_gets() {
609        // Two calls, one generous and one not. What holds at the parameter is what holds at every
610        // call, so the eight byte slot decides and the bounds check is not marked. The generous
611        // call does not get it either, because there is one parameter and not one per call site.
612        let mut names = Interner::new();
613        let mut module = module(&mut names);
614        callee(&mut names, &mut module, 16);
615        caller(&mut names, &mut module, "f", |build| local(build, 32));
616        caller(&mut names, &mut module, "h", |build| local(build, 8));
617        assert_eq!(
618            annotate(&mut module, Pic::Executable),
619            1,
620            "the lifetime check, which eight bytes settle"
621        );
622    }
623
624    #[test]
625    fn a_call_handing_a_field_of_a_slot_leaves_what_is_past_the_field() {
626        // Sixteen bytes past the start of a thirty two byte slot is sixteen bytes left, which is
627        // exactly what the check asks for.
628        let mut names = Interner::new();
629        let mut module = module(&mut names);
630        callee(&mut names, &mut module, 16);
631        caller(&mut names, &mut module, "f", |build| {
632            let slot = local(build, 32);
633            past(build, slot, 16)
634        });
635        assert_eq!(annotate(&mut module, Pic::Executable), 2);
636    }
637
638    #[test]
639    fn a_call_handing_a_field_that_leaves_too_little_marks_only_the_lifetime_check() {
640        // Twelve bytes left of the thirty two, which is less than the sixteen the bounds check
641        // reads and more than the one the lifetime check is about.
642        let mut names = Interner::new();
643        let mut module = module(&mut names);
644        callee(&mut names, &mut module, 16);
645        caller(&mut names, &mut module, "f", |build| {
646            let slot = local(build, 32);
647            past(build, slot, 20)
648        });
649        assert_eq!(annotate(&mut module, Pic::Executable), 1);
650    }
651
652    #[test]
653    fn a_callee_anything_can_reach_is_left_alone() {
654        // The same module with `g` external rather than static. A call this module cannot see
655        // passes an argument nobody counted, so nothing is claimed about the parameter.
656        let mut names = Interner::new();
657        let mut module = module(&mut names);
658        callee(&mut names, &mut module, 16);
659        let id = module.funcs().next().expect("the callee");
660        module[id].linkage = Linkage::External;
661        caller(&mut names, &mut module, "f", |build| local(build, 32));
662        assert_eq!(annotate(&mut module, Pic::Executable), 0);
663    }
664
665    #[test]
666    fn a_callee_whose_address_is_taken_is_left_alone() {
667        // The `global_addr` is what taking the address of a function looks like, and after it the
668        // call this counted is no longer the only way in.
669        let mut names = Interner::new();
670        let mut module = module(&mut names);
671        callee(&mut names, &mut module, 16);
672        caller(&mut names, &mut module, "f", |build| local(build, 32));
673        let called = names.intern("g");
674        caller(&mut names, &mut module, "h", |build| {
675            let extra = Extra::Symbol(called);
676            build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
677            local(build, 32)
678        });
679        assert_eq!(annotate(&mut module, Pic::Executable), 0);
680    }
681
682    #[test]
683    fn a_callee_named_by_a_globals_image_is_left_alone() {
684        let mut names = Interner::new();
685        let mut module = module(&mut names);
686        callee(&mut names, &mut module, 16);
687        caller(&mut names, &mut module, "f", |build| local(build, 32));
688        let at = module.add_reloc(rucc_ir::Reloc { symbol: names.intern("g"), addend: 0, size: 8 });
689        let init = module.push_data(&[rucc_ir::Datum::Addr(at)]);
690        let mut global = Global::new(names.intern("table"), 8, 8);
691        global.init = Some(init);
692        module.add_global(global);
693        assert_eq!(annotate(&mut module, Pic::Executable), 0);
694    }
695
696    #[test]
697    fn a_callee_nothing_in_the_module_calls_is_left_alone() {
698        // The fewest of no numbers is not a number, and saying otherwise would claim anything at
699        // all about a function only reached from outside.
700        let mut names = Interner::new();
701        let mut module = module(&mut names);
702        callee(&mut names, &mut module, 16);
703        assert_eq!(annotate(&mut module, Pic::Executable), 0);
704    }
705
706    #[test]
707    fn a_chain_of_static_helpers_reaches_the_slot_at_the_top() {
708        // `f` has the slot, `h` is handed it, `g` is handed what `h` was handed. The middle link
709        // is what makes the fixed point worth iterating: `g` gets an answer only after `h` has
710        // one, which is the round after.
711        let mut names = Interner::new();
712        let mut module = module(&mut names);
713        callee(&mut names, &mut module, 16);
714        let name = names.intern("h");
715        let called = names.intern("g");
716        let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR]));
717        func.linkage = Linkage::Internal;
718        let block = func.create_block();
719        let pointer = func.append_param(block, Type::PTR);
720        let mut build = Builder::new(&mut func, block);
721        let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
722        build.call(called, signature, &[pointer]);
723        build.ret(&[]);
724        module.add_func(func);
725        let at = names.intern("f");
726        let called = names.intern("h");
727        let mut func = Func::new(at, Signature::new());
728        let block = func.create_block();
729        let mut build = Builder::new(&mut func, block);
730        let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
731        let slot = local(&mut build, 32);
732        build.call(called, signature, &[slot]);
733        build.ret(&[]);
734        module.add_func(func);
735        assert_eq!(annotate(&mut module, Pic::Executable), 2);
736    }
737
738    #[test]
739    fn two_functions_handing_each_other_their_own_parameter_hold_nothing_up() {
740        // The reason the fixed point starts from unknown. Neither of these has a call site with an
741        // object in it, and starting from the other end they would agree on any number at all.
742        let mut names = Interner::new();
743        let mut module = module(&mut names);
744        relay(&mut names, &mut module, "g", "h", 16);
745        relay(&mut names, &mut module, "h", "g", 16);
746        assert_eq!(annotate(&mut module, Pic::Executable), 0);
747    }
748
749    /// What `!aligned(a)` says about the first parameter of `g`, once the pass has run.
750    fn fact(names: &mut Interner, module: &Module) -> Option<u32> {
751        let name = names.intern("g");
752        let id = module.funcs().find(|&id| module[id].name == name).expect("the callee");
753        let func = &module[id];
754        let entry = func.entry().expect("its entry block");
755        let &param = func[entry].params.first().expect("its pointer parameter");
756        func.facts(param).align
757    }
758
759    #[test]
760    fn an_alignment_every_call_hands_in_reaches_the_parameter() {
761        // Nothing inside `g` says anything about the pointer it was handed, and the only call to
762        // it passes a slot aligned to eight, so eight is what the parameter is aligned to wherever
763        // it is used. This is the fact, and it is worked out from what the caller computed rather
764        // than from what the parameter is declared to be.
765        let mut names = Interner::new();
766        let mut module = module(&mut names);
767        callee(&mut names, &mut module, 16);
768        caller(&mut names, &mut module, "f", |build| local(build, 32));
769        annotate(&mut module, Pic::Executable);
770        assert_eq!(fact(&mut names, &module), Some(8));
771    }
772
773    #[test]
774    fn the_least_alignment_any_call_hands_is_what_the_parameter_gets() {
775        // One call passes the slot and the other passes four bytes into it. Four divides by four
776        // and not by eight, so four is what holds at every call and four is what is claimed.
777        let mut names = Interner::new();
778        let mut module = module(&mut names);
779        callee(&mut names, &mut module, 16);
780        caller(&mut names, &mut module, "f", |build| local(build, 32));
781        caller(&mut names, &mut module, "h", |build| {
782            let slot = local(build, 32);
783            past(build, slot, 4)
784        });
785        annotate(&mut module, Pic::Executable);
786        assert_eq!(fact(&mut names, &module), Some(4));
787    }
788
789    #[test]
790    fn a_call_that_moves_a_pointer_off_its_alignment_leaves_the_parameter_with_nothing() {
791        // Row S7 crossing a call. One byte past an eight byte aligned slot is an address that is a
792        // multiple of one and nothing else, and a fact that says a value is aligned to one byte
793        // says nothing, so the parameter is left with none and the checks in `g` stay. If this
794        // ever answers, the alignment conjunct is off for every static function in the program.
795        let mut names = Interner::new();
796        let mut module = module(&mut names);
797        callee(&mut names, &mut module, 16);
798        caller(&mut names, &mut module, "f", |build| {
799            let slot = local(build, 32);
800            past(build, slot, 1)
801        });
802        annotate(&mut module, Pic::Executable);
803        assert_eq!(fact(&mut names, &module), None);
804    }
805
806    #[test]
807    fn one_call_this_cannot_read_leaves_the_parameter_with_nothing() {
808        // What is claimed holds at every call or it holds nowhere, so a second call passing a
809        // pointer nothing here knows the origin of takes the fact away from the first.
810        let mut names = Interner::new();
811        let mut module = module(&mut names);
812        callee(&mut names, &mut module, 16);
813        caller(&mut names, &mut module, "f", |build| local(build, 32));
814        let outside = names.intern("somewhere");
815        caller(&mut names, &mut module, "h", |build| {
816            let extra = Extra::Symbol(outside);
817            let global =
818                build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
819            let args = build.func().push_values(&[global]);
820            let info = MemInfo {
821                size: 8,
822                align: 8,
823                order: MemOrder::NotAtomic,
824                tbaa: None,
825                owns: 0,
826                restrict: Restrict::NONE,
827            };
828            let extra = Extra::Mem(build.func().add_mem(info));
829            build.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR)
830        });
831        annotate(&mut module, Pic::Executable);
832        assert_eq!(fact(&mut names, &module), None);
833    }
834
835    #[test]
836    fn a_callee_anything_can_reach_gets_no_alignment_either() {
837        // The visibility test is one test and both halves are behind it. A call this module cannot
838        // see passes an address nobody measured.
839        let mut names = Interner::new();
840        let mut module = module(&mut names);
841        callee(&mut names, &mut module, 16);
842        let id = module.funcs().next().expect("the callee");
843        module[id].linkage = Linkage::External;
844        caller(&mut names, &mut module, "f", |build| local(build, 32));
845        annotate(&mut module, Pic::Executable);
846        assert_eq!(fact(&mut names, &module), None);
847    }
848
849    #[test]
850    fn an_alignment_reaches_down_a_chain_of_static_helpers() {
851        // `f` has the slot, `h` is handed it, `g` is handed what `h` was handed. `h` gets its
852        // answer in the first round and `g` reads it out of `h` in the second, which is the same
853        // thing the extent half iterates for.
854        let mut names = Interner::new();
855        let mut module = module(&mut names);
856        callee(&mut names, &mut module, 16);
857        relay(&mut names, &mut module, "h", "g", 16);
858        let at = names.intern("f");
859        let called = names.intern("h");
860        let mut func = Func::new(at, Signature::new());
861        let block = func.create_block();
862        let mut build = Builder::new(&mut func, block);
863        let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
864        let slot = local(&mut build, 32);
865        build.call(called, signature, &[slot]);
866        build.ret(&[]);
867        module.add_func(func);
868        annotate(&mut module, Pic::Executable);
869        assert_eq!(fact(&mut names, &module), Some(8));
870    }
871
872    /// A static function taking one pointer, checking `size` bytes at it and handing it on.
873    fn relay(names: &mut Interner, module: &mut Module, name: &str, on: &str, size: u64) {
874        let at = names.intern(name);
875        let called = names.intern(on);
876        let mut func = Func::new(at, Signature::new().with_params(&[Type::PTR]));
877        func.linkage = Linkage::Internal;
878        let block = func.create_block();
879        let pointer = func.append_param(block, Type::PTR);
880        let mut build = Builder::new(&mut func, block);
881        check(&mut build, pointer, size);
882        let signature = build.func().add_signature(Signature::new().with_params(&[Type::PTR]));
883        build.call(called, signature, &[pointer]);
884        build.ret(&[]);
885        module.add_func(func);
886    }
887}