use std::collections::HashMap;
use rucc_base::Interner;
use rucc_ir::{
CallInfo, Datum, Extra, Flags, Func, Global, Imm, Inst, InstData, Linkage, Meta, Module,
Opcode, Signature, Type, Value,
};
use crate::plane;
pub const WIDTH: u64 = 16;
pub const SECTION: &str = ".rucc_safety_desc";
const DESCRIPTOR: &str = "__rucc_safety_desc";
const ACCESS: u8 = 1;
const DERIVE: u8 = 2;
const RESTRICT: u8 = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Descriptor {
pub judgement: u8,
pub class: u8,
pub size: u16,
}
pub fn lower(module: &mut Module, names: &mut Interner) -> usize {
let word = Type::int(module.datalayout.pointer_bits);
let mut written: Vec<Descriptor> = Vec::new();
let numbers = plane::numbers(module, names);
for id in module.funcs() {
if module[id].is_declaration() {
continue;
}
calls(&mut module[id], names, word, &numbers, &mut written);
}
for (index, row) in written.iter().enumerate() {
emit(module, names, index, *row);
}
written.len()
}
fn calls(
func: &mut Func,
names: &mut Interner,
word: Type,
numbers: &HashMap<Meta, u32>,
table: &mut Vec<Descriptor>,
) {
let insts: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for &inst in &insts {
match func[inst].opcode {
Opcode::CheckBounds => bounds(func, names, word, table, inst),
Opcode::CheckLive => live(func, names, table, inst),
Opcode::CheckDeriv => deriv(func, names, word, table, inst),
Opcode::CheckType => typed(func, names, word, numbers, table, inst),
Opcode::CheckInit => began(func, names, word, table, inst),
Opcode::CheckRestrictRead => promised(func, names, word, table, inst, false),
Opcode::CheckRestrictWrite => promised(func, names, word, table, inst, true),
Opcode::RestrictEnter => opened(func, names, inst),
Opcode::RestrictLeave => closed(func, names, inst),
Opcode::MetaType => judgement(func, names, word, numbers, inst),
Opcode::MetaTypeCopy => carriage(func, names, word, inst),
Opcode::MetaInit => written(func, names, word, inst),
Opcode::MetaInitCopy => carried(func, names, word, inst),
Opcode::CapExtent => extent(func, names, word, inst, "__rucc_extent"),
Opcode::CapExtentBack => extent(func, names, word, inst, "__rucc_extent_back"),
_ => {}
}
}
for &inst in &insts {
if func[inst].opcode == Opcode::CapOf {
func.remove_inst(inst);
}
}
}
fn bounds(
func: &mut Func,
names: &mut Interner,
word: Type,
table: &mut Vec<Descriptor>,
inst: Inst,
) {
let args = &func[func[inst].args];
let (Some(&pointer), computed) = (args.get(1), args.get(2).copied()) else { return };
let Extra::Mem(mem) = func[inst].extra else { return };
let size = func[mem].size;
let row = Descriptor {
judgement: ACCESS,
class: 0,
size: if computed.is_some() { 0 } else { u16::try_from(size).unwrap_or(u16::MAX) },
};
let desc = record(func, names, table, inst, row);
let bytes = match computed {
Some(value) => fitted(func, inst, value, word),
None => konst(func, inst, Imm::int(i128::from(size), word), word),
};
let claim = if computed.is_some() { 1 } else { i128::from(func[mem].align) };
let align = konst(func, inst, Imm::int(claim, word), word);
let params = &[Type::PTR, word, word, Type::PTR];
call(func, names, inst, "__rucc_check_bounds", params, &[], &[pointer, bytes, align, desc]);
}
fn fitted(func: &mut Func, inst: Inst, value: Value, word: Type) -> Value {
let ty = func[value].ty;
if ty == word {
return value;
}
let opcode = if ty.bits() > word.bits() { Opcode::Trunc } else { Opcode::ZExt };
let span = func.span(inst);
let args = func.push_values(&[value]);
let made = func.create_inst(InstData { args, ..InstData::new(opcode) }, &[word], span);
func.insert_before(made, inst);
func[made].results().next().expect("a cast created with one result has one")
}
fn live(func: &mut Func, names: &mut Interner, table: &mut Vec<Descriptor>, inst: Inst) {
let [_capability, pointer] = func[func[inst].args] else { return };
let row = Descriptor { judgement: ACCESS, class: 0, size: 0 };
let desc = record(func, names, table, inst, row);
call(func, names, inst, "__rucc_check_live", &[Type::PTR, Type::PTR], &[], &[pointer, desc]);
}
fn deriv(
func: &mut Func,
names: &mut Interner,
word: Type,
table: &mut Vec<Descriptor>,
inst: Inst,
) {
let [_capability, base, derived, stride] = func[func[inst].args] else { return };
let row = Descriptor { judgement: DERIVE, class: 0, size: 0 };
let desc = record(func, names, table, inst, row);
let params = &[Type::PTR, Type::PTR, word, Type::PTR];
call(func, names, inst, "__rucc_check_deriv", params, &[], &[base, derived, stride, desc]);
}
fn typed(
func: &mut Func,
names: &mut Interner,
word: Type,
numbers: &HashMap<Meta, u32>,
table: &mut Vec<Descriptor>,
inst: Inst,
) {
let [_capability, pointer] = func[func[inst].args] else { return };
let Extra::Mem(mem) = func[inst].extra else { return };
let size = func[mem].size;
let Some(node) = func[mem].tbaa else { return };
let Some(&number) = numbers.get(&node) else { return };
let row = Descriptor {
judgement: ACCESS,
class: 0,
size: u16::try_from(size).unwrap_or(u16::MAX),
};
let desc = record(func, names, table, inst, row);
let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
let small = Type::int(32);
let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
let params = &[Type::PTR, word, small, Type::PTR];
call(func, names, inst, "__rucc_check_type", params, &[], &[pointer, bytes, ty, desc]);
}
fn began(
func: &mut Func,
names: &mut Interner,
word: Type,
table: &mut Vec<Descriptor>,
inst: Inst,
) {
let [_capability, pointer] = func[func[inst].args] else { return };
let Extra::Mem(mem) = func[inst].extra else { return };
let size = func[mem].size;
let row = Descriptor {
judgement: ACCESS,
class: 0,
size: u16::try_from(size).unwrap_or(u16::MAX),
};
let desc = record(func, names, table, inst, row);
let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
let params = &[Type::PTR, word, Type::PTR];
call(func, names, inst, "__rucc_check_init", params, &[], &[pointer, bytes, desc]);
}
fn tag(clique: u16, base: u16) -> u32 {
(u32::from(clique) << 16) | u32::from(base)
}
fn promised(
func: &mut Func,
names: &mut Interner,
word: Type,
table: &mut Vec<Descriptor>,
inst: Inst,
write: bool,
) {
let [pointer] = func[func[inst].args] else { return };
let Extra::Mem(mem) = func[inst].extra else { return };
let size = func[mem].size;
let named = func[mem].restrict;
let row = Descriptor {
judgement: RESTRICT,
class: 0,
size: u16::try_from(size).unwrap_or(u16::MAX),
};
let desc = record(func, names, table, inst, row);
let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
let small = Type::int(32);
let which =
konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
let wrote = konst(func, inst, Imm::int(i128::from(u8::from(write)), small), small);
let params = &[Type::PTR, word, small, small, Type::PTR];
let args = &[pointer, bytes, which, wrote, desc];
call(func, names, inst, "__rucc_check_restrict", params, &[], args);
}
fn opened(func: &mut Func, names: &mut Interner, inst: Inst) {
let [scope] = func[func[inst].args] else { return };
let Extra::Mem(mem) = func[inst].extra else { return };
let named = func[mem].restrict;
let small = Type::int(32);
let which =
konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
call(func, names, inst, "__rucc_restrict_enter", &[Type::PTR, small], &[], &[scope, which]);
}
fn closed(func: &mut Func, names: &mut Interner, inst: Inst) {
let [scope] = func[func[inst].args] else { return };
call(func, names, inst, "__rucc_restrict_leave", &[Type::PTR], &[], &[scope]);
}
fn judgement(
func: &mut Func,
names: &mut Interner,
word: Type,
numbers: &HashMap<Meta, u32>,
inst: Inst,
) {
let [pointer, length] = func[func[inst].args] else { return };
let Extra::Node(node) = func[inst].extra else { return };
let Some(&number) = numbers.get(&node) else { return };
let bytes = fitted(func, inst, length, word);
let small = Type::int(32);
let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
let params = &[Type::PTR, word, small];
call(func, names, inst, "__rucc_meta_type", params, &[], &[pointer, bytes, ty]);
}
fn carriage(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
let [to, from, length] = func[func[inst].args] else { return };
let bytes = fitted(func, inst, length, word);
let params = &[Type::PTR, Type::PTR, word];
call(func, names, inst, "__rucc_meta_type_copy", params, &[], &[to, from, bytes]);
}
fn written(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
let [pointer, length] = func[func[inst].args] else { return };
let bytes = fitted(func, inst, length, word);
let params = &[Type::PTR, word];
call(func, names, inst, "__rucc_meta_init", params, &[], &[pointer, bytes]);
}
fn carried(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
let [to, from, length] = func[func[inst].args] else { return };
let bytes = fitted(func, inst, length, word);
let params = &[Type::PTR, Type::PTR, word];
call(func, names, inst, "__rucc_meta_init_copy", params, &[], &[to, from, bytes]);
}
fn extent(func: &mut Func, names: &mut Interner, word: Type, inst: Inst, called: &str) {
let [_capability, address, want] = func[func[inst].args] else { return };
let asked = fitted(func, inst, want, word);
let result = func[inst].results().next().expect("an extent query produces one value");
let ty = func[result].ty;
let params = &[Type::PTR, word];
if ty == word {
call(func, names, inst, called, params, &[word], &[address, asked]);
return;
}
let made = calling(func, names, called, params, &[word], &[address, asked]);
let holder = func.create_inst(made, &[word], func.span(inst));
func.insert_before(holder, inst);
let got = func[holder].results().next().expect("a call returning one value produces one");
let opcode = if word.bits() > ty.bits() { Opcode::Trunc } else { Opcode::ZExt };
let args = func.push_values(&[got]);
func[inst] = InstData { args, ..InstData::new(opcode) };
}
fn record(
func: &mut Func,
names: &mut Interner,
table: &mut Vec<Descriptor>,
inst: Inst,
row: Descriptor,
) -> Value {
let name = names.intern(&label(table.len()));
table.push(row);
let span = func.span(inst);
let data = InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) };
let made = func.create_inst(data, &[Type::PTR], span);
func.insert_before(made, inst);
func[made].results().next().expect("an address created with one result has one")
}
fn label(index: usize) -> String {
format!("{DESCRIPTOR}_{index}")
}
fn konst(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
let span = func.span(inst);
let extra = Extra::Imm(func.add_imm(imm));
let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
func.insert_before(made, inst);
func[made].results().next().expect("a constant created with one result has one")
}
fn call(
func: &mut Func,
names: &mut Interner,
inst: Inst,
routine: &str,
params: &[Type],
returns: &[Type],
args: &[Value],
) {
let made = calling(func, names, routine, params, returns, args);
let data = &mut func[inst];
data.opcode = made.opcode;
data.args = made.args;
data.extra = made.extra;
data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
}
fn calling(
func: &mut Func,
names: &mut Interner,
routine: &str,
params: &[Type],
returns: &[Type],
args: &[Value],
) -> InstData {
let sig = func.add_signature(Signature::new().with_params(params).with_returns(returns));
let callee = names.intern(routine);
let varargs = func.push_abis(&[]);
let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
let args = func.push_values(args);
InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) }
}
fn emit(module: &mut Module, names: &mut Interner, index: usize, row: Descriptor) {
let byte = Type::int(8);
let half = Type::int(16);
let judgement = module.add_imm(Imm::int(i128::from(row.judgement), byte));
let class = module.add_imm(Imm::int(i128::from(row.class), byte));
let size = module.add_imm(Imm::int(i128::from(row.size), half));
let image = [
Datum::Scalar { ty: byte, value: judgement },
Datum::Scalar { ty: byte, value: class },
Datum::Scalar { ty: half, value: size },
Datum::Zero(4),
Datum::Zero(8),
];
let init = module.push_data(&image);
let mut global = Global::new(names.intern(&label(index)), WIDTH, 8);
global.linkage = Linkage::Internal;
global.constant = true;
global.section = Some(names.intern(SECTION));
global.init = Some(init);
module.add_global(global);
}
#[cfg(test)]
mod tests {
use rucc_ir::{
Builder, MemInfo, MemOrder, MetaNode, Restrict, TbaaNode, print_func, verify_func,
};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::*;
use crate::{Plane, Promise, Subobject, insert};
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
fn checked(names: &mut Interner) -> Module {
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: 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_);
b.ret(&[loaded]);
insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
let mut module = Module::new(names.intern("read.c"), &target());
module.add_func(func);
module
}
fn unaligned(names: &mut Interner) -> Module {
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: 4,
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(&[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]);
insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
let mut module = Module::new(names.intern("read.c"), &target());
module.add_func(func);
module
}
fn planeless(names: &mut Interner) -> (Plane, HashMap<Meta, u32>) {
let mut module = Module::new(names.intern("planeless.c"), &target());
let plane = Plane::build(&mut module);
let numbers = plane::numbers(&module, names);
(plane, numbers)
}
fn copied(names: &mut Interner) -> Module {
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::Memcpy) }, &[]);
b.ret(&[]);
insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
let mut module = Module::new(names.intern("move.c"), &target());
module.add_func(func);
module
}
fn stored(names: &mut Interner) -> Module {
let mut module = Module::new(names.intern("write.c"), &target());
let plane = Plane::build(&mut module);
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(&[]);
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
module.add_func(func);
module
}
fn asking_the_plane(names: &mut Interner) -> Module {
let mut module = Module::new(names.intern("read.c"), &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);
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: 4,
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(&[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]);
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
module.add_func(func);
module
}
fn marker(b: &mut Builder<'_>, opcode: Opcode, info: Option<MemInfo>, on: &[Value]) {
let args = b.func().push_values(on);
let extra = match info {
Some(info) => Extra::Mem(b.func().add_mem(info)),
None => Extra::None,
};
b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
}
fn promising(names: &mut Interner) -> Module {
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 empty = MemInfo {
size: 0,
align: 1,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let slot = MemInfo { size: 112, align: 8, ..empty };
let mut b = Builder::new(&mut func, entry);
let extra = Extra::Mem(b.func().add_mem(slot));
let scope = b.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR);
let read =
MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 2 }, ..empty };
let writ =
MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 1 }, ..empty };
let opening = MemInfo { restrict: Restrict { clique: 1, base: 2 }, ..slot };
marker(&mut b, Opcode::RestrictEnter, Some(opening), &[scope]);
marker(&mut b, Opcode::CheckRestrictRead, Some(read), &[from]);
let args = b.func().push_values(&[from]);
let extra = Extra::Mem(b.func().add_mem(read));
let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
marker(&mut b, Opcode::CheckRestrictWrite, Some(writ), &[to]);
let args = b.func().push_values(&[loaded, to]);
let extra = Extra::Mem(b.func().add_mem(writ));
b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
marker(&mut b, Opcode::RestrictLeave, None, &[scope]);
b.ret(&[]);
let mut module = Module::new(names.intern("kernel.c"), &target());
module.add_func(func);
module
}
#[test]
fn a_restrict_check_becomes_the_call_that_says_which_pointer_reached_where() {
let mut names = Interner::new();
let mut module = promising(&mut names);
assert_eq!(lower(&mut module, &mut names), 2);
let id = module.funcs().next().expect("the module has one function");
assert_eq!(
print_func(&module, &module[id], &names),
"func @kernel(ptr, ptr), linkage(external) {\n\
block0(%0: ptr, %1: ptr):\n \
%2 = alloca, size 112, align 8\n \
%3 = iconst.i32 65538\n \
call @__rucc_restrict_enter(%2, %3) : (ptr, i32)\n \
%4 = global_addr @__rucc_safety_desc_0\n \
%5 = iconst.i64 4\n \
%6 = iconst.i32 65538\n \
%7 = iconst.i32 0\n \
call @__rucc_check_restrict(%1, %5, %6, %7, %4) : (ptr, i64, i32, i32, ptr)\n \
%8 = load.i32 %1, size 4, align 4, restrict(1, 2)\n \
%9 = global_addr @__rucc_safety_desc_1\n \
%10 = iconst.i64 4\n \
%11 = iconst.i32 65537\n \
%12 = iconst.i32 1\n \
call @__rucc_check_restrict(%0, %10, %11, %12, %9) : (ptr, i64, i32, i32, ptr)\n \
store %8 -> %0, size 4, align 4, restrict(1, 1)\n \
call @__rucc_restrict_leave(%2) : (ptr)\n \
return\n\
}\n"
);
if let Err(errors) = verify_func(&module, &module[id], &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn the_judgement_a_restrict_check_names_is_the_one_about_the_pair() {
let mut names = Interner::new();
let mut module = promising(&mut names);
lower(&mut module, &mut names);
let rows: Vec<u8> = module
.globals()
.map(|id| {
let init = module[id].init.expect("a descriptor is a definition");
match module[init][0] {
Datum::Scalar { value, .. } => {
u8::try_from(module[value].bits()).expect("a judgement is one byte")
}
_ => panic!("a descriptor starts with its judgement"),
}
})
.collect();
assert_eq!(rows, [RESTRICT, RESTRICT]);
}
#[test]
fn the_two_numbers_are_packed_the_way_the_runtime_unpacks_them() {
assert_eq!(tag(1, 2), 0x0001_0002);
assert_eq!(tag(0xffff, 0xffff), u32::MAX);
assert_eq!(tag(0, 0), 0);
}
#[test]
fn a_read_of_the_plane_becomes_the_call_that_carries_the_type_asked_about() {
let mut names = Interner::new();
let mut module = asking_the_plane(&mut names);
assert_eq!(lower(&mut module, &mut names), 4);
let number = i32::from_ne_bytes(plane::identifier("int").to_ne_bytes());
let id = module.funcs().next().expect("the module has one function");
assert_eq!(
print_func(&module, &module[id], &names),
format!(
"func @read(ptr) -> i32, linkage(external) {{\n\
block0(%0: ptr):\n \
%1 = global_addr @__rucc_safety_desc_0\n \
%2 = iconst.i64 4\n \
%3 = iconst.i64 4\n \
call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n \
%4 = global_addr @__rucc_safety_desc_1\n \
call @__rucc_check_live(%0, %4) : (ptr, ptr)\n \
%5 = global_addr @__rucc_safety_desc_2\n \
%6 = iconst.i64 4\n \
%7 = iconst.i32 {number}\n \
call @__rucc_check_type(%0, %6, %7, %5) : (ptr, i64, i32, ptr)\n \
%8 = global_addr @__rucc_safety_desc_3\n \
%9 = iconst.i64 4\n \
call @__rucc_check_init(%0, %9, %8) : (ptr, i64, ptr)\n \
%10 = load.i32 %0, size 4, align 4, tbaa !1\n \
return %10\n\
}}\n"
)
);
if let Err(errors) = verify_func(&module, &module[id], &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn the_judgement_a_type_check_names_is_the_one_about_the_planes() {
let mut names = Interner::new();
let mut module = asking_the_plane(&mut names);
lower(&mut module, &mut names);
let rows: Vec<u8> = module
.globals()
.map(|id| {
let init = module[id].init.expect("a descriptor is a definition");
match module[init][0] {
Datum::Scalar { value, .. } => {
u8::try_from(module[value].bits()).expect("a judgement is one byte")
}
_ => panic!("a descriptor starts with its judgement"),
}
})
.collect();
assert_eq!(rows, [ACCESS, ACCESS, ACCESS, ACCESS]);
}
#[test]
fn a_store_becomes_the_calls_that_record_what_it_wrote() {
let mut names = Interner::new();
let mut module = stored(&mut names);
assert_eq!(lower(&mut module, &mut names), 2);
let id = module.funcs().next().expect("the module has one function");
let printed = print_func(&module, &module[id], &names);
assert!(
printed.contains("call @__rucc_meta_type(%0, %6, %7) : (ptr, i64, i32)\n"),
"{printed}"
);
assert!(printed.contains("call @__rucc_meta_init(%0, %8) : (ptr, i64)\n"), "{printed}");
if let Err(errors) = verify_func(&module, &module[id], &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn a_copy_becomes_the_calls_that_move_the_planes_across() {
let mut names = Interner::new();
let mut module = copied(&mut names);
assert_eq!(lower(&mut module, &mut names), 0);
let id = module.funcs().next().expect("the module has one function");
assert_eq!(
print_func(&module, &module[id], &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 \
call @__rucc_meta_type_copy(%0, %1, %2) : (ptr, ptr, i64)\n \
%3 = iconst.i64 24\n \
call @__rucc_meta_init_copy(%0, %1, %3) : (ptr, ptr, i64)\n \
return\n\
}\n"
);
if let Err(errors) = verify_func(&module, &module[id], &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn the_alignment_that_goes_through_is_the_one_the_access_may_assume() {
let mut names = Interner::new();
let mut module = unaligned(&mut names);
assert_eq!(lower(&mut module, &mut names), 3);
let id = module.funcs().next().expect("the module has one function");
let printed = print_func(&module, &module[id], &names);
assert!(printed.contains("%2 = iconst.i64 4\n"), "{printed}");
assert!(printed.contains("%3 = iconst.i64 1\n"), "{printed}");
assert!(
printed.contains("call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n"),
"{printed}"
);
}
#[test]
fn every_check_becomes_a_call_carrying_the_descriptor_it_is_described_by() {
let mut names = Interner::new();
let mut module = checked(&mut names);
assert_eq!(lower(&mut module, &mut names), 3);
let id = module.funcs().next().expect("the module has one function");
assert_eq!(
print_func(&module, &module[id], &names),
"func @read(ptr) -> i32, linkage(external) {\n\
block0(%0: ptr):\n \
%1 = global_addr @__rucc_safety_desc_0\n \
%2 = iconst.i64 4\n \
%3 = iconst.i64 4\n \
call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n \
%4 = global_addr @__rucc_safety_desc_1\n \
call @__rucc_check_live(%0, %4) : (ptr, ptr)\n \
%5 = global_addr @__rucc_safety_desc_2\n \
%6 = iconst.i64 4\n \
call @__rucc_check_init(%0, %6, %5) : (ptr, i64, ptr)\n \
%7 = load.i32 %0, size 4, align 4\n \
return %7\n\
}\n"
);
}
#[test]
fn the_capabilities_the_checks_were_reading_are_taken_out() {
let mut names = Interner::new();
let mut module = checked(&mut names);
lower(&mut module, &mut names);
let id = module.funcs().next().expect("the module has one function");
let func = &module[id];
let left: Vec<Opcode> = func
.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.map(|inst| func[inst].opcode)
.collect();
assert!(!left.contains(&Opcode::CapOf), "{left:?}");
}
#[test]
fn what_it_produces_is_a_module_the_verifier_believes() {
let mut names = Interner::new();
let mut module = checked(&mut names);
lower(&mut module, &mut names);
let id = module.funcs().next().expect("the module has one function");
if let Err(errors) = verify_func(&module, &module[id], &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn the_section_is_one_descriptor_per_check_and_nothing_else() {
let mut names = Interner::new();
let mut module = checked(&mut names);
let rows = lower(&mut module, &mut names);
let globals: Vec<_> = module.globals().collect();
assert_eq!(globals.len(), rows);
for (index, id) in globals.iter().enumerate() {
let desc = &module[*id];
assert_eq!(names.resolve(desc.name), label(index));
assert_eq!(
names.resolve(desc.section.expect("a descriptor names its section")),
SECTION
);
assert_eq!(desc.linkage, Linkage::Internal);
assert!(desc.constant);
assert_eq!(desc.align, 8);
assert_eq!(desc.size, WIDTH);
let init = desc.init.expect("a descriptor is a definition");
let written: u64 = module[init].iter().map(|datum| datum.size(&module)).sum();
assert_eq!(written, WIDTH);
}
}
#[test]
fn the_judgement_a_descriptor_names_is_the_one_the_check_decides() {
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 (plane, numbers) = planeless(&mut names);
insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
let mut table = Vec::new();
calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
assert_eq!(table, [Descriptor { judgement: DERIVE, class: 0, size: 0 }]);
}
#[test]
fn a_check_over_a_length_the_program_worked_out_passes_that_length_along() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("sweep"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]),
);
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 of = b.unary(Opcode::CapOf, p, Type::CAP);
let args = b.func().push_values(&[of, p, n]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
b.ret(&[]);
let mut table = Vec::new();
let numbers = planeless(&mut names).1;
calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
assert_eq!(table, [Descriptor { judgement: ACCESS, class: 0, size: 0 }]);
let mut module = Module::new(names.intern("sweep.c"), &target());
module.add_func(func);
let id = module.funcs().next().expect("the module has one function");
assert_eq!(
print_func(&module, &module[id], &names),
"func @sweep(ptr, i64), linkage(external) {\n\
block0(%0: ptr, %1: i64):\n \
%2 = global_addr @__rucc_safety_desc_0\n \
%3 = iconst.i64 1\n \
call @__rucc_check_bounds(%0, %1, %3, %2) : (ptr, i64, i64, ptr)\n \
return\n\
}\n"
);
}
#[test]
fn a_length_wider_than_the_word_is_cut_down_to_it() {
let mut names = Interner::new();
let mut func = Func::new(
names.intern("sweep"),
Signature::new().with_params(&[Type::PTR, Type::int(64)]),
);
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 of = b.unary(Opcode::CapOf, p, Type::CAP);
let args = b.func().push_values(&[of, p, n]);
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
b.ret(&[]);
let mut table = Vec::new();
let numbers = planeless(&mut names).1;
calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
let opcodes: Vec<Opcode> = func
.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.map(|inst| func[inst].opcode)
.collect();
assert!(opcodes.contains(&Opcode::Trunc), "{opcodes:?}");
}
fn asking(names: &mut Interner, ty: Type) -> Func {
let mut func = Func::new(
names.intern("cover"),
Signature::new().with_params(&[Type::PTR, ty]).with_returns(&[ty]),
);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let want = func.append_param(entry, ty);
let mut b = Builder::new(&mut func, entry);
let of = b.unary(Opcode::CapOf, p, Type::CAP);
let args = b.func().push_values(&[of, p, want]);
let got = b.value(InstData { args, ..InstData::new(Opcode::CapExtent) }, ty);
b.ret(&[got]);
func
}
#[test]
fn the_extent_query_becomes_a_call_that_carries_no_descriptor() {
let mut names = Interner::new();
let mut func = asking(&mut names, Type::int(64));
let mut table = Vec::new();
let numbers = planeless(&mut names).1;
calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
assert!(table.is_empty(), "{table:?}");
let mut module = Module::new(names.intern("cover.c"), &target());
module.add_func(func);
let id = module.funcs().next().expect("the module has one function");
assert_eq!(
print_func(&module, &module[id], &names),
"func @cover(ptr, i64) -> i64, linkage(external) {\n\
block0(%0: ptr, %1: i64):\n \
%2 = call @__rucc_extent(%0, %1) : (ptr, i64) -> i64\n \
return %2\n\
}\n"
);
if let Err(errors) = verify_func(&module, &module[id], &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
#[test]
fn an_extent_asked_for_in_a_width_the_target_does_not_have_is_converted_back() {
let mut names = Interner::new();
let mut func = asking(&mut names, Type::int(64));
let mut table = Vec::new();
let numbers = planeless(&mut names).1;
calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
let opcodes: Vec<Opcode> = func
.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.map(|inst| func[inst].opcode)
.collect();
assert!(opcodes.contains(&Opcode::Trunc), "the limit goes in narrowed: {opcodes:?}");
assert!(opcodes.contains(&Opcode::ZExt), "and the answer comes back widened: {opcodes:?}");
assert!(
!opcodes.contains(&Opcode::CapExtent),
"with nothing left of the query: {opcodes:?}"
);
}
#[test]
fn a_module_with_nothing_to_check_gets_no_section_at_all() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("empty.c"), &target());
assert_eq!(lower(&mut module, &mut names), 0);
assert_eq!(module.globals().count(), 0);
}
}