use std::collections::HashMap;
use rucc_base::Symbol;
use rucc_ir::{Extra, Func, Inst, Module, Opcode, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Frame {
Elided,
Checked,
Outside,
Unknown,
Pointerless,
}
#[must_use]
pub fn remaining(module: &Module) -> HashMap<Symbol, usize> {
module
.funcs()
.filter(|&id| !module[id].is_declaration())
.map(|id| (module[id].name, checks_left(&module[id])))
.collect()
}
#[must_use]
pub fn wanted(func: &Func, inst: Inst, left: &HashMap<Symbol, usize>) -> Option<Frame> {
if !matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect) {
return None;
}
if pointers(func, inst).next().is_none() {
return Some(Frame::Pointerless);
}
let named = callee(func, inst);
match named.and_then(|name| left.get(&name)) {
Some(0) => Some(Frame::Elided),
Some(_) => Some(Frame::Checked),
None if named.is_none() => Some(Frame::Unknown),
None => Some(Frame::Outside),
}
}
#[must_use]
pub fn callee(func: &Func, inst: Inst) -> Option<Symbol> {
if func[inst].opcode == Opcode::CallIndirect {
return None;
}
match func[inst].extra {
Extra::Call(at) => func[at].callee,
_ => None,
}
}
pub fn pointers<'a>(func: &'a Func, inst: Inst) -> impl Iterator<Item = Value> + 'a {
let indirect = usize::from(func[inst].opcode == Opcode::CallIndirect);
func[func[inst].args].iter().skip(indirect).copied().filter(|&value| func[value].ty.is_ptr())
}
pub fn checks_left(func: &Func) -> usize {
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<Inst>>())
.filter(|&inst| {
matches!(
func[inst].opcode,
Opcode::CheckBounds
| Opcode::CheckLive
| Opcode::CheckDeriv
| Opcode::CheckType
| Opcode::CheckInit
)
})
.count()
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, CallInfo, InstData, MemInfo, MemOrder, Restrict, Signature, Type};
use super::*;
fn through_a_pointer(names: &mut Interner) -> Func {
let mut func =
Func::new(names.intern("f"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
let entry = func.create_block();
let at = func.append_param(entry, Type::PTR);
let p = func.append_param(entry, Type::PTR);
let sig = func.add_signature(Signature::new().with_params(&[Type::PTR]));
let varargs = func.push_abis(&[]);
let info = func.add_call(CallInfo { callee: None, signature: sig, varargs });
let args = func.push_values(&[at, p]);
let mut b = Builder::new(&mut func, entry);
let data =
InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) };
b.inst(data, &[]);
b.ret(&[]);
func
}
fn only(func: &Func) -> Inst {
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<Inst>>())
.find(|&inst| matches!(func[inst].opcode, Opcode::Call | Opcode::CallIndirect))
.expect("the function calls something")
}
#[test]
fn the_address_an_indirect_call_jumps_to_is_not_one_of_the_pointers_it_hands_over() {
let mut names = Interner::new();
let func = through_a_pointer(&mut names);
let call = only(&func);
assert_eq!(pointers(&func, call).count(), 1);
assert_eq!(callee(&func, call), None);
}
#[test]
fn a_call_through_a_pointer_is_one_nothing_here_can_ask_about() {
let mut names = Interner::new();
let func = through_a_pointer(&mut names);
let call = only(&func);
assert_eq!(wanted(&func, call, &HashMap::new()), Some(Frame::Unknown));
}
#[test]
fn something_that_is_not_a_call_is_none_of_the_five() {
let mut names = Interner::new();
let func = through_a_pointer(&mut names);
let ret = func
.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<Inst>>())
.find(|&inst| func[inst].opcode == Opcode::Return)
.expect("the function returns");
assert_eq!(wanted(&func, ret, &HashMap::new()), None);
}
fn checking(names: &mut Interner, opcode: Opcode) -> Func {
let mut func = Func::new(names.intern("g"), Signature::new().with_params(&[Type::PTR]));
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let mut b = Builder::new(&mut func, entry);
let cap = b.value(InstData::new(Opcode::CapNull), Type::CAP);
let args = b.func().push_values(&[cap, p]);
let info = MemInfo {
size: 4,
align: 4,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
let extra = Extra::Mem(b.func().add_mem(info));
b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
b.ret(&[]);
func
}
#[test]
fn every_class_of_check_is_one_that_keeps_the_frame() {
let mut names = Interner::new();
for opcode in [
Opcode::CheckBounds,
Opcode::CheckLive,
Opcode::CheckDeriv,
Opcode::CheckType,
Opcode::CheckInit,
] {
let func = checking(&mut names, opcode);
assert_eq!(checks_left(&func), 1, "{opcode:?}");
}
}
#[test]
fn a_function_whose_only_checks_are_restrict_promises_wants_no_frame() {
let mut names = Interner::new();
for opcode in [Opcode::CheckRestrictRead, Opcode::CheckRestrictWrite] {
let func = checking(&mut names, opcode);
assert_eq!(checks_left(&func), 0, "{opcode:?}");
}
}
}