rucc_codegen/elsewhere.rs
1//! Which names this file may not work the address of out for itself.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.3.
4//!
5//! Everything this compiler emits is position independent, so the address of a name is the distance
6//! from the instruction asking to the name, and that distance is a number the assembler leaves a
7//! hole for and the linker fills in. The linker can only fill it in when it is putting both ends in
8//! the same program. A name this file only declares may turn out to be in a shared library, and
9//! then there is no such distance and the link fails rather than guessing one.
10//!
11//! The way round it is a table: the linker gives the name one slot in the global offset table, fills
12//! the slot with whatever address the name ends up at, and the code loads the address out of the
13//! slot instead of working it out. The slot is in this program, so the distance to the slot is a
14//! number the linker has. It costs a load, and the linker takes the load back out again when the
15//! name turns out to have been in this program all along.
16//!
17//! Which names need it is a fact about the whole module and the code generator sees one function at
18//! a time, which is why this is worked out first and handed in rather than asked at the point of
19//! use.
20
21use std::collections::HashSet;
22
23use rucc_base::Symbol;
24use rucc_ir::Module;
25
26/// The names whose address only the linker knows.
27///
28/// Functions and not objects, which is not an oversight. The address of a `static` or of anything
29/// this file defines is in this file, so there is a distance and no table is needed. The address of
30/// an object another file defines is also reachable that way in an executable, because the linker
31/// answers a reference to one by making room for it in this program and copying it there, so the
32/// name really does end up somewhere this file can measure to. A function cannot be copied: it has
33/// exactly one address that every object in the program has to agree on, or two pointers to it
34/// compare unequal, so the one address is what the table holds and what everything reads.
35///
36/// A name this module has never heard of is not in here. Nothing the front end writes produces one,
37/// and treating an unknown name as a function would put the addresses the instrumentation takes of
38/// its own tables through a table of their own for no reason.
39#[derive(Debug, Clone, Default, PartialEq, Eq)]
40pub struct Elsewhere {
41 names: HashSet<Symbol>,
42}
43
44impl Elsewhere {
45 /// The functions a module declares and does not define.
46 #[must_use]
47 pub fn of(module: &Module) -> Self {
48 module.funcs().filter(|&id| module[id].is_declaration()).map(|id| module[id].name).collect()
49 }
50
51 /// Whether the address of that name has to be read out of the global offset table.
52 #[must_use]
53 pub fn holds(&self, name: Symbol) -> bool {
54 self.names.contains(&name)
55 }
56}
57
58/// The same set, written out by hand.
59///
60/// [`Elsewhere::of`] is how the driver builds one and is the only way a compilation does. This is
61/// for a test that wants to lower one function and say what is outside the file without building a
62/// module for it to be outside of.
63impl FromIterator<Symbol> for Elsewhere {
64 fn from_iter<T: IntoIterator<Item = Symbol>>(names: T) -> Self {
65 Self { names: names.into_iter().collect() }
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 use rucc_base::Interner;
74 use rucc_ir::{Func, Signature};
75 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
76
77 /// A module of one function with a body and one without.
78 fn module(names: &mut Interner) -> Module {
79 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
80 let mut module = Module::new(names.intern("test.c"), &target);
81 let mut defined = Func::new(names.intern("here"), Signature::new());
82 defined.create_block();
83 module.add_func(defined);
84 module.add_func(Func::new(names.intern("exit"), Signature::new()));
85 module
86 }
87
88 #[test]
89 fn a_function_this_file_only_declares_is_reached_through_the_table() {
90 let mut names = Interner::new();
91 let module = module(&mut names);
92 let elsewhere = Elsewhere::of(&module);
93 assert!(elsewhere.holds(names.intern("exit")));
94 }
95
96 #[test]
97 fn a_function_this_file_defines_is_not() {
98 let mut names = Interner::new();
99 let module = module(&mut names);
100 let elsewhere = Elsewhere::of(&module);
101 assert!(!elsewhere.holds(names.intern("here")));
102 }
103
104 #[test]
105 fn a_name_the_module_does_not_carry_at_all_is_not() {
106 let mut names = Interner::new();
107 let module = module(&mut names);
108 let elsewhere = Elsewhere::of(&module);
109 assert!(!elsewhere.holds(names.intern("nowhere")));
110 }
111}