use std::collections::HashSet;
use rucc_base::Symbol;
use rucc_ir::{AttrSet, Dll, Extra, Linkage, Module, Opcode, Pic, Visibility};
use rucc_target::ObjectFormat;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Elsewhere {
names: HashSet<Symbol>,
threads: HashSet<Symbol>,
twice: HashSet<Symbol>,
described: bool,
indexed: bool,
imported: HashSet<Symbol>,
referred: HashSet<Symbol>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Slot {
Imported,
Referred,
}
impl Slot {
#[must_use]
pub fn name(self, name: &str) -> String {
match self {
Self::Imported => format!("__imp_{name}"),
Self::Referred => format!(".refptr.{name}"),
}
}
}
impl Elsewhere {
#[must_use]
pub fn of(module: &Module, pic: Pic, format: ObjectFormat, copies: bool) -> Self {
let threads = module
.globals()
.filter(|&id| module[id].tls.is_some())
.map(|id| module[id].name)
.collect();
let twice = module
.funcs()
.filter(|&id| module[id].attrs.set.contains(AttrSet::RETURNS_TWICE))
.map(|id| module[id].name)
.collect();
let described = format == ObjectFormat::MachO;
let indexed = format == ObjectFormat::Coff;
let (imported, referred) = Self::pointers(module, format);
Self {
threads,
twice,
described,
indexed,
imported,
referred,
..Self::table(module, pic, format, copies)
}
}
fn pointers(module: &Module, format: ObjectFormat) -> (HashSet<Symbol>, HashSet<Symbol>) {
if format != ObjectFormat::Coff {
return (HashSet::new(), HashSet::new());
}
let funcs = module
.funcs()
.filter(|&id| module[id].is_declaration() && module[id].dll == Dll::Import)
.map(|id| module[id].name);
let globals = module
.globals()
.filter(|&id| module[id].is_declaration() && module[id].tls.is_none())
.filter(|&id| module[id].dll == Dll::Import)
.map(|id| module[id].name);
let referred = module
.globals()
.filter(|&id| {
let global = &module[id];
global.is_declaration()
&& global.tls.is_none()
&& global.dll != Dll::Import
&& global.visibility == Visibility::Default
&& matches!(global.linkage, Linkage::External | Linkage::Weak)
})
.map(|id| module[id].name)
.collect();
(funcs.chain(globals).collect(), referred)
}
fn table(module: &Module, pic: Pic, format: ObjectFormat, copies: bool) -> Self {
if format == ObjectFormat::Coff {
return Self::default();
}
let funcs = module.funcs().filter(|&id| {
let func = &module[id];
func.is_declaration() || pic.replaceable(func.linkage, func.visibility)
});
let uncopied = format == ObjectFormat::MachO || !copies;
let globals = module
.globals()
.filter(|&id| {
let global = &module[id];
(global.is_declaration()
&& (global.linkage == Linkage::Weak
|| (uncopied && global.visibility == Visibility::Default)))
|| pic.replaceable(global.linkage, global.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)
}
#[must_use]
pub fn slot(&self, name: Symbol) -> Option<Slot> {
if self.imported.contains(&name) {
Some(Slot::Imported)
} else if self.referred.contains(&name) {
Some(Slot::Referred)
} else {
None
}
}
#[must_use]
pub fn referred(&self, module: &Module) -> Vec<Symbol> {
if self.referred.is_empty() {
return Vec::new();
}
let mut read = HashSet::new();
for id in module.funcs() {
let func = &module[id];
for block in func.blocks() {
for inst in func.insts(block) {
let data = &func[inst];
if let (Opcode::GlobalAddr, Extra::Symbol(name)) = (data.opcode, data.extra) {
read.insert(name);
}
}
}
}
module
.globals()
.map(|id| module[id].name)
.filter(|name| self.referred.contains(name) && read.contains(name))
.collect()
}
#[must_use]
pub fn thread(&self, name: Symbol) -> bool {
self.threads.contains(&name)
}
#[must_use]
pub fn twice(&self, name: Symbol) -> bool {
self.twice.contains(&name)
}
#[must_use]
pub const fn described(&self) -> bool {
self.described
}
#[must_use]
pub const fn indexed(&self) -> bool {
self.indexed
}
}
impl FromIterator<Symbol> for Elsewhere {
fn from_iter<T: IntoIterator<Item = Symbol>>(names: T) -> Self {
Self { names: names.into_iter().collect(), ..Self::default() }
}
}
impl Elsewhere {
#[must_use]
pub fn with_threads<T: IntoIterator<Item = Symbol>>(mut self, threads: T) -> Self {
self.threads = threads.into_iter().collect();
self
}
#[must_use]
pub const fn with_descriptors(mut self) -> Self {
self.described = true;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use rucc_base::Interner;
use rucc_ir::{
Alias, Builder, Func, Global, InstData, Linkage, Signature, TlsModel, 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);
let mut own = Global::new(names.intern("own"), 4, 4);
own.init = Some(module.push_data(&[]));
own.tls = Some(TlsModel::GlobalDynamic);
module.add_global(own);
module.add_alias(Alias::new(names.intern("second"), names.intern("here")));
module
}
#[test]
fn a_variable_every_thread_has_its_own_copy_of_is_one() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
assert!(elsewhere.thread(names.intern("own")));
}
#[test]
fn an_ordinary_variable_is_not() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
for name in ["kept", "away", "quiet", "shy", "here"] {
assert!(!elsewhere.thread(names.intern(name)), "{name} was called thread-local");
}
}
#[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, ObjectFormat::Elf, true);
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, ObjectFormat::Elf, true);
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, ObjectFormat::Elf, true);
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, ObjectFormat::Elf, true);
for name in ["kept", "away", "quiet", "shy", "second"] {
assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
}
}
#[test]
fn a_weak_variable_this_file_only_declares_is_reached_through_the_table() {
let mut names = Interner::new();
let mut module = module(&mut names);
let mut maybe = Global::new(names.intern("maybe"), 4, 4);
maybe.linkage = Linkage::Weak;
module.add_global(maybe);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
assert!(elsewhere.holds(names.intern("maybe")));
}
#[test]
fn a_mach_o_executable_pays_for_the_variables_it_does_not_define_as_well() {
let mut names = Interner::new();
let mut module = module(&mut names);
let mut near = Global::new(names.intern("near"), 4, 4);
near.visibility = Visibility::Hidden;
module.add_global(near);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::MachO, true);
assert!(elsewhere.holds(names.intern("away")));
for name in ["kept", "quiet", "shy", "near"] {
assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
}
}
#[test]
fn an_executable_that_gets_no_copies_pays_for_the_variables_it_does_not_define() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, false);
assert!(elsewhere.holds(names.intern("away")));
for name in ["kept", "quiet", "shy"] {
assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
}
let copied = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
assert!(!copied.holds(names.intern("away")));
}
#[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, ObjectFormat::Elf, true);
for name in ["here", "exit", "kept", "away", "second"] {
assert!(elsewhere.holds(names.intern(name)), "{name} was not in the table");
}
}
#[test]
fn a_format_with_no_table_puts_nothing_in_one() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
for name in ["here", "exit", "kept", "away", "quiet", "shy", "second"] {
assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
}
}
#[test]
fn a_format_with_no_table_does_not_grow_one_under_the_library_flag() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Library, ObjectFormat::Coff, true);
for name in ["here", "exit", "kept", "away", "second"] {
assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
}
}
#[test]
fn a_format_with_no_table_still_says_which_variable_every_thread_has_a_copy_of() {
let mut names = Interner::new();
let module = module(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
assert!(elsewhere.thread(names.intern("own")));
}
#[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, ObjectFormat::Elf, true);
assert!(!elsewhere.holds(names.intern("quiet")));
assert!(!elsewhere.holds(names.intern("shy")));
}
fn windows(names: &mut Interner) -> Module {
let mut module = module(names);
let mut pid = Func::new(names.intern("GetCurrentProcessId"), Signature::new());
pid.dll = Dll::Import;
module.add_func(pid);
let mut mode = Global::new(names.intern("_fmode"), 4, 4);
mode.dll = Dll::Import;
module.add_global(mode);
let mut near = Global::new(names.intern("near"), 4, 4);
near.visibility = Visibility::Hidden;
module.add_global(near);
let mut reader = Func::new(names.intern("reader"), Signature::new());
let block = reader.create_block();
for name in ["kept", "away", "quiet", "_fmode", "near"] {
let symbol = names.intern(name);
let data =
InstData { extra: Extra::Symbol(symbol), ..InstData::new(Opcode::GlobalAddr) };
Builder::new(&mut reader, block).value(data, rucc_ir::Type::PTR);
}
module.add_func(reader);
module
}
#[test]
fn a_name_in_a_dll_is_reached_through_the_pointer_the_loader_fills_in() {
let mut names = Interner::new();
let module = windows(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
for name in ["GetCurrentProcessId", "_fmode"] {
assert_eq!(elsewhere.slot(names.intern(name)), Some(Slot::Imported), "{name}");
}
assert_eq!(elsewhere.slot(names.intern("away")), Some(Slot::Referred));
for name in ["exit", "here", "kept", "quiet", "shy", "own", "near"] {
assert_eq!(elsewhere.slot(names.intern(name)), None, "{name}");
}
assert_eq!(Slot::Imported.name("_fmode"), "__imp__fmode");
assert_eq!(Slot::Referred.name("away"), ".refptr.away");
}
#[test]
fn a_pointer_is_written_only_for_a_variable_the_code_reads() {
let mut names = Interner::new();
let mut module = windows(&mut names);
module.add_global(Global::new(names.intern("unread"), 4, 4));
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
assert_eq!(elsewhere.referred(&module), vec![names.intern("away")]);
}
#[test]
fn a_format_with_a_table_has_no_pointers() {
let mut names = Interner::new();
let module = windows(&mut names);
let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
for name in ["GetCurrentProcessId", "_fmode", "away"] {
assert_eq!(elsewhere.slot(names.intern(name)), None, "{name}");
}
assert!(elsewhere.referred(&module).is_empty());
}
}