Skip to main content

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//!
21//! It is also a fact about which link is coming, which is [`rucc_ir::Pic`] and is why this is built
22//! from more than the module. Under `-fPIC` the link may be one that produces a shared library, and
23//! then a name this file exports is one the dynamic linker may find a different definition of, so
24//! reaching it from the instruction pointer would reach the wrong one. The static linker will not
25//! let that happen quietly: `R_X86_64_PC32` against a name it can see is replaceable is refused
26//! when it is making a shared object, which is how tamnd/rucc#756 was found.
27
28use std::collections::HashSet;
29
30use rucc_base::Symbol;
31use rucc_ir::{Module, Pic};
32
33/// The names whose address only the linker knows.
34///
35/// Two ways in, and the first one holds whichever link is coming. A function this file only
36/// declares is one, because a function cannot be copied: it has exactly one address that every
37/// object in the program has to agree on, or two pointers to it compare unequal, so the one address
38/// is what the table holds and what everything reads. A variable can be copied, and in an
39/// executable it is, since the linker answers a reference to one another object defines by making
40/// room for it here and copying it there, so the name really does end up somewhere this file can
41/// measure to.
42///
43/// The second way in is `-fPIC`, where the link may be one that produces a shared library and the
44/// copying does not happen. There every replaceable name is in here, defined or not and function or
45/// variable, because the definition the process ends up using may be in another object however
46/// plainly this file defines it. What is not in here is what `-fPIC` costs nothing for: a `static`,
47/// and a name marked hidden or protected, which is the reason `-fPIC -fvisibility=hidden` is the
48/// combination a library that cares about its own speed is built with.
49///
50/// A name this module has never heard of is not in here. Nothing the front end writes produces one,
51/// and treating an unknown name as a function would put the addresses the instrumentation takes of
52/// its own tables through a table of their own for no reason.
53#[derive(Debug, Clone, Default, PartialEq, Eq)]
54pub struct Elsewhere {
55    names: HashSet<Symbol>,
56}
57
58impl Elsewhere {
59    /// The names that link cannot reach from the instruction pointer.
60    #[must_use]
61    pub fn of(module: &Module, pic: Pic) -> Self {
62        let funcs = module.funcs().filter(|&id| {
63            let func = &module[id];
64            func.is_declaration() || pic.replaceable(func.linkage, func.visibility)
65        });
66        let globals = module
67            .globals()
68            .filter(|&id| pic.replaceable(module[id].linkage, module[id].visibility))
69            .map(|id| module[id].name);
70        // An alias is a symbol of its own with a linkage and a visibility of its own, so it answers
71        // this for itself the same way it answered the visibility question in #752. What it points
72        // at is a separate name and is decided separately, which is what `weak, alias,
73        // visibility("hidden")` over an exported definition needs.
74        let aliases = module
75            .aliases()
76            .filter(|&id| pic.replaceable(module[id].linkage, module[id].visibility))
77            .map(|id| module[id].name);
78        funcs.map(|id| module[id].name).chain(globals).chain(aliases).collect()
79    }
80
81    /// Whether the address of that name has to be read out of the global offset table.
82    #[must_use]
83    pub fn holds(&self, name: Symbol) -> bool {
84        self.names.contains(&name)
85    }
86}
87
88/// The same set, written out by hand.
89///
90/// [`Elsewhere::of`] is how the driver builds one and is the only way a compilation does. This is
91/// for a test that wants to lower one function and say what is outside the file without building a
92/// module for it to be outside of.
93impl FromIterator<Symbol> for Elsewhere {
94    fn from_iter<T: IntoIterator<Item = Symbol>>(names: T) -> Self {
95        Self { names: names.into_iter().collect() }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    use rucc_base::Interner;
104    use rucc_ir::{Alias, Func, Global, Linkage, Signature, Visibility};
105    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
106
107    /// A module with one of everything: a function with a body and one without, a variable with an
108    /// image and one without, a `static`, a hidden export and an alias.
109    fn module(names: &mut Interner) -> Module {
110        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
111        let mut module = Module::new(names.intern("test.c"), &target);
112        let mut defined = Func::new(names.intern("here"), Signature::new());
113        defined.create_block();
114        module.add_func(defined);
115        module.add_func(Func::new(names.intern("exit"), Signature::new()));
116
117        let mut kept = Global::new(names.intern("kept"), 4, 4);
118        kept.init = Some(module.push_data(&[]));
119        module.add_global(kept);
120        module.add_global(Global::new(names.intern("away"), 4, 4));
121
122        let mut quiet = Global::new(names.intern("quiet"), 4, 4);
123        quiet.init = Some(module.push_data(&[]));
124        quiet.linkage = Linkage::Internal;
125        module.add_global(quiet);
126
127        let mut shy = Global::new(names.intern("shy"), 4, 4);
128        shy.init = Some(module.push_data(&[]));
129        shy.visibility = Visibility::Hidden;
130        module.add_global(shy);
131
132        module.add_alias(Alias::new(names.intern("second"), names.intern("here")));
133        module
134    }
135
136    #[test]
137    fn a_function_this_file_only_declares_is_reached_through_the_table() {
138        let mut names = Interner::new();
139        let module = module(&mut names);
140        let elsewhere = Elsewhere::of(&module, Pic::Executable);
141        assert!(elsewhere.holds(names.intern("exit")));
142    }
143
144    #[test]
145    fn a_function_this_file_defines_is_not() {
146        let mut names = Interner::new();
147        let module = module(&mut names);
148        let elsewhere = Elsewhere::of(&module, Pic::Executable);
149        assert!(!elsewhere.holds(names.intern("here")));
150    }
151
152    #[test]
153    fn a_name_the_module_does_not_carry_at_all_is_not() {
154        let mut names = Interner::new();
155        let module = module(&mut names);
156        let elsewhere = Elsewhere::of(&module, Pic::Executable);
157        assert!(!elsewhere.holds(names.intern("nowhere")));
158    }
159
160    /// The whole of what an executable pays, which is one entry for the one function it calls in a
161    /// library. Every variable is reached from the instruction pointer, the one it does not define
162    /// included, because the linker copies that one in here.
163    #[test]
164    fn an_executable_pays_for_the_functions_and_for_nothing_else() {
165        let mut names = Interner::new();
166        let module = module(&mut names);
167        let elsewhere = Elsewhere::of(&module, Pic::Executable);
168        for name in ["kept", "away", "quiet", "shy", "second"] {
169            assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
170        }
171    }
172
173    /// A library pays for every name it exports, defined here or not, because the definition the
174    /// process uses may be in another object however plainly this file defines it.
175    #[test]
176    fn a_library_pays_for_every_name_something_else_may_define() {
177        let mut names = Interner::new();
178        let module = module(&mut names);
179        let elsewhere = Elsewhere::of(&module, Pic::Library);
180        for name in ["here", "exit", "kept", "away", "second"] {
181            assert!(elsewhere.holds(names.intern(name)), "{name} was not in the table");
182        }
183    }
184
185    /// And not for the names nothing outside can reach, which is what makes `-fvisibility=hidden`
186    /// worth writing next to it.
187    #[test]
188    fn a_library_pays_nothing_for_a_name_nothing_outside_it_can_see() {
189        let mut names = Interner::new();
190        let module = module(&mut names);
191        let elsewhere = Elsewhere::of(&module, Pic::Library);
192        assert!(!elsewhere.holds(names.intern("quiet")));
193        assert!(!elsewhere.holds(names.intern("shy")));
194    }
195}