use rucc_base::{Interner, Symbol};
use rucc_ir::{CallInfo, Extra, Inst, Module, Opcode};
pub const PREFIX: &str = "__rucc_wrap_";
pub const INTERPOSED: &[&str] = &[
"memcpy",
"memmove",
"memset",
"memcmp",
"memchr",
"bcopy",
"bzero",
"strlen",
"strnlen",
"strcmp",
"strncmp",
"strchr",
"strrchr",
"strstr",
"strcpy",
"stpcpy",
"strncpy",
"strcat",
"strncat",
"read",
"write",
"pread",
"pread64",
"pwrite",
"pwrite64",
"recv",
"send",
"readv",
"writev",
"pthread_mutex_lock",
"pthread_mutex_trylock",
"pthread_mutex_unlock",
"pthread_rwlock_rdlock",
"pthread_rwlock_wrlock",
"pthread_rwlock_tryrdlock",
"pthread_rwlock_trywrlock",
"pthread_rwlock_unlock",
"pthread_create",
"pthread_join",
"pthread_cond_wait",
"pthread_cond_timedwait",
"sem_wait",
"sem_trywait",
"sem_post",
];
pub fn redirect(module: &mut Module, names: &mut Interner) -> usize {
let table: Vec<(Symbol, Symbol)> = INTERPOSED
.iter()
.map(|&name| (names.intern(name), names.intern(&[PREFIX, name].concat())))
.collect();
let ids: Vec<_> = module.funcs().collect();
let mut defined: Vec<Symbol> =
ids.iter().filter(|&&id| !module[id].is_declaration()).map(|&id| module[id].name).collect();
defined.extend(
module.globals().filter(|&id| !module[id].is_declaration()).map(|id| module[id].name),
);
let mut moved = 0;
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].opcode == Opcode::GlobalAddr {
let Extra::Symbol(named) = func[inst].extra else { continue };
if defined.contains(&named) {
continue;
}
if let Some(&(_, wrapper)) = table.iter().find(|&&(name, _)| name == named) {
func[inst].extra = Extra::Symbol(wrapper);
moved += 1;
}
continue;
}
if !matches!(func[inst].opcode, Opcode::Call | Opcode::TailCall) {
continue;
}
let Extra::Call(at) = func[inst].extra else { continue };
let info = func[at];
let Some(callee) = info.callee else { continue };
if defined.contains(&callee) {
continue;
}
let Some(&(_, wrapper)) = table.iter().find(|&&(name, _)| name == callee) else {
continue;
};
let redirected = func.add_call(CallInfo { callee: Some(wrapper), ..info });
func[inst].extra = Extra::Call(redirected);
moved += 1;
}
}
for reloc in module.relocs_mut() {
if defined.contains(&reloc.symbol) {
continue;
}
if let Some(&(_, wrapper)) = table.iter().find(|&&(name, _)| name == reloc.symbol) {
reloc.symbol = wrapper;
moved += 1;
}
}
moved
}
#[cfg(test)]
mod tests {
use rucc_ir::{Builder, Func, Signature, Type};
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 calling(names: &mut Interner, callee: &str) -> (Module, Symbol) {
let ptr = Type::PTR;
let mut func = Func::new(
names.intern("run"),
Signature::new().with_params(&[ptr, ptr, Type::int(64)]),
);
let entry = func.create_block();
let dst = func.append_param(entry, ptr);
let src = func.append_param(entry, ptr);
let n = func.append_param(entry, Type::int(64));
let name = names.intern(callee);
let mut b = Builder::new(&mut func, entry);
let signature = b.func().add_signature(
Signature::new().with_params(&[ptr, ptr, Type::int(64)]).with_returns(&[ptr]),
);
b.call(name, signature, &[dst, src, n]);
let other = names.intern("puts");
let takes = b.func().add_signature(Signature::new().with_params(&[ptr]));
b.call(other, takes, &[dst]);
b.ret(&[]);
let mut module = Module::new(names.intern("run.c"), &target());
module.add_func(func);
(module, name)
}
fn callees(module: &Module, names: &Interner) -> Vec<String> {
let mut out = Vec::new();
for id in module.funcs() {
let func = &module[id];
for block in func.blocks() {
for inst in func.insts(block) {
if let Extra::Call(at) = func[inst].extra {
if let Some(callee) = func[at].callee {
out.push(names.resolve(callee).to_owned());
}
}
}
}
}
out
}
#[test]
fn a_call_to_an_interposed_function_goes_to_its_wrapper() {
let mut names = Interner::new();
let (mut module, _) = calling(&mut names, "memcpy");
assert_eq!(redirect(&mut module, &mut names), 1);
assert_eq!(callees(&module, &names), ["__rucc_wrap_memcpy", "puts"]);
}
#[test]
fn a_call_to_anything_else_is_left_where_it_was() {
let mut names = Interner::new();
let (mut module, _) = calling(&mut names, "getenv");
assert_eq!(redirect(&mut module, &mut names), 0);
assert_eq!(callees(&module, &names), ["getenv", "puts"]);
}
#[test]
fn a_program_that_defines_the_name_itself_keeps_its_own() {
let mut names = Interner::new();
let (mut module, name) = calling(&mut names, "memcpy");
let ptr = Type::PTR;
let mut own = Func::new(
name,
Signature::new().with_params(&[ptr, ptr, Type::int(64)]).with_returns(&[ptr]),
);
let entry = own.create_block();
let dst = own.append_param(entry, ptr);
own.append_param(entry, ptr);
own.append_param(entry, Type::int(64));
let mut b = Builder::new(&mut own, entry);
b.ret(&[dst]);
module.add_func(own);
assert_eq!(redirect(&mut module, &mut names), 0);
assert_eq!(callees(&module, &names), ["memcpy", "puts"]);
}
#[test]
fn what_it_produces_is_a_module_the_verifier_believes() {
let mut names = Interner::new();
let (mut module, _) = calling(&mut names, "memcpy");
redirect(&mut module, &mut names);
if let Err(errors) = rucc_ir::verify(&module, &names) {
panic!("that was expected to be believed: {errors:#?}");
}
}
fn addressed(module: &Module, names: &Interner) -> Vec<String> {
let mut out = Vec::new();
for id in module.funcs() {
let func = &module[id];
for block in func.blocks() {
for inst in func.insts(block) {
if func[inst].opcode == Opcode::GlobalAddr {
if let Extra::Symbol(named) = func[inst].extra {
out.push(names.resolve(named).to_owned());
}
}
}
}
}
out
}
fn addressing(names: &mut Interner, named: &str) -> Module {
let mut func = Func::new(names.intern("run"), Signature::new());
let entry = func.create_block();
let mut b = Builder::new(&mut func, entry);
for each in [named, "puts"] {
let symbol = names.intern(each);
b.value(
rucc_ir::InstData {
extra: Extra::Symbol(symbol),
..rucc_ir::InstData::new(Opcode::GlobalAddr)
},
Type::PTR,
);
}
b.ret(&[]);
let mut module = Module::new(names.intern("run.c"), &target());
module.add_func(func);
module
}
#[test]
fn the_address_of_an_interposed_function_is_the_address_of_its_wrapper() {
let mut names = Interner::new();
let mut module = addressing(&mut names, "read");
assert_eq!(redirect(&mut module, &mut names), 1);
assert_eq!(addressed(&module, &names), ["__rucc_wrap_read", "puts"]);
}
#[test]
fn the_address_of_a_name_the_module_defines_itself_is_left_alone() {
let mut names = Interner::new();
let mut module = addressing(&mut names, "read");
let own = names.intern("read");
let mut global = rucc_ir::Global::new(own, 8, 8);
let ty = Type::int(64);
let value = module.add_imm(rucc_ir::Imm::int(0, ty));
global.init = Some(module.push_data(&[rucc_ir::Datum::Scalar { ty, value }]));
module.add_global(global);
assert_eq!(redirect(&mut module, &mut names), 0);
assert_eq!(addressed(&module, &names), ["read", "puts"]);
}
#[test]
fn a_name_in_a_static_initializer_becomes_the_wrapper_too() {
let mut names = Interner::new();
let mut module = addressing(&mut names, "getenv");
let read = names.intern("read");
let at = module.add_reloc(rucc_ir::Reloc { symbol: read, addend: 0, size: 8 });
let mut table = rucc_ir::Global::new(names.intern("table"), 8, 8);
table.init = Some(module.push_data(&[rucc_ir::Datum::Addr(at)]));
module.add_global(table);
assert_eq!(redirect(&mut module, &mut names), 1);
assert_eq!(names.resolve(module[at].symbol), "__rucc_wrap_read");
}
#[test]
fn every_name_in_the_list_is_spelled_once_and_has_a_wrapper() {
for (at, &name) in INTERPOSED.iter().enumerate() {
assert!(!INTERPOSED[..at].contains(&name), "{name} is in the list twice");
assert!(!name.is_empty());
}
assert!(PREFIX.starts_with("__rucc"));
}
}