#![doc(html_root_url = "https://docs.rs/rucc-safety/0.10.21")]
pub mod boundary;
pub mod lower;
pub mod plane;
pub mod promise;
pub mod summary;
pub mod wrap;
pub use boundary::{Sites, WITNESS, witness};
pub use lower::{Descriptor, SECTION, lower};
pub use plane::Plane;
pub use promise::{Kept, promise};
pub use summary::{Frames, Summary, summarize};
pub use wrap::{INTERPOSED, PREFIX, redirect};
use rucc_ir::{Def, Extra, Func, Imm, Inst, InstData, Module, Opcode, Type, Value};
pub use rucc_session::{Promise, Subobject};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Counts {
pub checked: usize,
pub live: usize,
pub derived: usize,
pub skipped: usize,
pub judged: usize,
pub carried: usize,
pub asked: usize,
pub wrote: usize,
pub filled: usize,
pub moved: usize,
pub promised: usize,
pub scoped: usize,
}
impl Counts {
fn add(&mut self, other: Counts) {
self.checked += other.checked;
self.live += other.live;
self.derived += other.derived;
self.skipped += other.skipped;
self.judged += other.judged;
self.carried += other.carried;
self.asked += other.asked;
self.wrote += other.wrote;
self.filled += other.filled;
self.moved += other.moved;
self.promised += other.promised;
self.scoped += other.scoped;
}
fn add_kept(&mut self, kept: Kept) {
self.promised += kept.promised;
self.scoped += kept.scoped;
}
}
pub fn run(module: &mut Module, subobject: Subobject, promise: Promise) -> Counts {
let plane = Plane::build(module);
let width = u64::from(module.datalayout.pointer_bits / 8);
let mut counts = Counts::default();
for id in module.funcs() {
if !module[id].is_declaration() {
counts.add(insert(&mut module[id], &plane, width, subobject, promise));
}
}
counts
}
pub fn insert(
func: &mut Func,
plane: &Plane,
width: u64,
subobject: Subobject,
promise: Promise,
) -> Counts {
let mut counts = Counts::default();
let insts: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in insts {
match func[inst].opcode {
Opcode::Load | Opcode::Store => match pointer_of(func, inst) {
Some(pointer) => {
let capability = check(func, inst, pointer, width);
counts.checked += 1;
counts.live += 1;
if func[inst].opcode == Opcode::Store {
if subobject.asks() && ask(func, plane, inst, pointer, capability, width) {
counts.asked += 1;
}
if wrote(func, inst, pointer, width) {
counts.wrote += 1;
}
if judge(func, plane, inst, pointer, width) {
counts.judged += 1;
}
} else {
if ask(func, plane, inst, pointer, capability, width) {
counts.asked += 1;
}
if filled(func, inst, pointer, capability, width) {
counts.filled += 1;
}
}
}
None => counts.skipped += 1,
},
Opcode::Memcpy | Opcode::Memmove => {
if moved(func, inst) {
counts.moved += 1;
}
if carry(func, inst) {
counts.carried += 1;
}
}
Opcode::PtrAdd => {
if derivation(func, inst) {
counts.derived += 1;
} else {
counts.skipped += 1;
}
}
_ => {}
}
}
if promise.checks() {
counts.add_kept(promise::promise(func, width));
}
counts
}
fn pointer_of(func: &Func, access: Inst) -> Option<Value> {
let args = &func[func[access].args];
let at = match func[access].opcode {
Opcode::Load => 0,
Opcode::Store => 1,
_ => return None,
};
let &value = args.get(at)?;
func[value].ty.is_ptr().then_some(value)
}
fn check(func: &mut Func, access: Inst, pointer: Value, width: u64) -> Option<Value> {
let span = func.span(access);
let Extra::Mem(info) = func[access].extra else { return None };
let mut info = func[info];
info.size = covered(func, access, info.size, width);
info.owns = 0;
let capability = cap_of(func, pointer, access);
let args = func.push_values(&[capability, pointer]);
let extra = Extra::Mem(func.add_mem(info));
let bounds =
func.create_inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[], span);
func.insert_before(bounds, access);
let args = func.push_values(&[capability, pointer]);
let live = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[], span);
func.insert_before(live, access);
Some(capability)
}
fn judge(func: &mut Func, plane: &Plane, store: Inst, pointer: Value, width: u64) -> bool {
let Extra::Mem(info) = func[store].extra else { return false };
let size = covered(func, store, func[info].size, width);
if size == 0 {
return false;
}
let node = plane.entry(func[info].tbaa);
let span = func.span(store);
let (made, length) = extent(func, store, size);
let args = func.push_values(&[pointer, length]);
let data = InstData { args, extra: Extra::Node(node), ..InstData::new(Opcode::MetaType) };
let judged = func.create_inst(data, &[], span);
func.insert_after(judged, made);
true
}
fn ask(
func: &mut Func,
plane: &Plane,
read: Inst,
pointer: Value,
capability: Option<Value>,
width: u64,
) -> bool {
let Some(capability) = capability else { return false };
let Extra::Mem(at) = func[read].extra else { return false };
let mut info = func[at];
let Some(node) = info.tbaa else { return false };
info.size = covered(func, read, info.size, width);
if info.size == 0 {
return false;
}
info.tbaa = Some(plane.entry(Some(node)));
info.owns = 0;
let span = func.span(read);
let args = func.push_values(&[capability, pointer]);
let extra = Extra::Mem(func.add_mem(info));
let data = InstData { args, extra, ..InstData::new(Opcode::CheckType) };
let asked = func.create_inst(data, &[], span);
func.insert_before(asked, read);
true
}
fn carry(func: &mut Func, copy: Inst) -> bool {
let Extra::Mem(info) = func[copy].extra else { return false };
let size = func[info].size;
if size == 0 {
return false;
}
let [to, from] = func[func[copy].args] else { return false };
let span = func.span(copy);
let (made, length) = extent(func, copy, size);
let args = func.push_values(&[to, from, length]);
let data = InstData { args, ..InstData::new(Opcode::MetaTypeCopy) };
let carried = func.create_inst(data, &[], span);
func.insert_after(carried, made);
true
}
fn wrote(func: &mut Func, store: Inst, pointer: Value, width: u64) -> bool {
let Extra::Mem(info) = func[store].extra else { return false };
let size = covered(func, store, func[info].size, width).max(u64::from(func[info].owns));
if size == 0 {
return false;
}
let span = func.span(store);
let (made, length) = extent(func, store, size);
let args = func.push_values(&[pointer, length]);
let data = InstData { args, ..InstData::new(Opcode::MetaInit) };
let judged = func.create_inst(data, &[], span);
func.insert_after(judged, made);
true
}
fn filled(
func: &mut Func,
read: Inst,
pointer: Value,
capability: Option<Value>,
width: u64,
) -> bool {
let Some(capability) = capability else { return false };
let Extra::Mem(at) = func[read].extra else { return false };
let mut info = func[at];
info.size = covered(func, read, info.size, width);
if info.size == 0 {
return false;
}
info.tbaa = None;
let span = func.span(read);
let args = func.push_values(&[capability, pointer]);
let extra = Extra::Mem(func.add_mem(info));
let data = InstData { args, extra, ..InstData::new(Opcode::CheckInit) };
let asked = func.create_inst(data, &[], span);
func.insert_before(asked, read);
true
}
fn moved(func: &mut Func, copy: Inst) -> bool {
let Extra::Mem(info) = func[copy].extra else { return false };
let size = func[info].size;
if size == 0 {
return false;
}
let [to, from] = func[func[copy].args] else { return false };
let span = func.span(copy);
let (made, length) = extent(func, copy, size);
let args = func.push_values(&[to, from, length]);
let data = InstData { args, ..InstData::new(Opcode::MetaInitCopy) };
let carried = func.create_inst(data, &[], span);
func.insert_after(carried, made);
true
}
fn extent(func: &mut Func, at: Inst, size: u64) -> (Inst, Value) {
let span = func.span(at);
let word = Type::int(64);
let extra = Extra::Imm(func.add_imm(Imm::int(i128::from(size), word)));
let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[word], span);
func.insert_after(made, at);
let length = func[made].results().next().expect("a constant created with one result has one");
(made, length)
}
fn covered(func: &Func, access: Inst, stated: u64, width: u64) -> u64 {
if stated != 0 {
return stated;
}
let ty = match func[access].opcode {
Opcode::Load => func[access].results().next().map(|value| func[value].ty),
Opcode::Store => func[func[access].args].first().map(|&value| func[value].ty),
_ => None,
};
ty.map_or(0, |ty| {
if ty.is_ptr() {
return width;
}
u64::from(ty.bits().div_ceil(8)) * u64::from(ty.lanes())
})
}
fn derivation(func: &mut Func, add: Inst) -> bool {
let Some(&base) = func[func[add].args].first() else { return false };
if !func[base].ty.is_ptr() {
return false;
}
let Some(derived) = func[add].results().next() else { return false };
let span = func.span(add);
let width = stride(func, add);
let capability = cap_of(func, base, add);
let args = func.push_values(&[capability, base, derived, width]);
let check = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[], span);
func.insert_after(check, add);
true
}
fn stride(func: &mut Func, add: Inst) -> Value {
let Some(&offset) = func[func[add].args].get(1) else { return one(func, add, Type::int(64)) };
let word = func[offset].ty;
let forwards = match operand_of(func, offset, Opcode::Sub, 0) {
Some(zero) if is_zero(func, zero) => operand_of(func, offset, Opcode::Sub, 1),
_ => None,
};
let scaled = forwards.unwrap_or(offset);
match operand_of(func, scaled, Opcode::Mul, 1) {
Some(width) if func[width].ty == word => width,
_ => one(func, add, word),
}
}
fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
let Def::Result { inst, .. } = func[value].def else { return None };
if func[inst].opcode != opcode {
return None;
}
func[func[inst].args].get(index).copied()
}
fn is_zero(func: &Func, value: Value) -> bool {
let Def::Result { inst, .. } = func[value].def else { return false };
match func[inst].extra {
Extra::Imm(imm) if func[inst].opcode == Opcode::IConst => func[imm].bits() == 0,
_ => false,
}
}
fn one(func: &mut Func, at: Inst, ty: Type) -> Value {
let span = func.span(at);
let extra = Extra::Imm(func.add_imm(Imm::int(1, ty)));
let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
func.insert_before(made, at);
func[made].results().next().expect("a constant created with one result has one")
}
fn cap_of(func: &mut Func, pointer: Value, at: Inst) -> Value {
let span = func.span(at);
let args = func.push_values(&[pointer]);
let cap =
func.create_inst(InstData { args, ..InstData::new(Opcode::CapOf) }, &[Type::CAP], span);
func.insert_before(cap, at);
func[cap].results().next().expect("cap_of produces one value")
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Builder, Flags, MemInfo, MemOrder, Meta, MetaNode, PlaneNode, Restrict, Signature,
TbaaNode, print_func, verify_func,
};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::*;
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn planed(names: &mut Interner, unit: &str) -> (Module, Plane) {
let mut module = Module::new(names.intern(unit), &target());
let plane = Plane::build(&mut module);
(module, plane)
}
fn one_of_each(names: &mut Interner) -> Func {
let i32_ = Type::int(32);
let mut func = Func::new(
names.intern("both"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let info = MemInfo {
size: 4,
align: 4,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[p]);
let extra = Extra::Mem(b.func().add_mem(info));
let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
let args = b.func().push_values(&[loaded, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[loaded]);
func
}
fn promising(names: &mut Interner) -> Func {
let i32_ = Type::int(32);
let mut func = Func::new(
names.intern("kernel"),
Signature::new().with_params(&[Type::PTR, Type::PTR]),
);
let entry = func.create_block();
let to = func.append_param(entry, Type::PTR);
let from = func.append_param(entry, Type::PTR);
let info = MemInfo {
size: 4,
align: 4,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict { clique: 1, base: 1 },
};
let mut b = Builder::new(&mut func, entry);
let read = MemInfo { restrict: Restrict { clique: 1, base: 2 }, ..info };
let loaded = b.load(i32_, from, read, Flags::default());
b.store(loaded, to, info, Flags::default());
b.ret(&[]);
func
}
#[test]
fn the_restrict_checks_wait_until_the_build_asks_for_them() {
let mut names = Interner::new();
let (_, plane) = planed(&mut names, "kernel.c");
let mut quiet = promising(&mut names);
let counts = insert(&mut quiet, &plane, 8, Subobject::Off, Promise::Off);
assert_eq!((counts.promised, counts.scoped), (0, 0));
let mut asked = promising(&mut names);
let counts = insert(&mut asked, &plane, 8, Subobject::Off, Promise::Blocks);
assert_eq!((counts.promised, counts.scoped), (2, 1));
}
#[test]
fn every_access_gets_a_bounds_check_and_a_lifetime_check() {
let mut names = Interner::new();
let mut func = one_of_each(&mut names);
let (module, plane) = planed(&mut names, "both.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
Counts { checked: 2, live: 2, judged: 1, wrote: 1, filled: 1, ..Counts::default() }
);
assert_eq!(
print_func(&module, &func, &names),
"func @both(ptr) -> i32, linkage(external) {\n\
block0(%0: ptr):\n \
%1 = cap_of %0\n \
check_bounds %1, %0, size 4, align 4\n \
check_live %1, %0\n \
check_init %1, %0, size 4, align 4\n \
%2 = load.i32 %0, size 4, align 4\n \
%3 = cap_of %0\n \
check_bounds %3, %0, size 4, align 4\n \
check_live %3, %0\n \
store %2 -> %0, size 4, align 4\n \
%4 = iconst.i64 4\n \
meta_type %0, %4, tbaa !1\n \
%5 = iconst.i64 4\n \
meta_init %0, %5\n \
return %2\n\
}\n"
);
}
fn one_pointer_read(names: &mut Interner) -> Func {
let mut func = Func::new(
names.intern("deref"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::PTR]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let info = MemInfo {
size: 0,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[p]);
let extra = Extra::Mem(b.func().add_mem(info));
let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR);
b.ret(&[loaded]);
func
}
#[test]
fn an_access_that_reads_a_pointer_is_checked_over_the_targets_pointer_width() {
let mut names = Interner::new();
let mut func = one_pointer_read(&mut names);
let (module, plane) = planed(&mut names, "deref.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
Counts { checked: 1, live: 1, filled: 1, ..Counts::default() }
);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("check_bounds %1, %0, size 8, align 8\n"), "{printed}");
assert!(printed.contains("check_init %1, %0, size 8, align 8\n"), "{printed}");
}
#[test]
fn a_pointer_width_of_four_is_what_a_thirty_two_bit_target_gets() {
let mut names = Interner::new();
let mut func = one_pointer_read(&mut names);
let (module, plane) = planed(&mut names, "deref.c");
insert(&mut func, &plane, 4, Subobject::Off, Promise::Off);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("check_bounds %1, %0, size 4, align 8\n"), "{printed}");
}
fn typed(names: &mut Interner, unit: &str) -> (Module, Plane, Meta) {
let mut module = Module::new(names.intern(unit), &target());
let root = names.intern("char");
let root =
module.add_meta(MetaNode::Tbaa(TbaaNode { name: root, parent: None, offset: 0 }));
let int = names.intern("int");
let int =
module.add_meta(MetaNode::Tbaa(TbaaNode { name: int, parent: Some(root), offset: 0 }));
let plane = Plane::build(&mut module);
(module, plane, int)
}
fn reading(names: &mut Interner, node: Option<Meta>) -> Func {
let i32_ = Type::int(32);
let mut func = Func::new(
names.intern("read"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let info = MemInfo {
size: 0,
align: 4,
order: MemOrder::NotAtomic,
tbaa: node,
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[p]);
let extra = Extra::Mem(b.func().add_mem(info));
let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
b.ret(&[loaded]);
func
}
#[test]
fn a_read_asks_the_plane_whether_the_bytes_agree_with_the_type_it_reads_them_as() {
let mut names = Interner::new();
let (module, plane, int) = typed(&mut names, "read.c");
let mut func = reading(&mut names, Some(int));
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).asked, 1);
let printed = print_func(&module, &func, &names);
let entry = plane.entry(Some(int));
assert_eq!(module[entry], MetaNode::Plane(PlaneNode::Type(int)));
let wanted = format!("check_type %1, %0, size 4, align 4, tbaa !{}\n", entry.index());
assert!(printed.contains(&wanted), "{printed}");
let asked = printed.find(&wanted).expect("the check is there");
let read = printed.find("load.i32").expect("and so is the read");
assert!(asked < read, "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_read_the_front_end_named_no_type_for_asks_nothing() {
let mut names = Interner::new();
let (module, plane, _) = typed(&mut names, "copy.c");
let mut func = reading(&mut names, None);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).asked, 0);
let printed = print_func(&module, &func, &names);
assert!(!printed.contains("check_type"), "{printed}");
}
fn writing(names: &mut Interner, node: Option<Meta>) -> Func {
let i32_ = Type::int(32);
let mut func =
Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i32_]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let v = func.append_param(entry, i32_);
let info = MemInfo {
size: 0,
align: 4,
order: MemOrder::NotAtomic,
tbaa: node,
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[v, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
func
}
#[test]
fn a_store_asks_the_plane_too_once_the_build_has_said_the_member_matters() {
let mut names = Interner::new();
let (module, plane, int) = typed(&mut names, "member.c");
let mut func = writing(&mut names, Some(int));
let counts = insert(&mut func, &plane, 8, Subobject::Members, Promise::Off);
assert_eq!((counts.asked, counts.judged), (1, 1));
let printed = print_func(&module, &func, &names);
let entry = plane.entry(Some(int));
let wanted = format!("check_type %2, %0, size 4, align 4, tbaa !{}\n", entry.index());
assert!(printed.contains(&wanted), "{printed}");
let asked = printed.find(&wanted).expect("the check is there");
let wrote = printed.find("store %1").expect("and so is the store");
let recorded = printed.find("meta_type").expect("and so is the recording");
assert!(asked < wrote && wrote < recorded, "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_store_the_front_end_named_no_type_for_asks_nothing_whatever_the_build_asked() {
let mut names = Interner::new();
let (module, plane, _) = typed(&mut names, "aggregate.c");
let mut func = writing(&mut names, None);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Members, Promise::Off).asked, 0);
let printed = print_func(&module, &func, &names);
assert!(!printed.contains("check_type"), "{printed}");
}
#[test]
fn a_store_answers_the_question_rather_than_asking_it() {
let mut names = Interner::new();
let (module, plane, int) = typed(&mut names, "write.c");
let i32_ = Type::int(32);
let mut func =
Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i32_]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let v = func.append_param(entry, i32_);
let info = MemInfo {
size: 0,
align: 4,
order: MemOrder::NotAtomic,
tbaa: Some(int),
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[v, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
assert_eq!((counts.judged, counts.asked), (1, 0));
let printed = print_func(&module, &func, &names);
assert!(!printed.contains("check_type"), "{printed}");
}
#[test]
fn a_store_records_the_type_it_stored_through() {
let mut names = Interner::new();
let (module, plane, int) = typed(&mut names, "typed.c");
let i32_ = Type::int(32);
let mut func =
Func::new(names.intern("record"), Signature::new().with_params(&[Type::PTR, i32_]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let v = func.append_param(entry, i32_);
let info = MemInfo {
size: 0,
align: 4,
order: MemOrder::NotAtomic,
tbaa: Some(int),
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[v, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).judged, 1);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("%3 = iconst.i64 4\n"), "{printed}");
let entry = plane.entry(Some(int));
assert_eq!(module[entry], MetaNode::Plane(PlaneNode::Type(int)));
let wanted = format!("meta_type %0, %3, tbaa !{}\n", entry.index());
assert!(printed.contains(&wanted), "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_store_records_that_the_bytes_it_wrote_hold_something() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "wrote.c");
let i64_ = Type::int(64);
let mut func =
Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let v = func.append_param(entry, i64_);
let info = MemInfo {
size: 0,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[v, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).wrote, 1);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("%4 = iconst.i64 8\n meta_init %0, %4\n"), "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_store_that_owns_the_padding_after_it_records_that_too() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "owns.c");
let byte = Type::int(8);
let mut func =
Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, byte]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let v = func.append_param(entry, byte);
let info = MemInfo {
size: 0,
align: 1,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 4,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[v, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).wrote, 1);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("%4 = iconst.i64 4\n meta_init %0, %4\n"), "{printed}");
assert!(printed.contains("check_bounds %2, %0, size 1"), "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_read_asks_whether_anything_ever_wrote_the_bytes_it_is_about_to_read() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "ask.c");
let mut func = reading(&mut names, None);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).filled, 1);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("check_init %1, %0, size 4, align 4\n"), "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_read_the_front_end_named_no_type_for_still_asks_the_init_plane() {
let mut names = Interner::new();
let (_module, plane) = planed(&mut names, "untyped.c");
let mut func = reading(&mut names, None);
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
assert_eq!(counts.asked, 0);
assert_eq!(counts.filled, 1);
}
#[test]
fn a_store_asks_the_init_plane_nothing() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "store.c");
let i64_ = Type::int(64);
let mut func =
Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let v = func.append_param(entry, i64_);
let info = MemInfo {
size: 8,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[v, p]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).filled, 0);
let printed = print_func(&module, &func, &names);
assert!(!printed.contains("check_init"), "{printed}");
}
#[test]
fn a_read_tells_the_init_plane_nothing() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "read.c");
let mut func = reading(&mut names, None);
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off).wrote, 0);
let printed = print_func(&module, &func, &names);
assert!(!printed.contains("meta_init"), "{printed}");
}
fn one_copy(names: &mut Interner, opcode: Opcode) -> Func {
let mut func =
Func::new(names.intern("move"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
let entry = func.create_block();
let to = func.append_param(entry, Type::PTR);
let from = func.append_param(entry, Type::PTR);
let info = MemInfo {
size: 24,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[to, from]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
b.ret(&[]);
func
}
#[test]
fn a_copy_carries_whatever_the_bytes_it_read_said() {
let mut names = Interner::new();
let mut func = one_copy(&mut names, Opcode::Memcpy);
let (module, plane) = planed(&mut names, "move.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
Counts { carried: 1, moved: 1, ..Counts::default() }
);
assert_eq!(
print_func(&module, &func, &names),
"func @move(ptr, ptr), linkage(external) {\n\
block0(%0: ptr, %1: ptr):\n \
memcpy %0, %1, size 24, align 8\n \
%2 = iconst.i64 24\n \
meta_type_copy %0, %1, %2\n \
%3 = iconst.i64 24\n \
meta_init_copy %0, %1, %3\n \
return\n\
}\n"
);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_copy_whose_ranges_may_overlap_is_carried_the_same_way() {
let mut names = Interner::new();
let mut func = one_copy(&mut names, Opcode::Memmove);
let (module, plane) = planed(&mut names, "overlap.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
Counts { carried: 1, moved: 1, ..Counts::default() }
);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("meta_type_copy %0, %1, %2\n"), "{printed}");
assert!(printed.contains("meta_init_copy %0, %1, %3\n"), "{printed}");
}
#[test]
fn a_walk_over_elements_hands_the_check_the_width_of_one() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("walk"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, Type::int(64));
let mut b = Builder::new(&mut func, entry);
let width = b.iconst(Type::int(64), 24);
let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
let args = b.func().push_values(&[p, bytes]);
let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
b.ret(&[moved]);
let (module, plane) = planed(&mut names, "walk.c");
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
assert_eq!(
print_func(&module, &func, &names),
"func @walk(ptr, i64) -> ptr, linkage(external) {\n\
block0(%0: ptr, %1: i64):\n \
%2 = iconst.i64 24\n \
%3 = mul.nsw %1, %2\n \
%4 = cap_of %0\n \
%5 = ptr_add %0, %3\n \
check_deriv %4, %0, %5, %2\n \
return %5\n\
}\n"
);
}
#[test]
fn a_walk_that_goes_backwards_is_still_a_walk_over_elements() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("back"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, Type::int(64));
let mut b = Builder::new(&mut func, entry);
let width = b.iconst(Type::int(64), 24);
let bytes = b.binary(Opcode::Mul, n, width, Flags::NSW);
let zero = b.iconst(Type::int(64), 0);
let back = b.binary(Opcode::Sub, zero, bytes, Flags::NONE);
let args = b.func().push_values(&[p, back]);
let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
b.ret(&[moved]);
let (module, plane) = planed(&mut names, "back.c");
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("check_deriv %6, %0, %7, %2\n"), "{printed}");
}
#[test]
fn a_pointer_computed_from_another_pointer_is_checked_where_it_is_computed() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("walk"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, Type::int(64));
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[p, n]);
let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
b.ret(&[moved]);
let (module, plane) = planed(&mut names, "walk.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off),
Counts { derived: 1, ..Counts::default() }
);
assert_eq!(
print_func(&module, &func, &names),
"func @walk(ptr, i64) -> ptr, linkage(external) {\n\
block0(%0: ptr, %1: i64):\n \
%2 = iconst.i64 1\n \
%3 = cap_of %0\n \
%4 = ptr_add %0, %1\n \
check_deriv %3, %0, %4, %2\n \
return %4\n\
}\n"
);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn what_it_produces_is_a_function_the_verifier_believes() {
let mut names = Interner::new();
let mut func = one_of_each(&mut names);
let (module, plane) = planed(&mut names, "both.c");
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn every_definition_in_a_module_is_walked_and_the_declarations_are_not() {
let mut names = Interner::new();
let one = one_of_each(&mut names);
let mut two = one_of_each(&mut names);
two.name = names.intern("other");
let declared = Func::new(
names.intern("elsewhere"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::int(32)]),
);
let mut module = Module::new(names.intern("two.c"), &target());
module.add_func(one);
module.add_func(two);
module.add_func(declared);
assert_eq!(
run(&mut module, Subobject::Off, Promise::Off),
Counts { checked: 4, live: 4, judged: 2, wrote: 2, filled: 2, ..Counts::default() }
);
if let Err(errors) = rucc_ir::verify(&module, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_function_with_no_accesses_is_left_alone() {
let mut names = Interner::new();
let i32_ = Type::int(32);
let mut func = Func::new(names.intern("nothing"), Signature::new().with_returns(&[i32_]));
let entry = func.create_block();
let mut b = Builder::new(&mut func, entry);
let zero = b.iconst(i32_, 0);
b.ret(&[zero]);
let (_module, plane) = planed(&mut names, "nothing.c");
let before = func.counts();
assert_eq!(insert(&mut func, &plane, 8, Subobject::Off, Promise::Off), Counts::default());
assert_eq!(func.counts(), before);
}
}