use std::collections::HashMap;
use rucc_base::Symbol;
use rucc_ir::{Extra, Func, Imm, Inst, InstData, Module, Opcode, Type, Value};
use crate::frame::ARGS;
use crate::{origin, slot};
#[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);
let signature = match func[inst].extra {
Extra::Call(at) => Some(func[at].signature),
_ => None,
};
func[func[inst].args].iter().skip(indirect).enumerate().filter_map(move |(nth, &value)| {
let named = func[signature?].params.get(nth)?;
(named.ty == Type::PTR).then_some(value)
})
}
pub fn checks_left(func: &Func) -> usize {
all(func)
.into_iter()
.filter(|&inst| {
matches!(
func[inst].opcode,
Opcode::CheckBounds
| Opcode::CheckLive
| Opcode::CheckDeriv
| Opcode::CheckType
| Opcode::CheckInit
)
})
.count()
}
pub fn arrange(module: &mut Module) -> usize {
let left = remaining(module);
let word = Type::int(module.datalayout.pointer_bits);
let mut published = 0;
for id in module.funcs() {
if module[id].is_declaration() {
continue;
}
published += one(&mut module[id], &left, word);
}
published
}
fn one(func: &mut Func, left: &HashMap<Symbol, usize>, word: Type) -> usize {
if checks_left(func) > 0 {
from_the_frame(func, word);
}
let held = origin::existing(func);
let mut published = 0;
for inst in all(func) {
if func[inst].opcode == Opcode::TailCall {
continue;
}
match wanted(func, inst, left) {
Some(Frame::Checked) => {
if over(func, inst, &held) {
published += 1;
from_the_call(func, inst);
}
}
Some(Frame::Outside | Frame::Unknown) => empty(func, inst),
Some(Frame::Elided | Frame::Pointerless) | None => {}
}
}
giving_back(func, &held);
published
}
fn from_the_frame(func: &mut Func, word: Type) -> usize {
let Some(entry) = func.entry() else { return 0 };
let mut position: HashMap<Value, usize> = HashMap::new();
let mut at = 0;
for ¶m in &func[entry].params {
if !func[param].ty.is_ptr() {
continue;
}
if at < ARGS {
position.insert(param, at);
}
at += 1;
}
if position.is_empty() {
return 0;
}
let mut done = 0;
for inst in all(func) {
if func[inst].opcode != Opcode::CapOf {
continue;
}
let Some(&pointer) = func[func[inst].args].first() else { continue };
let Some(&nth) = position.get(&pointer) else { continue };
let Ok(nth) = i128::try_from(nth) else { continue };
let index = slot::konst(func, inst, Imm::int(nth, word), word);
let args = func.push_values(&[pointer, index]);
func[inst].opcode = Opcode::CapArg;
func[inst].args = args;
done += 1;
}
done
}
fn from_the_call(func: &mut Func, inst: Inst) -> bool {
let Some(result) = func[inst].results().next() else { return false };
if !func[result].ty.is_ptr() {
return false;
}
let Some(next) = behind(func, inst) else { return false };
if func[next].opcode != Opcode::CapOf {
return false;
}
if func[func[next].args].first() != Some(&result) {
return false;
}
func[next].opcode = Opcode::CapResult;
true
}
fn giving_back(func: &mut Func, held: &HashMap<Value, Value>) -> usize {
let mut done = 0;
for inst in all(func) {
if func[inst].opcode != Opcode::Return {
continue;
}
let returned: Vec<Value> = func[func[inst].args].to_vec();
let Some(&pointer) = returned.iter().find(|&&value| func[value].ty.is_ptr()) else {
continue;
};
let Some(cap) = origin::already(func, held, pointer) else { continue };
let args = func.push_values(&[cap]);
let data = InstData { args, ..InstData::new(Opcode::CapYield) };
let made = func.create_inst(data, &[], func.span(inst));
func.insert_before(made, inst);
done += 1;
}
done
}
fn behind(func: &Func, inst: Inst) -> Option<Inst> {
let block = func.block_of(inst)?;
let mut after = func.insts(block).skip_while(|&at| at != inst);
after.next();
after.next()
}
fn over(func: &mut Func, inst: Inst, held: &HashMap<Value, Value>) -> bool {
let carried: Vec<Value> = pointers(func, inst).take(ARGS).collect();
let found: Vec<Option<Value>> =
carried.iter().map(|&value| origin::already(func, held, value)).collect();
let Some(last) = found.iter().rposition(Option::is_some) else {
empty(func, inst);
return false;
};
let mut caps = Vec::with_capacity(last + 1);
for each in &found[..=last] {
let cap = match *each {
Some(cap) => cap,
None => nothing(func, inst),
};
caps.push(cap);
}
let args = func.push_values(&caps);
let data = InstData { args, ..InstData::new(Opcode::CapPublish) };
let made = func.create_inst(data, &[], func.span(inst));
func.insert_before(made, inst);
true
}
fn empty(func: &mut Func, inst: Inst) {
let made = func.create_inst(InstData::new(Opcode::CapClear), &[], func.span(inst));
func.insert_before(made, inst);
}
fn nothing(func: &mut Func, inst: Inst) -> Value {
let made = func.create_inst(InstData::new(Opcode::CapNull), &[Type::CAP], func.span(inst));
func.insert_before(made, inst);
func[made].results().next().expect("cap_null produces one value")
}
fn all(func: &Func) -> Vec<Inst> {
func.blocks()
.collect::<Vec<_>>()
.into_iter()
.flat_map(|block| func.insts(block).collect::<Vec<Inst>>())
.collect()
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Builder, CallInfo, InstData, MemInfo, MemOrder, Restrict, Signature, Type};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
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:?}");
}
}
fn unit(names: &mut Interner) -> Module {
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
Module::new(names.intern("f.c"), &target)
}
fn caller(
names: &mut Interner,
name: &str,
callee: Option<&str>,
signature: Signature,
checks: bool,
) -> Func {
let word = Type::int(64);
let params = Signature::new().with_params(&[Type::PTR, word, Type::PTR]);
let mut func = Func::new(names.intern(name), params);
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let n = func.append_param(entry, word);
let q = func.append_param(entry, Type::PTR);
let sig = func.add_signature(signature);
let callee = callee.map(|each| names.intern(each));
let varargs = func.push_abis(&[]);
let info = func.add_call(CallInfo { callee, signature: sig, varargs });
let mut b = Builder::new(&mut func, entry);
if checks {
let args = b.func().push_values(&[p]);
let cap = b.value(InstData { args, ..InstData::new(Opcode::CapOf) }, 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::CheckLive) }, &[]);
}
let args = b.func().push_values(&[p, n, q]);
b.inst(InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) }, &[]);
b.ret(&[]);
func
}
fn three() -> Signature {
Signature::new().with_params(&[Type::PTR, Type::int(64), Type::PTR])
}
fn count(func: &Func, opcode: Opcode) -> usize {
all(func).into_iter().filter(|&inst| func[inst].opcode == opcode).count()
}
fn the(func: &Func, opcode: Opcode) -> Inst {
all(func)
.into_iter()
.find(|&inst| func[inst].opcode == opcode)
.unwrap_or_else(|| panic!("there is a {opcode:?}"))
}
fn operands(func: &Func, opcode: Opcode) -> Vec<Value> {
let inst = the(func, opcode);
func[func[inst].args].to_vec()
}
fn produced(func: &Func, opcode: Opcode) -> Value {
func[the(func, opcode)].results().next().expect("it gives something back")
}
#[test]
fn a_callee_that_still_checks_something_reads_its_parameter_out_of_the_frame() {
let mut names = Interner::new();
let mut module = unit(&mut names);
module.add_func(caller(&mut names, "f", Some("g"), three(), true));
arrange(&mut module);
let func = &module[module.funcs().next().expect("the module defines one")];
assert_eq!(count(func, Opcode::CapOf), 0);
assert_eq!(count(func, Opcode::CapArg), 1);
let args = operands(func, Opcode::CapArg);
assert_eq!(args.len(), 2);
assert_eq!(args[0], func[func.entry().expect("an entry")].params[0]);
}
#[test]
fn a_call_into_something_this_unit_does_not_define_says_there_is_no_frame() {
let mut names = Interner::new();
let mut module = unit(&mut names);
module.add_func(caller(&mut names, "f", Some("g"), three(), true));
assert_eq!(arrange(&mut module), 0);
let func = &module[module.funcs().next().expect("the module defines one")];
assert_eq!(count(func, Opcode::CapClear), 1);
assert_eq!(count(func, Opcode::CapPublish), 0);
}
#[test]
fn a_caller_hands_over_the_capability_it_already_had() {
let mut names = Interner::new();
let mut module = unit(&mut names);
module.add_func(caller(&mut names, "f", Some("g"), three(), true));
module.add_func(caller(&mut names, "g", Some("h"), three(), true));
assert_eq!(arrange(&mut module), 1);
let func = &module[module.funcs().next().expect("the module defines two")];
assert_eq!(count(func, Opcode::CapPublish), 1);
assert_eq!(count(func, Opcode::CapClear), 0);
let caps = operands(func, Opcode::CapPublish);
assert_eq!(caps.len(), 1);
assert_eq!(caps[0], produced(func, Opcode::CapArg));
}
#[test]
fn a_caller_holding_nothing_clears_rather_than_publishing_the_bottom_capability() {
let mut names = Interner::new();
let mut module = unit(&mut names);
module.add_func(caller(&mut names, "f", Some("g"), three(), false));
module.add_func(caller(&mut names, "g", Some("h"), three(), true));
assert_eq!(arrange(&mut module), 0);
let func = &module[module.funcs().next().expect("the module defines two")];
assert_eq!(count(func, Opcode::CapPublish), 0);
assert_eq!(count(func, Opcode::CapNull), 0);
assert_eq!(count(func, Opcode::CapClear), 1);
}
#[test]
fn a_call_into_something_with_nothing_left_to_check_gets_no_frame_at_all() {
let mut names = Interner::new();
let mut module = unit(&mut names);
module.add_func(caller(&mut names, "f", Some("g"), three(), true));
module.add_func(caller(&mut names, "g", Some("h"), three(), false));
assert_eq!(arrange(&mut module), 0);
let func = &module[module.funcs().next().expect("the module defines two")];
assert_eq!(count(func, Opcode::CapPublish), 0);
assert_eq!(count(func, Opcode::CapClear), 0);
}
#[test]
fn a_publish_goes_immediately_in_front_of_the_call_it_is_about() {
let mut names = Interner::new();
let mut module = unit(&mut names);
module.add_func(caller(&mut names, "f", Some("g"), three(), true));
module.add_func(caller(&mut names, "g", Some("h"), three(), true));
arrange(&mut module);
let func = &module[module.funcs().next().expect("the module defines two")];
let publish = all(func)
.into_iter()
.find(|&inst| func[inst].opcode == Opcode::CapPublish)
.expect("the call got a frame");
assert!(crate::frame::placeable(func, publish));
}
#[test]
fn a_variadic_call_describes_only_the_pointers_its_signature_names() {
let mut names = Interner::new();
let mut module = unit(&mut names);
let one = Signature::new().with_params(&[Type::PTR]).variadic();
module.add_func(caller(&mut names, "f", Some("g"), one, true));
let func = &module[module.funcs().next().expect("the module defines one")];
let call = only(func);
assert_eq!(pointers(func, call).count(), 1);
}
#[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:?}");
}
}
fn one_each() -> Signature {
Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::PTR])
}
fn checked(b: &mut Builder<'_>, pointer: Value) -> Value {
let args = b.func().push_values(&[pointer]);
let cap = b.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let args = b.func().push_values(&[cap, pointer]);
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::CheckLive) }, &[]);
cap
}
fn passing_on(names: &mut Interner, name: &str, callee: Option<&str>, checks: bool) -> Func {
let mut func = Func::new(names.intern(name), one_each());
let entry = func.create_block();
let p = func.append_param(entry, Type::PTR);
let sig = func.add_signature(one_each());
let callee = callee.map(|each| names.intern(each));
let varargs = func.push_abis(&[]);
let info = func.add_call(CallInfo { callee, signature: sig, varargs });
let mut b = Builder::new(&mut func, entry);
if checks {
checked(&mut b, p);
}
let args = b.func().push_values(&[p]);
let data = InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) };
let got = b.value(data, Type::PTR);
if checks {
checked(&mut b, got);
}
b.ret(&[got]);
func
}
fn both_ends(names: &mut Interner, checks: bool) -> Module {
let mut module = unit(names);
module.add_func(passing_on(names, "f", Some("g"), checks));
module.add_func(passing_on(names, "g", Some("h"), true));
module
}
#[test]
fn a_pointer_a_call_gave_back_is_read_out_of_the_frame_rather_than_recovered() {
let mut names = Interner::new();
let mut module = both_ends(&mut names, true);
assert_eq!(arrange(&mut module), 1);
let func = &module[module.funcs().next().expect("the module defines two")];
assert_eq!(count(func, Opcode::CapResult), 1);
assert_eq!(count(func, Opcode::CapOf), 0);
let args = operands(func, Opcode::CapResult);
assert_eq!(args.len(), 1);
assert_eq!(args[0], func[only(func)].results().next().expect("the call gives one back"));
}
#[test]
fn the_result_sits_behind_a_call_that_was_published_to() {
let mut names = Interner::new();
let mut module = both_ends(&mut names, true);
arrange(&mut module);
let func = &module[module.funcs().next().expect("the module defines two")];
assert!(crate::frame::given(func, the(func, Opcode::CapResult)));
}
#[test]
fn a_function_yields_the_capability_of_the_pointer_it_gives_back() {
let mut names = Interner::new();
let mut module = both_ends(&mut names, true);
arrange(&mut module);
let func = &module[module.funcs().next().expect("the module defines two")];
assert_eq!(count(func, Opcode::CapYield), 1);
let args = operands(func, Opcode::CapYield);
assert_eq!(args.len(), 1);
assert_eq!(args[0], produced(func, Opcode::CapResult));
assert!(crate::frame::leaving(func, the(func, Opcode::CapYield)));
}
#[test]
fn a_returned_pointer_nothing_holds_a_capability_for_gets_neither_end() {
let mut names = Interner::new();
let mut module = both_ends(&mut names, false);
arrange(&mut module);
let func = &module[module.funcs().next().expect("the module defines two")];
assert_eq!(count(func, Opcode::CapResult), 0);
assert_eq!(count(func, Opcode::CapYield), 0);
}
}