use std::collections::HashSet;
use rucc_base::Symbol;
use rucc_ir::{Module, Pic};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Elsewhere {
names: HashSet<Symbol>,
}
impl Elsewhere {
#[must_use]
pub fn of(module: &Module, pic: Pic) -> Self {
let funcs = module.funcs().filter(|&id| {
let func = &module[id];
func.is_declaration() || pic.replaceable(func.linkage, func.visibility)
});
let globals = module
.globals()
.filter(|&id| pic.replaceable(module[id].linkage, module[id].visibility))
.map(|id| module[id].name);
let aliases = module
.aliases()
.filter(|&id| pic.replaceable(module[id].linkage, module[id].visibility))
.map(|id| module[id].name);
funcs.map(|id| module[id].name).chain(globals).chain(aliases).collect()
}
#[must_use]
pub fn holds(&self, name: Symbol) -> bool {
self.names.contains(&name)
}
}
impl FromIterator<Symbol> for Elsewhere {
fn from_iter<T: IntoIterator<Item = Symbol>>(names: T) -> Self {
Self { names: names.into_iter().collect() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use rucc_base::Interner;
use rucc_ir::{Alias, Func, Global, Linkage, Signature, Visibility};
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
fn module(names: &mut Interner) -> Module {
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
let mut module = Module::new(names.intern("test.c"), &target);
let mut defined = Func::new(names.intern("here"), Signature::new());
defined.create_block();
module.add_func(defined);
module.add_func(Func::new(names.intern("exit"), Signature::new()));
let mut kept = Global::new(names.intern("kept"), 4, 4);
kept.init = Some(module.push_data(&[]));
module.add_global(kept);
module.add_global(Global::new(names.intern("away"), 4, 4));
let mut quiet = Global::new(names.intern("quiet"), 4, 4);
quiet.init = Some(module.push_data(&[]));
quiet.linkage = Linkage::Internal;
module.add_global(quiet);
let mut shy = Global::new(names.intern("shy"), 4, 4);
shy.init = Some(module.push_data(&[]));
shy.visibility = Visibility::Hidden;
module.add_global(shy);
module.add_alias(Alias::new(names.intern("second"), names.intern("here")));
module
}
#[test]
fn a_function_this_file_only_declares_is_reached_through_the_table() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable);
assert!(elsewhere.holds(names.intern("exit")));
}
#[test]
fn a_function_this_file_defines_is_not() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable);
assert!(!elsewhere.holds(names.intern("here")));
}
#[test]
fn a_name_the_module_does_not_carry_at_all_is_not() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable);
assert!(!elsewhere.holds(names.intern("nowhere")));
}
#[test]
fn an_executable_pays_for_the_functions_and_for_nothing_else() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable);
for name in ["kept", "away", "quiet", "shy", "second"] {
assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
}
}
#[test]
fn a_library_pays_for_every_name_something_else_may_define() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Library);
for name in ["here", "exit", "kept", "away", "second"] {
assert!(elsewhere.holds(names.intern(name)), "{name} was not in the table");
}
}
#[test]
fn a_library_pays_nothing_for_a_name_nothing_outside_it_can_see() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Library);
assert!(!elsewhere.holds(names.intern("quiet")));
assert!(!elsewhere.holds(names.intern("shy")));
}
}