use std::collections::HashMap;
use rucc_base::Symbol;
use rucc_ir::{Def, Extra, Flags, Func, FuncId, Inst, Linkage, Module, Opcode, Value};
use crate::discharge::{Fact, about, alive, covers, derives};
pub fn annotate(module: &mut Module) -> usize {
let sizes = extents(module);
if sizes.is_empty() {
return 0;
}
let mut marked = 0;
let ids: Vec<FuncId> = module.funcs().collect();
for id in ids {
if module[id].is_declaration() {
continue;
}
let func = &mut module[id];
let insts: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in insts {
if func[inst].flags.contains(Flags::STATIC) || !inside(func, inst, &sizes) {
continue;
}
func[inst].flags |= Flags::STATIC;
marked += 1;
}
}
marked
}
fn inside(func: &Func, inst: Inst, sizes: &HashMap<Symbol, u64>) -> bool {
match func[inst].opcode {
Opcode::CheckBounds => {
if func[func[inst].args].len() > 2 {
return false;
}
let Some(asked) = about(func, inst) else { return false };
object(func, asked.base, sizes).is_some_and(|whole| covers(&whole, &asked))
}
Opcode::CheckLive => {
let Some(asked) = alive(func, inst) else { return false };
object(func, asked.base, sizes).is_some_and(|whole| covers(&whole, &asked))
}
Opcode::CheckDeriv => {
let Some((from, to)) = derives(func, inst) else { return false };
object(func, from.base, sizes)
.is_some_and(|whole| covers(&whole, &from) && covers(&whole, &to))
}
_ => false,
}
}
fn object(func: &Func, base: Value, sizes: &HashMap<Symbol, u64>) -> Option<Fact> {
let Def::Result { inst, .. } = func[base].def else { return None };
if func[inst].opcode != Opcode::GlobalAddr {
return None;
}
let Extra::Symbol(name) = func[inst].extra else { return None };
let size = *sizes.get(&name)?;
Some(Fact::whole(base, i128::from(size)))
}
fn extents(module: &Module) -> HashMap<Symbol, u64> {
let mut sizes: HashMap<Symbol, u64> = HashMap::new();
for id in module.globals() {
let global = &module[id];
if global.is_declaration() || global.size == 0 || global.tls.is_some() {
continue;
}
if !matches!(global.linkage, Linkage::External | Linkage::Internal) {
continue;
}
let at = sizes.entry(global.name).or_insert(global.size);
*at = (*at).min(global.size);
}
sizes
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Builder, Extra, Flags, Func, Global, InstData, Linkage, MemInfo, MemOrder, Module, Opcode,
Restrict, Signature, TlsModel, Type, Value,
};
use rucc_target::{TargetInfo, Triple};
use super::annotate;
fn module(size: u64, linkage: Linkage, defined: bool) -> (Interner, Module) {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
let mut global = Global::new(names.intern("g"), size, 8);
global.linkage = linkage;
if defined {
global.init = Some(module.push_data(&[]));
}
module.add_global(global);
(names, module)
}
fn defined() -> (Interner, Module) {
module(64, Linkage::External, true)
}
fn func(names: &mut Interner, module: &mut Module, body: impl FnOnce(&mut Builder<'_>, Value)) {
let name = names.intern("f");
let at = names.intern("g");
let mut func = Func::new(name, Signature::new());
let block = func.create_block();
let mut build = Builder::new(&mut func, block);
let extra = Extra::Symbol(at);
let at = build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
body(&mut build, at);
build.ret(&[]);
module.add_func(func);
}
fn check(build: &mut Builder<'_>, pointer: Value, size: u64) {
let args = build.func().push_values(&[pointer]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let info = MemInfo {
size,
align: 1,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
};
let args = build.func().push_values(&[capability, pointer]);
let extra = Extra::Mem(build.func().add_mem(info));
build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
}
fn live(build: &mut Builder<'_>, pointer: Value) {
let args = build.func().push_values(&[pointer]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let args = build.func().push_values(&[capability, pointer]);
build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
}
fn deriv(build: &mut Builder<'_>, from: Value, to: Value) {
let args = build.func().push_values(&[from]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let width = build.iconst(Type::int(64), 1);
let args = build.func().push_values(&[capability, from, to, width]);
build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
}
fn past(build: &mut Builder<'_>, pointer: Value, bytes: i128) -> Value {
let offset = build.iconst(Type::int(64), bytes);
let args = build.func().push_values(&[pointer, offset]);
build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
}
fn flagged(module: &Module) -> usize {
module
.funcs()
.map(|id| {
let func = &module[id];
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.filter(|&inst| func[inst].flags.contains(Flags::STATIC))
.count()
})
.sum()
}
#[test]
fn every_check_inside_a_global_this_module_defines_is_marked() {
let (mut names, mut module) = defined();
func(&mut names, &mut module, |build, at| {
let field = past(build, at, 32);
deriv(build, at, field);
check(build, field, 8);
live(build, field);
});
assert_eq!(annotate(&mut module), 3);
assert_eq!(flagged(&module), 3);
assert_eq!(annotate(&mut module), 0);
assert_eq!(flagged(&module), 3);
}
#[test]
fn a_check_that_runs_off_the_end_of_a_global_is_left_alone() {
let (mut names, mut module) = defined();
func(&mut names, &mut module, |build, at| {
let field = past(build, at, 60);
check(build, field, 8);
});
assert_eq!(annotate(&mut module), 0);
}
#[test]
fn a_walk_that_leaves_the_global_is_left_alone() {
let (mut names, mut module) = defined();
func(&mut names, &mut module, |build, at| {
let away = past(build, at, 128);
deriv(build, at, away);
});
assert_eq!(annotate(&mut module), 0);
}
#[test]
fn a_check_before_the_start_of_a_global_is_left_alone() {
let (mut names, mut module) = defined();
func(&mut names, &mut module, |build, at| {
let before = past(build, at, -8);
check(build, before, 8);
live(build, before);
});
assert_eq!(annotate(&mut module), 0);
}
#[test]
fn a_global_this_module_only_declares_says_nothing_about_how_big_it_is() {
let (mut names, mut module) = module(64, Linkage::External, false);
func(&mut names, &mut module, |build, at| check(build, at, 8));
assert_eq!(annotate(&mut module), 0);
}
#[test]
fn a_definition_the_linker_may_replace_is_not_believed() {
for linkage in [Linkage::Weak, Linkage::LinkOnce, Linkage::Common] {
let (mut names, mut module) = module(64, linkage, true);
func(&mut names, &mut module, |build, at| check(build, at, 8));
assert_eq!(annotate(&mut module), 0, "{}", linkage.name());
}
}
#[test]
fn a_thread_local_is_not_believed() {
let (mut names, mut module) = module(64, Linkage::Internal, true);
let id = module.globals().next().unwrap();
module[id].tls = Some(TlsModel::GlobalDynamic);
func(&mut names, &mut module, |build, at| check(build, at, 8));
assert_eq!(annotate(&mut module), 0);
}
#[test]
fn a_check_over_a_length_the_program_worked_out_is_left_alone() {
let (mut names, mut module) = defined();
func(&mut names, &mut module, |build, at| {
let args = build.func().push_values(&[at]);
let capability =
build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let bytes = build.iconst(Type::int(64), 8);
let info = MemInfo {
size: 8,
align: 1,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
};
let extra = Extra::Mem(build.func().add_mem(info));
let args = build.func().push_values(&[capability, at, bytes]);
build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
});
assert_eq!(annotate(&mut module), 0);
}
#[test]
fn a_check_on_a_name_this_module_never_heard_of_is_left_alone() {
let (mut names, mut module) = defined();
let name = names.intern("f");
let elsewhere = names.intern("elsewhere");
let mut body = Func::new(name, Signature::new());
let block = body.create_block();
let mut build = Builder::new(&mut body, block);
let extra = Extra::Symbol(elsewhere);
let at = build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
check(&mut build, at, 8);
build.ret(&[]);
module.add_func(body);
assert_eq!(annotate(&mut module), 0);
}
#[test]
fn a_module_with_no_globals_is_nothing_to_work_out() {
let mut names = Interner::new();
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let mut module = Module::new(names.intern("t.c"), &target);
func(&mut names, &mut module, |build, at| check(build, at, 8));
assert_eq!(annotate(&mut module), 0);
}
}