#![doc(html_root_url = "https://docs.rs/rucc-safety/0.10.72")]
pub mod boundary;
pub mod ending;
pub mod frame;
pub mod handover;
pub mod lower;
pub mod origin;
pub mod plane;
pub mod promise;
pub mod slot;
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, Races, Subobject};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Counts {
pub checked: usize,
pub live: usize,
pub freed: 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 relocated: usize,
pub promised: usize,
pub watched: usize,
pub stamped: usize,
pub edged: usize,
pub saved: usize,
pub recalled: usize,
pub scoped: usize,
}
impl Counts {
fn add(&mut self, other: Counts) {
self.checked += other.checked;
self.live += other.live;
self.freed += other.freed;
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.relocated += other.relocated;
self.stamped += other.stamped;
self.saved += other.saved;
self.recalled += other.recalled;
self.watched += other.watched;
self.edged += other.edged;
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, races: Races) -> 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, races));
}
}
counts
}
pub fn insert(
func: &mut Func,
plane: &Plane,
width: u64,
subobject: Subobject,
promise: Promise,
races: Races,
) -> Counts {
let mut counts = Counts::default();
let mut origins = origin::Origins::new();
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, &mut origins, 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 races.records() && raced(func, inst, pointer, capability, width) {
counts.watched += 1;
}
if saved(func, &mut origins, inst, pointer, capability) {
counts.saved += 1;
}
if wrote(func, inst, pointer, width) {
counts.wrote += 1;
}
if judge(func, plane, inst, pointer, width) {
counts.judged += 1;
}
if races.records() && stamped(func, inst, pointer, width) {
counts.stamped += 1;
}
} else {
if ask(func, plane, inst, pointer, capability, width) {
counts.asked += 1;
}
if filled(func, inst, pointer, capability, width) {
counts.filled += 1;
}
if races.reads() && raced(func, inst, pointer, capability, width) {
counts.watched += 1;
}
if recalled(func, &mut origins, inst, pointer, capability) {
counts.recalled += 1;
}
}
}
None => counts.skipped += 1,
},
Opcode::Memcpy | Opcode::Memmove => {
if relocation(func, inst) {
counts.relocated += 1;
}
if moved(func, inst) {
counts.moved += 1;
}
if carry(func, inst) {
counts.carried += 1;
}
}
Opcode::Memset => {
if spanned(func, &mut origins, inst) {
counts.checked += 1;
counts.live += 1;
} else {
counts.skipped += 1;
}
if recorded(func, plane, inst) {
counts.wrote += 1;
counts.judged += 1;
}
}
Opcode::PtrAdd => {
if derivation(func, &mut origins, inst) {
counts.derived += 1;
} else {
counts.skipped += 1;
}
}
Opcode::AtomicLoad | Opcode::AtomicStore | Opcode::AtomicRmw | Opcode::Cmpxchg
if races.records() =>
{
counts.edged += edges(func, inst);
}
Opcode::Fence if races.records() => {
counts.edged += fenced(func, inst);
}
_ => {}
}
}
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,
origins: &mut origin::Origins,
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 = origins.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 spanned(func: &mut Func, origins: &mut origin::Origins, fill: Inst) -> bool {
let Some(bulk) = func.bulk(fill) else { return false };
let Extra::Mem(info) = func[fill].extra else { return false };
let mut info = func[info];
info.owns = 0;
let span = func.span(fill);
let capability = origins.of(func, bulk.to, fill);
let mut operands = vec![capability, bulk.to];
operands.extend(bulk.length);
let args = func.push_values(&operands);
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, fill);
let args = func.push_values(&[capability, bulk.to]);
let live = func.create_inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[], span);
func.insert_before(live, fill);
true
}
fn recorded(func: &mut Func, plane: &Plane, fill: Inst) -> bool {
let Some(to) = func.bulk(fill).map(|bulk| bulk.to) else { return false };
let Some((made, length)) = copied(func, fill) else { return false };
scrubbed(func, fill, made, to, length);
untyped(func, plane, fill, made, to, length);
true
}
fn scrubbed(func: &mut Func, fill: Inst, made: Inst, to: Value, length: Value) {
let span = func.span(fill);
let args = func.push_values(&[to, length]);
let data = InstData { args, ..InstData::new(Opcode::MetaInit) };
let written = func.create_inst(data, &[], span);
func.insert_after(written, made);
}
fn untyped(func: &mut Func, plane: &Plane, fill: Inst, made: Inst, to: Value, length: Value) {
let span = func.span(fill);
let args = func.push_values(&[to, length]);
let extra = Extra::Node(plane.entry(None));
let data = InstData { args, extra, ..InstData::new(Opcode::MetaType) };
let judged = func.create_inst(data, &[], span);
func.insert_after(judged, made);
}
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 Some(bulk) = func.bulk(copy) else { return false };
let (to, from) = (bulk.to, bulk.with);
let Some((made, length)) = copied(func, copy) else { return false };
let span = func.span(copy);
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 relocation(func: &mut Func, copy: Inst) -> bool {
let Some(bulk) = func.bulk(copy) else { return false };
let (to, from) = (bulk.to, bulk.with);
let Some((made, length)) = copied(func, copy) else { return false };
let span = func.span(copy);
let args = func.push_values(&[to, from, length]);
let data = InstData { args, ..InstData::new(Opcode::CapCopy) };
let carried = func.create_inst(data, &[], span);
func.insert_after(carried, made);
true
}
fn moved(func: &mut Func, copy: Inst) -> bool {
let Some(bulk) = func.bulk(copy) else { return false };
let (to, from) = (bulk.to, bulk.with);
let Some((made, length)) = copied(func, copy) else { return false };
let span = func.span(copy);
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 copied(func: &mut Func, at: Inst) -> Option<(Inst, Value)> {
let bulk = func.bulk(at)?;
if let Some(length) = bulk.length {
return Some((at, length));
}
let Extra::Mem(info) = func[at].extra else { return None };
let size = func[info].size;
if size == 0 {
return None;
}
Some(extent(func, at, size))
}
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 stamped(func: &mut Func, store: Inst, pointer: Value, width: u64) -> bool {
if !pointer_valued(func, store) {
return false;
}
let span = func.span(store);
let (made, length) = extent(func, store, width);
let args = func.push_values(&[pointer, length]);
let data = InstData { args, ..InstData::new(Opcode::MetaEpoch) };
let judged = func.create_inst(data, &[], span);
func.insert_after(judged, made);
true
}
fn saved(
func: &mut Func,
origins: &mut origin::Origins,
store: Inst,
pointer: Value,
container: Option<Value>,
) -> bool {
let Some(container) = container else { return false };
if !pointer_valued(func, store) {
return false;
}
let Some(&value) = func[func[store].args].first() else { return false };
let held = origins.of(func, value, store);
let span = func.span(store);
let args = func.push_values(&[container, pointer, value, held]);
let data = InstData { args, ..InstData::new(Opcode::CapStore) };
let made = func.create_inst(data, &[], span);
func.insert_after(made, store);
true
}
fn recalled(
func: &mut Func,
origins: &mut origin::Origins,
load: Inst,
pointer: Value,
container: Option<Value>,
) -> bool {
let Some(container) = container else { return false };
let Some(value) = func[load].results().next() else { return false };
if !func[value].ty.is_ptr() {
return false;
}
let span = func.span(load);
let args = func.push_values(&[container, pointer, value]);
let data = InstData { args, ..InstData::new(Opcode::CapLoad) };
let made = func.create_inst(data, &[Type::CAP], span);
func.insert_after(made, load);
let Some(held) = func[made].results().next() else { return false };
origins.seed(value, held);
true
}
fn pointer_valued(func: &Func, access: Inst) -> bool {
let carried = match func[access].opcode {
Opcode::Store => func[func[access].args].first().map(|&value| func[value].ty),
Opcode::Load => func[access].results().next().map(|value| func[value].ty),
_ => None,
};
carried.is_some_and(Type::is_ptr)
}
fn raced(
func: &mut Func,
access: Inst,
pointer: Value,
capability: Option<Value>,
width: u64,
) -> bool {
let Some(capability) = capability else { return false };
if !pointer_valued(func, access) {
return false;
}
let Extra::Mem(at) = func[access].extra else { return false };
let mut info = func[at];
info.size = covered(func, access, info.size, width);
if info.size == 0 {
return false;
}
info.tbaa = None;
info.align = 1;
info.owns = 0;
let span = func.span(access);
let args = func.push_values(&[capability, pointer]);
let extra = Extra::Mem(func.add_mem(info));
let data = InstData { args, extra, ..InstData::new(Opcode::CheckRace) };
let asked = func.create_inst(data, &[], span);
func.insert_before(asked, access);
true
}
fn edges(func: &mut Func, atomic: Inst) -> usize {
let Some(pointer) = keyed(func, atomic) else { return 0 };
let order = match func[atomic].extra {
Extra::Mem(at) => func[at].order,
Extra::Rmw(_, at) => func[at].order,
_ => return 0,
};
let span = func.span(atomic);
let mut put = 0;
if order.is_release() {
let args = func.push_values(&[pointer]);
let data = InstData { args, ..InstData::new(Opcode::MetaRelease) };
let made = func.create_inst(data, &[], span);
func.insert_before(made, atomic);
put += 1;
}
if order.is_acquire() {
let args = func.push_values(&[pointer]);
let data = InstData { args, ..InstData::new(Opcode::MetaAcquire) };
let made = func.create_inst(data, &[], span);
func.insert_after(made, atomic);
put += 1;
}
put
}
fn fenced(func: &mut Func, fence: Inst) -> usize {
let Extra::Order(order) = func[fence].extra else { return 0 };
let span = func.span(fence);
let mut put = 0;
if order.is_release() {
let made = func.create_inst(InstData::new(Opcode::MetaFenceRelease), &[], span);
func.insert_before(made, fence);
put += 1;
}
if order.is_acquire() {
let made = func.create_inst(InstData::new(Opcode::MetaFenceAcquire), &[], span);
func.insert_after(made, fence);
put += 1;
}
put
}
fn keyed(func: &Func, atomic: Inst) -> Option<Value> {
let args = &func[func[atomic].args];
let at = match func[atomic].opcode {
Opcode::AtomicStore => 1,
Opcode::AtomicLoad | Opcode::AtomicRmw | Opcode::Cmpxchg => 0,
_ => return None,
};
let &value = args.get(at)?;
func[value].ty.is_ptr().then_some(value)
}
fn derivation(func: &mut Func, origins: &mut origin::Origins, 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 = origins.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")
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Builder, Flags, MemInfo, MemOrder, Meta, MetaNode, PlaneNode, Restrict, RmwOp, 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, Races::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, Races::Off);
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, Races::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 \
check_bounds %1, %0, size 4, align 4\n \
check_live %1, %0\n \
store %2 -> %0, size 4, align 4\n \
%3 = iconst.i64 4\n \
meta_type %0, %3, tbaa !1\n \
%4 = iconst.i64 4\n \
meta_init %0, %4\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
}
fn one_pointer_write(names: &mut Interner) -> Func {
let mut func =
Func::new(names.intern("keep"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
let entry = func.create_block();
let at = func.append_param(entry, Type::PTR);
let value = 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(&[value, at]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
func
}
fn one_number_write(names: &mut Interner) -> Func {
let i32_ = Type::int(32);
let mut func =
Func::new(names.intern("set"), Signature::new().with_params(&[Type::PTR, i32_]));
let entry = func.create_block();
let at = func.append_param(entry, Type::PTR);
let value = func.append_param(entry, i32_);
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(&[value, at]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
func
}
fn one_number_read(names: &mut Interner) -> Func {
let i32_ = Type::int(32);
let mut func = Func::new(
names.intern("count"),
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: 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_);
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, Races::Off),
Counts { checked: 1, live: 1, filled: 1, recalled: 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_store_of_a_pointer_writes_its_capability_into_the_slot_beside_it() {
let mut names = Interner::new();
let mut func = one_pointer_write(&mut names);
let (module, plane) = planed(&mut names, "keep.c");
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
assert_eq!(counts.saved, 1);
assert_eq!(counts.recalled, 0);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("cap_store %3, %0, %1, %2\n return\n"), "{printed}");
}
#[test]
fn a_store_of_something_that_is_not_a_pointer_writes_no_capability() {
let mut names = Interner::new();
let mut func = one_number_write(&mut names);
let (_module, plane) = planed(&mut names, "keep.c");
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
assert_eq!(counts.saved, 0);
}
#[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, Races::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, Races::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, Races::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, Races::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, Races::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, Races::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, Races::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, Races::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_of_a_pointer_records_which_thread_wrote_it_and_a_store_of_a_number_does_not() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "stamp.c");
let i64_ = Type::int(64);
let mut func = Func::new(
names.intern("stamp"),
Signature::new().with_params(&[Type::PTR, Type::PTR, i64_]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let q = 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 extra = Extra::Mem(b.func().add_mem(info));
let args = b.func().push_values(&[q, p]);
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
let args = b.func().push_values(&[v, p]);
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
b.ret(&[]);
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
assert_eq!(counts.wrote, 2, "the init plane takes both, since both wrote bytes");
assert_eq!(counts.stamped, 1, "and the epoch plane takes the one that wrote a pointer");
assert_eq!(counts.watched, 1, "which is also the one that asks what was there before");
let printed = print_func(&module, &func, &names);
assert_eq!(printed.matches("meta_epoch").count(), 1, "{printed}");
assert!(printed.contains("meta_epoch %0, %"), "{printed}");
assert_eq!(printed.matches("check_race").count(), 1, "{printed}");
let asked = printed.find("check_race").expect("the check is in there");
let stamp = printed.find("meta_epoch").expect("so is the recording");
assert!(asked < stamp, "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn nothing_records_into_the_epoch_plane_unless_the_build_asked_for_it() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "quiet.c");
let mut func =
Func::new(names.intern("quiet"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let q = 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(&[q, 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, Races::Off);
assert_eq!((counts.stamped, counts.watched), (0, 0));
let printed = print_func(&module, &func, &names);
assert!(!printed.contains("meta_epoch"), "{printed}");
assert!(!printed.contains("check_race"), "{printed}");
}
#[test]
fn a_read_of_a_pointer_asks_about_races_only_in_the_mode_that_reports_them() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "read.c");
let mut quiet = one_pointer_read(&mut names);
let counts = insert(&mut quiet, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
assert_eq!(counts.watched, 0);
assert!(!print_func(&module, &quiet, &names).contains("check_race"));
let mut asking = one_pointer_read(&mut names);
let counts = insert(&mut asking, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
assert_eq!(counts.watched, 1);
let printed = print_func(&module, &asking, &names);
assert!(printed.contains("check_race %1, %0, size 8, align 1"), "{printed}");
if let Err(errors) = verify_func(&module, &asking, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_read_of_a_number_asks_nothing_even_in_the_mode_that_watches_reads() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "number.c");
let mut func = one_number_read(&mut names);
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
assert_eq!(counts.watched, 0);
assert!(!print_func(&module, &func, &names).contains("check_race"));
}
fn three_atomics(names: &mut Interner, store: MemOrder, load: MemOrder, rmw: MemOrder) -> Func {
let i64_ = Type::int(64);
let mut func =
Func::new(names.intern("atomics"), 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 extra = Extra::Mem(b.func().add_mem(MemInfo { order: store, ..info }));
let args = b.func().push_values(&[v, p]);
b.inst(InstData { args, extra, ..InstData::new(Opcode::AtomicStore) }, &[]);
let extra = Extra::Mem(b.func().add_mem(MemInfo { order: load, ..info }));
let args = b.func().push_values(&[p]);
b.inst(InstData { args, extra, ..InstData::new(Opcode::AtomicLoad) }, &[i64_]);
let at = b.func().add_mem(MemInfo { order: rmw, ..info });
let args = b.func().push_values(&[p, v]);
let extra = Extra::Rmw(RmwOp::Add, at);
b.inst(InstData { args, extra, ..InstData::new(Opcode::AtomicRmw) }, &[i64_]);
b.ret(&[]);
func
}
#[test]
fn a_release_publishes_in_front_of_its_atomic_and_an_acquire_takes_after_it() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "edges.c");
let mut func =
three_atomics(&mut names, MemOrder::Release, MemOrder::Acquire, MemOrder::Relaxed);
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
assert_eq!(counts.edged, 2, "the release and the acquire, and not the relaxed one");
let printed = print_func(&module, &func, &names);
assert_eq!(printed.matches("meta_release").count(), 1, "{printed}");
assert_eq!(printed.matches("meta_acquire").count(), 1, "{printed}");
assert!(printed.contains("meta_release %0\n atomic_store"), "{printed}");
assert!(printed.contains("= atomic_load"), "{printed}");
let read = printed.find("atomic_load").expect("the load is in there");
let took = printed.find("meta_acquire").expect("and so is the edge it takes");
assert!(read < took, "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_read_modify_write_that_orders_both_ways_carries_both_halves_of_an_edge() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "both.c");
let mut func =
three_atomics(&mut names, MemOrder::Relaxed, MemOrder::Relaxed, MemOrder::AcqRel);
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
assert_eq!(counts.edged, 2, "one of each, around the one atomic that orders anything");
let printed = print_func(&module, &func, &names);
let published = printed.find("meta_release").expect("the publishing half is in there");
let changed = printed.find("atomic_rmw").expect("so is the atomic");
let took = printed.find("meta_acquire").expect("and so is the taking half");
assert!(published < changed && changed < took, "{printed}");
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_relaxed_atomic_is_no_edge_and_neither_is_any_atomic_in_a_build_that_is_not_watching() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "relaxed.c");
let mut loose =
three_atomics(&mut names, MemOrder::Relaxed, MemOrder::Relaxed, MemOrder::Relaxed);
let counts = insert(&mut loose, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
assert_eq!(counts.edged, 0);
let printed = print_func(&module, &loose, &names);
assert!(
!printed.contains("meta_release") && !printed.contains("meta_acquire"),
"{printed}"
);
let mut off =
three_atomics(&mut names, MemOrder::SeqCst, MemOrder::SeqCst, MemOrder::SeqCst);
let counts = insert(&mut off, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
assert_eq!(counts.edged, 0);
let printed = print_func(&module, &off, &names);
assert!(
!printed.contains("meta_release") && !printed.contains("meta_acquire"),
"{printed}"
);
}
fn one_fence(names: &mut Interner, order: MemOrder) -> Func {
let mut func = Func::new(names.intern("fenced"), Signature::new());
let entry = func.create_block();
let mut b = Builder::new(&mut func, entry);
b.inst(InstData { extra: Extra::Order(order), ..InstData::new(Opcode::Fence) }, &[]);
b.ret(&[]);
func
}
#[test]
fn a_fence_carries_the_same_edge_as_an_atomic_with_no_object_to_key_it_on() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "fence.c");
let mut func = one_fence(&mut names, MemOrder::SeqCst);
let counts = insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Metadata);
assert_eq!(counts.edged, 2, "seq_cst publishes and takes, so one of each");
let printed = print_func(&module, &func, &names);
assert!(
printed.contains(
"meta_fence_release
fence"
),
"{printed}"
);
assert!(
printed.contains(
"fence seq_cst
meta_fence_acquire"
),
"{printed}"
);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_release_fence_publishes_and_an_acquire_fence_takes_and_neither_does_the_other() {
let mut names = Interner::new();
let (module, plane) = planed(&mut names, "halves.c");
let mut publishing = one_fence(&mut names, MemOrder::Release);
let counts =
insert(&mut publishing, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
assert_eq!(counts.edged, 1);
let printed = print_func(&module, &publishing, &names);
assert!(printed.contains("meta_fence_release"), "{printed}");
assert!(!printed.contains("meta_fence_acquire"), "{printed}");
let mut taking = one_fence(&mut names, MemOrder::Acquire);
let counts = insert(&mut taking, &plane, 8, Subobject::Off, Promise::Off, Races::Pointer);
assert_eq!(counts.edged, 1);
let printed = print_func(&module, &taking, &names);
assert!(printed.contains("meta_fence_acquire"), "{printed}");
assert!(!printed.contains("meta_fence_release"), "{printed}");
let mut off = one_fence(&mut names, MemOrder::SeqCst);
let counts = insert(&mut off, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
assert_eq!(counts.edged, 0);
assert!(!print_func(&module, &off, &names).contains("meta_fence"));
}
#[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, Races::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, Races::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, Races::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, Races::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, Races::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, Races::Off),
Counts { carried: 1, moved: 1, relocated: 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 \
%4 = iconst.i64 24\n \
cap_copy %0, %1, %4\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, Races::Off),
Counts { carried: 1, moved: 1, relocated: 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}");
assert!(printed.contains("cap_copy %0, %1, %4\n"), "{printed}");
}
#[test]
fn a_copy_of_a_length_the_program_works_out_carries_it_with_the_length_it_was_given() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("move"),
Signature::new().with_params(&[Type::PTR, Type::PTR, Type::int(64)]),
);
let entry = func.create_block();
let to = func.append_param(entry, Type::PTR);
let from = func.append_param(entry, Type::PTR);
let length = func.append_param(entry, Type::int(64));
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(&[to, from, length]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Memcpy) }, &[]);
b.ret(&[]);
let (module, plane) = planed(&mut names, "move.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
Counts { carried: 1, moved: 1, relocated: 1, ..Counts::default() }
);
assert_eq!(
print_func(&module, &func, &names),
"func @move(ptr, ptr, i64), linkage(external) {\n\
block0(%0: ptr, %1: ptr, %2: i64):\n \
memcpy %0, %1, %2, align 8\n \
meta_type_copy %0, %1, %2\n \
meta_init_copy %0, %1, %2\n \
cap_copy %0, %1, %2\n \
return\n\
}\n"
);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_fill_is_checked_over_the_bytes_it_covers_and_records_that_it_wrote_them() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("zero"),
Signature::new().with_params(&[Type::PTR, Type::int(8)]),
);
let entry = func.create_block();
let to = func.append_param(entry, Type::PTR);
let byte = func.append_param(entry, Type::int(8));
let info = MemInfo {
size: 24,
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(&[to, byte]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Memset) }, &[]);
b.ret(&[]);
let (module, plane) = planed(&mut names, "zero.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
Counts { checked: 1, live: 1, wrote: 1, judged: 1, ..Counts::default() }
);
assert_eq!(
print_func(&module, &func, &names),
"func @zero(ptr, i8), linkage(external) {\n\
block0(%0: ptr, %1: i8):\n \
%2 = cap_of %0\n \
check_bounds %2, %0, size 24, align 4\n \
check_live %2, %0\n \
memset %0, %1, size 24, align 4\n \
%3 = iconst.i64 24\n \
meta_type %0, %3, tbaa !1\n \
meta_init %0, %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_fill_of_a_length_the_program_works_out_is_checked_over_that_length() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("zero"),
Signature::new().with_params(&[Type::PTR, Type::int(8), Type::int(64)]),
);
let entry = func.create_block();
let to = func.append_param(entry, Type::PTR);
let byte = func.append_param(entry, Type::int(8));
let length = func.append_param(entry, Type::int(64));
let info = MemInfo {
size: 0,
align: 1,
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, byte, length]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Memset) }, &[]);
b.ret(&[]);
let (module, plane) = planed(&mut names, "zero.c");
assert_eq!(
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off),
Counts { checked: 1, live: 1, wrote: 1, judged: 1, ..Counts::default() }
);
assert_eq!(
print_func(&module, &func, &names),
"func @zero(ptr, i8, i64), linkage(external) {\n\
block0(%0: ptr, %1: i8, %2: i64):\n \
%3 = cap_of %0\n \
check_bounds %3, %0, %2, align 1\n \
check_live %3, %0\n \
memset %0, %1, %2, align 1\n \
meta_type %0, %2, tbaa !1\n \
meta_init %0, %2\n \
return\n\
}\n"
);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[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, Races::Off);
assert_eq!(
print_func(&module, &func, &names),
"func @walk(ptr, i64) -> ptr, linkage(external) {\n\
block0(%0: ptr, %1: i64):\n \
%2 = cap_of %0\n \
%3 = iconst.i64 24\n \
%4 = mul.nsw %1, %3\n \
%5 = ptr_add %0, %4\n \
check_deriv %2, %0, %5, %3\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, Races::Off);
let printed = print_func(&module, &func, &names);
assert!(printed.contains("check_deriv %2, %0, %7, %3\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, Races::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 = cap_of %0\n \
%3 = iconst.i64 1\n \
%4 = ptr_add %0, %1\n \
check_deriv %2, %0, %4, %3\n \
return %4\n\
}\n"
);
if let Err(errors) = verify_func(&module, &func, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_walk_and_the_access_through_it_read_the_capability_of_what_it_walked_off() {
let mut names = Interner::new();
let i32_ = Type::int(32);
let mut func = Func::new(
names.intern("through"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[i32_]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, Type::int(64));
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, n]);
let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
let args = b.func().push_values(&[moved]);
let extra = Extra::Mem(b.func().add_mem(info));
let read = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
b.ret(&[read]);
let (module, plane) = planed(&mut names, "through.c");
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
let printed = print_func(&module, &func, &names);
assert_eq!(printed.matches("cap_of").count(), 1, "{printed}");
assert!(printed.contains("check_deriv %2, %0, %4, %3\n"), "{printed}");
assert!(printed.contains("check_bounds %2, %4, size 4, align 4\n"), "{printed}");
}
#[test]
fn a_pointer_read_out_of_memory_takes_its_capability_at_the_read() {
let mut names = Interner::new();
let i32_ = Type::int(32);
let mut func = Func::new(
names.intern("indirect"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let mut b = Builder::new(&mut func, entry);
let args = b.func().push_values(&[p]);
let info = MemInfo {
size: 0,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let extra = Extra::Mem(b.func().add_mem(info));
let held = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, Type::PTR);
let args = b.func().push_values(&[held]);
let info = MemInfo { size: 4, align: 4, ..info };
let extra = Extra::Mem(b.func().add_mem(info));
let read = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
b.ret(&[read]);
let (module, plane) = planed(&mut names, "indirect.c");
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off, Races::Off);
let printed = print_func(&module, &func, &names);
assert!(
printed.contains("%2 = load %0, align 8\n %3 = cap_load %1, %0, %2\n"),
"{printed}"
);
assert!(printed.contains("check_bounds %3, %2, size 4, align 4\n"), "{printed}");
assert_eq!(printed.matches("cap_of").count(), 1, "{printed}");
}
#[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, Races::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, Races::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, Races::Off),
Counts::default()
);
assert_eq!(func.counts(), before);
}
}