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//!
28//! A thread-local variable is the other name this file cannot work the address of out for itself,
29//! and it is here for the same reason: which names are thread-local is a fact about the module and
30//! the code generator sees one function at a time. It is a harder case than the one above rather
31//! than a variation of it, because there is no address to work out at all. Every thread has its own
32//! copy, so what the link can say is only where the variable sits inside the block a thread gets,
33//! and turning that into an address is something the running program does. See [`Elsewhere::thread`].
34
35use std::collections::HashSet;
36
37use rucc_base::Symbol;
38use rucc_ir::{Module, Pic};
39
40/// The names whose address only the linker knows.
41///
42/// Two ways in, and the first one holds whichever link is coming. A function this file only
43/// declares is one, because a function cannot be copied: it has exactly one address that every
44/// object in the program has to agree on, or two pointers to it compare unequal, so the one address
45/// is what the table holds and what everything reads. A variable can be copied, and in an
46/// executable it is, since the linker answers a reference to one another object defines by making
47/// room for it here and copying it there, so the name really does end up somewhere this file can
48/// measure to.
49///
50/// The second way in is `-fPIC`, where the link may be one that produces a shared library and the
51/// copying does not happen. There every replaceable name is in here, defined or not and function or
52/// variable, because the definition the process ends up using may be in another object however
53/// plainly this file defines it. What is not in here is what `-fPIC` costs nothing for: a `static`,
54/// and a name marked hidden or protected, which is the reason `-fPIC -fvisibility=hidden` is the
55/// combination a library that cares about its own speed is built with.
56///
57/// A name this module has never heard of is not in here. Nothing the front end writes produces one,
58/// and treating an unknown name as a function would put the addresses the instrumentation takes of
59/// its own tables through a table of their own for no reason.
60///
61/// A thread-local variable is kept separately and answered by [`Self::thread`], because the two
62/// questions have different answers rather than one being a case of the other: the table slot of an
63/// ordinary name holds its address and the slot of a thread-local holds an offset, and reading
64/// either as though it were the other is a wrong answer rather than a slower one.
65#[derive(Debug, Clone, Default, PartialEq, Eq)]
66pub struct Elsewhere {
67 names: HashSet<Symbol>,
68 threads: HashSet<Symbol>,
69}
70
71impl Elsewhere {
72 /// The names that link cannot reach from the instruction pointer.
73 #[must_use]
74 pub fn of(module: &Module, pic: Pic) -> Self {
75 let threads = module
76 .globals()
77 .filter(|&id| module[id].tls.is_some())
78 .map(|id| module[id].name)
79 .collect();
80 Self { threads, ..Self::table(module, pic) }
81 }
82
83 /// The half of the above that is about the global offset table, which is the older one.
84 fn table(module: &Module, pic: Pic) -> Self {
85 let funcs = module.funcs().filter(|&id| {
86 let func = &module[id];
87 func.is_declaration() || pic.replaceable(func.linkage, func.visibility)
88 });
89 let globals = module
90 .globals()
91 .filter(|&id| pic.replaceable(module[id].linkage, module[id].visibility))
92 .map(|id| module[id].name);
93 // An alias is a symbol of its own with a linkage and a visibility of its own, so it answers
94 // this for itself the same way it answered the visibility question in #752. What it points
95 // at is a separate name and is decided separately, which is what `weak, alias,
96 // visibility("hidden")` over an exported definition needs.
97 let aliases = module
98 .aliases()
99 .filter(|&id| pic.replaceable(module[id].linkage, module[id].visibility))
100 .map(|id| module[id].name);
101 funcs.map(|id| module[id].name).chain(globals).chain(aliases).collect()
102 }
103
104 /// Whether the address of that name has to be read out of the global offset table.
105 #[must_use]
106 pub fn holds(&self, name: Symbol) -> bool {
107 self.names.contains(&name)
108 }
109
110 /// Whether that name is a variable every thread has its own copy of.
111 ///
112 /// Asked before [`Self::holds`] and not instead of it, because the two answers are about
113 /// different things: a thread-local variable that another object may define is still reached
114 /// the same way, since the table slot holds an offset that is the same for every copy and the
115 /// question of whose copy is answered by the segment register rather than by the link.
116 #[must_use]
117 pub fn thread(&self, name: Symbol) -> bool {
118 self.threads.contains(&name)
119 }
120}
121
122/// The same set, written out by hand.
123///
124/// [`Elsewhere::of`] is how the driver builds one and is the only way a compilation does. This is
125/// for a test that wants to lower one function and say what is outside the file without building a
126/// module for it to be outside of.
127impl FromIterator<Symbol> for Elsewhere {
128 fn from_iter<T: IntoIterator<Item = Symbol>>(names: T) -> Self {
129 Self { names: names.into_iter().collect(), threads: HashSet::new() }
130 }
131}
132
133impl Elsewhere {
134 /// The same set with those names said to be thread-local, for a test that lowers one function.
135 #[must_use]
136 pub fn with_threads<T: IntoIterator<Item = Symbol>>(mut self, threads: T) -> Self {
137 self.threads = threads.into_iter().collect();
138 self
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 use rucc_base::Interner;
147 use rucc_ir::{Alias, Func, Global, Linkage, Signature, TlsModel, Visibility};
148 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
149
150 /// A module with one of everything: a function with a body and one without, a variable with an
151 /// image and one without, a `static`, a hidden export, an alias and a thread-local.
152 fn module(names: &mut Interner) -> Module {
153 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
154 let mut module = Module::new(names.intern("test.c"), &target);
155 let mut defined = Func::new(names.intern("here"), Signature::new());
156 defined.create_block();
157 module.add_func(defined);
158 module.add_func(Func::new(names.intern("exit"), Signature::new()));
159
160 let mut kept = Global::new(names.intern("kept"), 4, 4);
161 kept.init = Some(module.push_data(&[]));
162 module.add_global(kept);
163 module.add_global(Global::new(names.intern("away"), 4, 4));
164
165 let mut quiet = Global::new(names.intern("quiet"), 4, 4);
166 quiet.init = Some(module.push_data(&[]));
167 quiet.linkage = Linkage::Internal;
168 module.add_global(quiet);
169
170 let mut shy = Global::new(names.intern("shy"), 4, 4);
171 shy.init = Some(module.push_data(&[]));
172 shy.visibility = Visibility::Hidden;
173 module.add_global(shy);
174
175 let mut own = Global::new(names.intern("own"), 4, 4);
176 own.init = Some(module.push_data(&[]));
177 own.tls = Some(TlsModel::GlobalDynamic);
178 module.add_global(own);
179
180 module.add_alias(Alias::new(names.intern("second"), names.intern("here")));
181 module
182 }
183
184 #[test]
185 fn a_variable_every_thread_has_its_own_copy_of_is_one() {
186 let mut names = Interner::new();
187 let module = module(&mut names);
188 let elsewhere = Elsewhere::of(&module, Pic::Executable);
189 assert!(elsewhere.thread(names.intern("own")));
190 }
191
192 /// The question the other five ask is a different question, and a variable that is not
193 /// thread-local answering yes to this one would put an offset where an address belongs.
194 #[test]
195 fn an_ordinary_variable_is_not() {
196 let mut names = Interner::new();
197 let module = module(&mut names);
198 let elsewhere = Elsewhere::of(&module, Pic::Executable);
199 for name in ["kept", "away", "quiet", "shy", "here"] {
200 assert!(!elsewhere.thread(names.intern(name)), "{name} was called thread-local");
201 }
202 }
203
204 #[test]
205 fn a_function_this_file_only_declares_is_reached_through_the_table() {
206 let mut names = Interner::new();
207 let module = module(&mut names);
208 let elsewhere = Elsewhere::of(&module, Pic::Executable);
209 assert!(elsewhere.holds(names.intern("exit")));
210 }
211
212 #[test]
213 fn a_function_this_file_defines_is_not() {
214 let mut names = Interner::new();
215 let module = module(&mut names);
216 let elsewhere = Elsewhere::of(&module, Pic::Executable);
217 assert!(!elsewhere.holds(names.intern("here")));
218 }
219
220 #[test]
221 fn a_name_the_module_does_not_carry_at_all_is_not() {
222 let mut names = Interner::new();
223 let module = module(&mut names);
224 let elsewhere = Elsewhere::of(&module, Pic::Executable);
225 assert!(!elsewhere.holds(names.intern("nowhere")));
226 }
227
228 /// The whole of what an executable pays, which is one entry for the one function it calls in a
229 /// library. Every variable is reached from the instruction pointer, the one it does not define
230 /// included, because the linker copies that one in here.
231 #[test]
232 fn an_executable_pays_for_the_functions_and_for_nothing_else() {
233 let mut names = Interner::new();
234 let module = module(&mut names);
235 let elsewhere = Elsewhere::of(&module, Pic::Executable);
236 for name in ["kept", "away", "quiet", "shy", "second"] {
237 assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
238 }
239 }
240
241 /// A library pays for every name it exports, defined here or not, because the definition the
242 /// process uses may be in another object however plainly this file defines it.
243 #[test]
244 fn a_library_pays_for_every_name_something_else_may_define() {
245 let mut names = Interner::new();
246 let module = module(&mut names);
247 let elsewhere = Elsewhere::of(&module, Pic::Library);
248 for name in ["here", "exit", "kept", "away", "second"] {
249 assert!(elsewhere.holds(names.intern(name)), "{name} was not in the table");
250 }
251 }
252
253 /// And not for the names nothing outside can reach, which is what makes `-fvisibility=hidden`
254 /// worth writing next to it.
255 #[test]
256 fn a_library_pays_nothing_for_a_name_nothing_outside_it_can_see() {
257 let mut names = Interner::new();
258 let module = module(&mut names);
259 let elsewhere = Elsewhere::of(&module, Pic::Library);
260 assert!(!elsewhere.holds(names.intern("quiet")));
261 assert!(!elsewhere.holds(names.intern("shy")));
262 }
263}