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//!
35//! COFF has no global offset table and answers the same question with pointers of its own, one per
36//! name, which is [`Elsewhere::slot`]. A name a declaration said is in another DLL is reached
37//! through the pointer the loader fills in for it, and a variable this file only declares is
38//! reached through a pointer the file writes itself, so that the link may send it to a DLL without
39//! this file having known.
40
41use std::collections::HashSet;
42
43use rucc_base::Symbol;
44use rucc_ir::{AttrSet, Dll, Extra, Linkage, Module, Opcode, Pic, Visibility};
45use rucc_target::ObjectFormat;
46
47/// The names whose address only the linker knows.
48///
49/// Two ways in, and the first one holds whichever link is coming. A function this file only
50/// declares is one, because a function cannot be copied: it has exactly one address that every
51/// object in the program has to agree on, or two pointers to it compare unequal, so the one address
52/// is what the table holds and what everything reads. A variable can be copied, and in an
53/// executable it is, since the linker answers a reference to one another object defines by making
54/// room for it here and copying it there, so the name really does end up somewhere this file can
55/// measure to.
56///
57/// The second way in is `-fPIC`, where the link may be one that produces a shared library and the
58/// copying does not happen. There every replaceable name is in here, defined or not and function or
59/// variable, because the definition the process ends up using may be in another object however
60/// plainly this file defines it. What is not in here is what `-fPIC` costs nothing for: a `static`,
61/// and a name marked hidden or protected, which is the reason `-fPIC -fvisibility=hidden` is the
62/// combination a library that cares about its own speed is built with.
63///
64/// Both ways in are shut on a format with no such table, which is COFF. See `Self::table` for why
65/// the question has a different answer there rather than no answer.
66///
67/// A name this module has never heard of is not in here. Nothing the front end writes produces one,
68/// and treating an unknown name as a function would put the addresses the instrumentation takes of
69/// its own tables through a table of their own for no reason.
70///
71/// A thread-local variable is kept separately and answered by [`Self::thread`], because the two
72/// questions have different answers rather than one being a case of the other: the table slot of an
73/// ordinary name holds its address and the slot of a thread-local holds an offset, and reading
74/// either as though it were the other is a wrong answer rather than a slower one.
75#[derive(Debug, Clone, Default, PartialEq, Eq)]
76pub struct Elsewhere {
77 names: HashSet<Symbol>,
78 threads: HashSet<Symbol>,
79 twice: HashSet<Symbol>,
80 described: bool,
81 indexed: bool,
82 imported: HashSet<Symbol>,
83 referred: HashSet<Symbol>,
84}
85
86/// Which pointer a name on COFF is reached through, when it is reached through one.
87///
88/// The code is the same for both, a load of the pointer from the instruction pointer and then the
89/// name's address in a register. What differs is who writes the pointer.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum Slot {
92 /// The one the loader fills in for a name in another DLL, which the import library calls
93 /// `__imp_` and the name. A declaration said `dllimport`, so there is no other way to the name:
94 /// the import library has no stub under the plain name for a variable, and for a function the
95 /// stub is a jump through this same pointer, so going through it here saves the jump.
96 Imported,
97 /// One this file writes itself, called `.refptr.` and the name, for a variable it only
98 /// declares and nothing said was in a DLL.
99 ///
100 /// The variable may still turn out to be in one, and `environ` in the C runtime is one that
101 /// is. Then the address a `lea` would work out is not a distance the linker has, since the DLL
102 /// is loaded wherever it fits, and what it does instead is write a record for the runtime to
103 /// patch the reference with once the DLL is loaded. A four byte reference from the code cannot
104 /// hold an address that far away, and an eight byte pointer in a data section can, so the
105 /// reference the runtime patches is this pointer. Every object that reads the name writes the
106 /// same one in a section of its own that the linker keeps one copy of, which is what gcc and
107 /// clang both do for `x86_64-w64-mingw32`.
108 Referred,
109}
110
111impl Slot {
112 /// The pointer's own name, for a pointer to `name`.
113 #[must_use]
114 pub fn name(self, name: &str) -> String {
115 match self {
116 Self::Imported => format!("__imp_{name}"),
117 Self::Referred => format!(".refptr.{name}"),
118 }
119 }
120}
121
122impl Elsewhere {
123 /// The names that link cannot reach from the instruction pointer.
124 ///
125 /// `copies` is whether the linker answers a reference from the instruction pointer to a
126 /// variable another object defines by copying the variable into the executable. x86-64 does,
127 /// even in a position independent executable. AArch64 and RISC-V do not: GNU ld refuses an
128 /// `adrp` against such a variable when it makes a PIE, which is the default link on every
129 /// distribution, and gcc reads the address out of the table there instead.
130 #[must_use]
131 pub fn of(module: &Module, pic: Pic, format: ObjectFormat, copies: bool) -> Self {
132 let threads = module
133 .globals()
134 .filter(|&id| module[id].tls.is_some())
135 .map(|id| module[id].name)
136 .collect();
137 let twice = module
138 .funcs()
139 .filter(|&id| module[id].attrs.set.contains(AttrSet::RETURNS_TWICE))
140 .map(|id| module[id].name)
141 .collect();
142 let described = format == ObjectFormat::MachO;
143 let indexed = format == ObjectFormat::Coff;
144 let (imported, referred) = Self::pointers(module, format);
145 Self {
146 threads,
147 twice,
148 described,
149 indexed,
150 imported,
151 referred,
152 ..Self::table(module, pic, format, copies)
153 }
154 }
155
156 /// The names COFF reaches through a pointer, as the ones a declaration said are in another DLL
157 /// and the ones this file writes a pointer to itself. Both are empty on every other format.
158 ///
159 /// A variable gets a pointer of this file's own when it is only declared here, the linker may
160 /// see it, and nothing said where it is. A thread-local is left out, since it has no address
161 /// to point at and [`Self::thread`] answers for it. So is a hidden one, which is promised to be
162 /// in this image: clang reaches that one directly and so does this, where gcc still goes
163 /// through a pointer for it. A function never gets one from this file, since the import
164 /// library's stub under the plain name is already an address in this image, and neither
165 /// compiler writes one for a function either.
166 fn pointers(module: &Module, format: ObjectFormat) -> (HashSet<Symbol>, HashSet<Symbol>) {
167 if format != ObjectFormat::Coff {
168 return (HashSet::new(), HashSet::new());
169 }
170 let funcs = module
171 .funcs()
172 .filter(|&id| module[id].is_declaration() && module[id].dll == Dll::Import)
173 .map(|id| module[id].name);
174 let globals = module
175 .globals()
176 .filter(|&id| module[id].is_declaration() && module[id].tls.is_none())
177 .filter(|&id| module[id].dll == Dll::Import)
178 .map(|id| module[id].name);
179 let referred = module
180 .globals()
181 .filter(|&id| {
182 let global = &module[id];
183 global.is_declaration()
184 && global.tls.is_none()
185 && global.dll != Dll::Import
186 && global.visibility == Visibility::Default
187 && matches!(global.linkage, Linkage::External | Linkage::Weak)
188 })
189 .map(|id| module[id].name)
190 .collect();
191 (funcs.chain(globals).collect(), referred)
192 }
193
194 /// The half of the above that is about the global offset table, which is the older one.
195 ///
196 /// Empty on a format that has no such table. COFF is the one, and it is not that the question
197 /// goes unanswered there: a name this file only declares is reached from the instruction
198 /// pointer like any other, because whatever supplies it supplies a piece of this image to
199 /// measure to. A name the link resolves out of another object is in the image, and a name that
200 /// comes from a DLL arrives through an import library, which is an archive member holding a
201 /// jump under the plain name, so the name still stands for an address in this image and every
202 /// object that takes it gets the one the linker kept. Measured against gcc 13.2 for
203 /// `x86_64-w64-mingw32`, which writes `leaq other(%rip), %rax` for the address of a function it
204 /// has only seen declared. Asking for a table there instead reached the object writer as a
205 /// relocation it has no way to write, which is what tamnd/rucc#1443 was.
206 ///
207 /// A variable has no stub to stand for it, so one this file only declares is reached through a
208 /// pointer instead, and so is anything a declaration said is in a DLL. Neither is a table the
209 /// linker builds, which is why they are [`Self::slot`] and not in here.
210 fn table(module: &Module, pic: Pic, format: ObjectFormat, copies: bool) -> Self {
211 if format == ObjectFormat::Coff {
212 return Self::default();
213 }
214 let funcs = module.funcs().filter(|&id| {
215 let func = &module[id];
216 func.is_declaration() || pic.replaceable(func.linkage, func.visibility)
217 });
218 // A weak variable nothing here defines is the one variable the copying above does not
219 // cover, since there may be no definition anywhere to copy and then its address is null. The
220 // distance from here to null is not a number the linker has, so lld refuses the
221 // `R_X86_64_PC32` and gcc reads the address out of a slot, which the linker fills with zero.
222 //
223 // Mach-O does no copying at all. `dyld` has no copy relocation, so a variable a library
224 // defines stays in the library and the only way to it is the slot. That is every variable
225 // this file only declares, unless it is hidden and so promised to be in the same image,
226 // and it is what clang writes: `_ext@GOTPAGE` on arm64 and `_ext@GOTPCREL` on x86-64.
227 let uncopied = format == ObjectFormat::MachO || !copies;
228 let globals = module
229 .globals()
230 .filter(|&id| {
231 let global = &module[id];
232 (global.is_declaration()
233 && (global.linkage == Linkage::Weak
234 || (uncopied && global.visibility == Visibility::Default)))
235 || pic.replaceable(global.linkage, global.visibility)
236 })
237 .map(|id| module[id].name);
238 // An alias is a symbol of its own with a linkage and a visibility of its own, so it answers
239 // this for itself the same way it answered the visibility question in #752. What it points
240 // at is a separate name and is decided separately, which is what `weak, alias,
241 // visibility("hidden")` over an exported definition needs.
242 let aliases = module
243 .aliases()
244 .filter(|&id| pic.replaceable(module[id].linkage, module[id].visibility))
245 .map(|id| module[id].name);
246 funcs.map(|id| module[id].name).chain(globals).chain(aliases).collect()
247 }
248
249 /// Whether the address of that name has to be read out of the global offset table.
250 #[must_use]
251 pub fn holds(&self, name: Symbol) -> bool {
252 self.names.contains(&name)
253 }
254
255 /// The pointer the address of that name is read out of on COFF, where it is read out of one.
256 ///
257 /// Asked after [`Self::thread`] and in place of [`Self::holds`], which is never yes on the
258 /// format this is ever yes on.
259 #[must_use]
260 pub fn slot(&self, name: Symbol) -> Option<Slot> {
261 if self.imported.contains(&name) {
262 Some(Slot::Imported)
263 } else if self.referred.contains(&name) {
264 Some(Slot::Referred)
265 } else {
266 None
267 }
268 }
269
270 /// The names this file has to write a pointer of its own for, in the order the module has
271 /// them, which are the ones [`Self::slot`] says are [`Slot::Referred`] and that some function
272 /// here takes the address of.
273 ///
274 /// Asked of the module once its functions have been compiled, because the question is which
275 /// references survived: a read the optimizer took out needs no pointer, and gcc and clang both
276 /// write one only for a name the code still reads. A pointer nothing reads would not be free
277 /// either, since it names the variable and so asks the link to find a definition of it.
278 #[must_use]
279 pub fn referred(&self, module: &Module) -> Vec<Symbol> {
280 if self.referred.is_empty() {
281 return Vec::new();
282 }
283 let mut read = HashSet::new();
284 for id in module.funcs() {
285 let func = &module[id];
286 for block in func.blocks() {
287 for inst in func.insts(block) {
288 let data = &func[inst];
289 if let (Opcode::GlobalAddr, Extra::Symbol(name)) = (data.opcode, data.extra) {
290 read.insert(name);
291 }
292 }
293 }
294 }
295 module
296 .globals()
297 .map(|id| module[id].name)
298 .filter(|name| self.referred.contains(name) && read.contains(name))
299 .collect()
300 }
301
302 /// Whether that name is a variable every thread has its own copy of.
303 ///
304 /// Asked before [`Self::holds`] and not instead of it, because the two answers are about
305 /// different things: a thread-local variable that another object may define is still reached
306 /// the same way, since the table slot holds an offset that is the same for every copy and the
307 /// question of whose copy is answered by the segment register rather than by the link.
308 #[must_use]
309 pub fn thread(&self, name: Symbol) -> bool {
310 self.threads.contains(&name)
311 }
312
313 /// Whether a call to that name may come back more than once, because a declaration of it said
314 /// `returns_twice`.
315 ///
316 /// Not a question about addresses like the two above, but it is the same kind of fact: it is
317 /// about the module, the function it changes is a different one from the function it is
318 /// written on, and the code generator sees one function at a time. See
319 /// [`crate::tail::comes_back`] for what the caller does with it.
320 #[must_use]
321 pub fn twice(&self, name: Symbol) -> bool {
322 self.twice.contains(&name)
323 }
324
325 /// Whether a thread-local variable is reached by calling through its descriptor, which is how
326 /// Mach-O does it on both architectures.
327 ///
328 /// The slot the table holds for such a variable is the address of the descriptor rather than an
329 /// offset from the thread pointer, and the first word of the descriptor is a function that takes
330 /// that address and gives back this thread's copy. So there is no thread pointer to add to,
331 /// and the answer is the value the call returns.
332 #[must_use]
333 pub const fn described(&self) -> bool {
334 self.described
335 }
336
337 /// Whether a thread-local variable is reached through the array of `.tls` copies a Windows
338 /// thread keeps, which is how COFF does it. See `crate::select::Indexed`.
339 #[must_use]
340 pub const fn indexed(&self) -> bool {
341 self.indexed
342 }
343}
344
345/// The same set, written out by hand.
346///
347/// [`Elsewhere::of`] is how the driver builds one and is the only way a compilation does. This is
348/// for a test that wants to lower one function and say what is outside the file without building a
349/// module for it to be outside of.
350impl FromIterator<Symbol> for Elsewhere {
351 fn from_iter<T: IntoIterator<Item = Symbol>>(names: T) -> Self {
352 Self { names: names.into_iter().collect(), ..Self::default() }
353 }
354}
355
356impl Elsewhere {
357 /// The same set with those names said to be thread-local, for a test that lowers one function.
358 #[must_use]
359 pub fn with_threads<T: IntoIterator<Item = Symbol>>(mut self, threads: T) -> Self {
360 self.threads = threads.into_iter().collect();
361 self
362 }
363
364 /// The same set with thread-locals reached through a descriptor, for a test that lowers one
365 /// function the way Mach-O would.
366 #[must_use]
367 pub const fn with_descriptors(mut self) -> Self {
368 self.described = true;
369 self
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 use rucc_base::Interner;
378 use rucc_ir::{
379 Alias, Builder, Func, Global, InstData, Linkage, Signature, TlsModel, Visibility,
380 };
381 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
382
383 /// A module with one of everything: a function with a body and one without, a variable with an
384 /// image and one without, a `static`, a hidden export, an alias and a thread-local.
385 fn module(names: &mut Interner) -> Module {
386 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
387 let mut module = Module::new(names.intern("test.c"), &target);
388 let mut defined = Func::new(names.intern("here"), Signature::new());
389 defined.create_block();
390 module.add_func(defined);
391 module.add_func(Func::new(names.intern("exit"), Signature::new()));
392
393 let mut kept = Global::new(names.intern("kept"), 4, 4);
394 kept.init = Some(module.push_data(&[]));
395 module.add_global(kept);
396 module.add_global(Global::new(names.intern("away"), 4, 4));
397
398 let mut quiet = Global::new(names.intern("quiet"), 4, 4);
399 quiet.init = Some(module.push_data(&[]));
400 quiet.linkage = Linkage::Internal;
401 module.add_global(quiet);
402
403 let mut shy = Global::new(names.intern("shy"), 4, 4);
404 shy.init = Some(module.push_data(&[]));
405 shy.visibility = Visibility::Hidden;
406 module.add_global(shy);
407
408 let mut own = Global::new(names.intern("own"), 4, 4);
409 own.init = Some(module.push_data(&[]));
410 own.tls = Some(TlsModel::GlobalDynamic);
411 module.add_global(own);
412
413 module.add_alias(Alias::new(names.intern("second"), names.intern("here")));
414 module
415 }
416
417 #[test]
418 fn a_variable_every_thread_has_its_own_copy_of_is_one() {
419 let mut names = Interner::new();
420 let module = module(&mut names);
421 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
422 assert!(elsewhere.thread(names.intern("own")));
423 }
424
425 /// The question the other five ask is a different question, and a variable that is not
426 /// thread-local answering yes to this one would put an offset where an address belongs.
427 #[test]
428 fn an_ordinary_variable_is_not() {
429 let mut names = Interner::new();
430 let module = module(&mut names);
431 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
432 for name in ["kept", "away", "quiet", "shy", "here"] {
433 assert!(!elsewhere.thread(names.intern(name)), "{name} was called thread-local");
434 }
435 }
436
437 #[test]
438 fn a_function_this_file_only_declares_is_reached_through_the_table() {
439 let mut names = Interner::new();
440 let module = module(&mut names);
441 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
442 assert!(elsewhere.holds(names.intern("exit")));
443 }
444
445 #[test]
446 fn a_function_this_file_defines_is_not() {
447 let mut names = Interner::new();
448 let module = module(&mut names);
449 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
450 assert!(!elsewhere.holds(names.intern("here")));
451 }
452
453 #[test]
454 fn a_name_the_module_does_not_carry_at_all_is_not() {
455 let mut names = Interner::new();
456 let module = module(&mut names);
457 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
458 assert!(!elsewhere.holds(names.intern("nowhere")));
459 }
460
461 /// The whole of what an executable pays, which is one entry for the one function it calls in a
462 /// library. Every variable is reached from the instruction pointer, the one it does not define
463 /// included, because the linker copies that one in here.
464 #[test]
465 fn an_executable_pays_for_the_functions_and_for_nothing_else() {
466 let mut names = Interner::new();
467 let module = module(&mut names);
468 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
469 for name in ["kept", "away", "quiet", "shy", "second"] {
470 assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
471 }
472 }
473
474 /// A weak variable nothing defines may be at zero, which no distance from the code reaches.
475 #[test]
476 fn a_weak_variable_this_file_only_declares_is_reached_through_the_table() {
477 let mut names = Interner::new();
478 let mut module = module(&mut names);
479 let mut maybe = Global::new(names.intern("maybe"), 4, 4);
480 maybe.linkage = Linkage::Weak;
481 module.add_global(maybe);
482 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
483 assert!(elsewhere.holds(names.intern("maybe")));
484 }
485
486 /// Mach-O never copies a variable into the executable, so the one this file only declares is
487 /// read through the table even in a program, and the ones it defines are still reached
488 /// directly.
489 #[test]
490 fn a_mach_o_executable_pays_for_the_variables_it_does_not_define_as_well() {
491 let mut names = Interner::new();
492 let mut module = module(&mut names);
493 let mut near = Global::new(names.intern("near"), 4, 4);
494 near.visibility = Visibility::Hidden;
495 module.add_global(near);
496 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::MachO, true);
497 assert!(elsewhere.holds(names.intern("away")));
498 for name in ["kept", "quiet", "shy", "near"] {
499 assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
500 }
501 }
502
503 /// An AArch64 executable pays for a variable it only declares, because the linker there makes
504 /// no copy for an `adrp` and refuses one in a PIE. bzip2 reading `stderr` is what found it.
505 #[test]
506 fn an_executable_that_gets_no_copies_pays_for_the_variables_it_does_not_define() {
507 let mut names = Interner::new();
508 let module = module(&mut names);
509 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, false);
510 assert!(elsewhere.holds(names.intern("away")));
511 for name in ["kept", "quiet", "shy"] {
512 assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
513 }
514 let copied = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
515 assert!(!copied.holds(names.intern("away")));
516 }
517
518 /// A library pays for every name it exports, defined here or not, because the definition the
519 /// process uses may be in another object however plainly this file defines it.
520 #[test]
521 fn a_library_pays_for_every_name_something_else_may_define() {
522 let mut names = Interner::new();
523 let module = module(&mut names);
524 let elsewhere = Elsewhere::of(&module, Pic::Library, ObjectFormat::Elf, true);
525 for name in ["here", "exit", "kept", "away", "second"] {
526 assert!(elsewhere.holds(names.intern(name)), "{name} was not in the table");
527 }
528 }
529
530 /// A format with no table asks nothing of anybody, which is not the same as asking and being
531 /// told no. The name of a function this file only declares stands for an address in the image
532 /// on this format whether the link finds it in another object or in an import library, so the
533 /// instruction pointer reaches it and there is nothing left over to put in a table. gcc writes
534 /// the same `leaq other(%rip)` for the same declaration.
535 #[test]
536 fn a_format_with_no_table_puts_nothing_in_one() {
537 let mut names = Interner::new();
538 let module = module(&mut names);
539 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
540 for name in ["here", "exit", "kept", "away", "quiet", "shy", "second"] {
541 assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
542 }
543 }
544
545 /// And the flag that fills the table on the other format does not fill it here either, since
546 /// there is no interposition on this one for it to be about.
547 #[test]
548 fn a_format_with_no_table_does_not_grow_one_under_the_library_flag() {
549 let mut names = Interner::new();
550 let module = module(&mut names);
551 let elsewhere = Elsewhere::of(&module, Pic::Library, ObjectFormat::Coff, true);
552 for name in ["here", "exit", "kept", "away", "second"] {
553 assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
554 }
555 }
556
557 /// The other question this type answers is not the table's, so it keeps its answer whatever the
558 /// format. What a target with no thread-local storage does about it is the writer's refusal
559 /// rather than a name quietly left out here.
560 #[test]
561 fn a_format_with_no_table_still_says_which_variable_every_thread_has_a_copy_of() {
562 let mut names = Interner::new();
563 let module = module(&mut names);
564 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
565 assert!(elsewhere.thread(names.intern("own")));
566 }
567
568 /// And not for the names nothing outside can reach, which is what makes `-fvisibility=hidden`
569 /// worth writing next to it.
570 #[test]
571 fn a_library_pays_nothing_for_a_name_nothing_outside_it_can_see() {
572 let mut names = Interner::new();
573 let module = module(&mut names);
574 let elsewhere = Elsewhere::of(&module, Pic::Library, ObjectFormat::Elf, true);
575 assert!(!elsewhere.holds(names.intern("quiet")));
576 assert!(!elsewhere.holds(names.intern("shy")));
577 }
578
579 /// The module above with the names a Windows program has: a function and a variable a
580 /// declaration said are in a DLL, a variable it only declares and one it said is hidden, and
581 /// a function that reads every variable in it.
582 fn windows(names: &mut Interner) -> Module {
583 let mut module = module(names);
584 let mut pid = Func::new(names.intern("GetCurrentProcessId"), Signature::new());
585 pid.dll = Dll::Import;
586 module.add_func(pid);
587 let mut mode = Global::new(names.intern("_fmode"), 4, 4);
588 mode.dll = Dll::Import;
589 module.add_global(mode);
590 let mut near = Global::new(names.intern("near"), 4, 4);
591 near.visibility = Visibility::Hidden;
592 module.add_global(near);
593 let mut reader = Func::new(names.intern("reader"), Signature::new());
594 let block = reader.create_block();
595 for name in ["kept", "away", "quiet", "_fmode", "near"] {
596 let symbol = names.intern(name);
597 let data =
598 InstData { extra: Extra::Symbol(symbol), ..InstData::new(Opcode::GlobalAddr) };
599 Builder::new(&mut reader, block).value(data, rucc_ir::Type::PTR);
600 }
601 module.add_func(reader);
602 module
603 }
604
605 /// What a declaration said is in a DLL is reached through the pointer the loader fills in,
606 /// whether it is a function or a variable, and what it said nothing about keeps the plain name
607 /// for a function and gets a pointer of the file's own for a variable.
608 #[test]
609 fn a_name_in_a_dll_is_reached_through_the_pointer_the_loader_fills_in() {
610 let mut names = Interner::new();
611 let module = windows(&mut names);
612 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
613 for name in ["GetCurrentProcessId", "_fmode"] {
614 assert_eq!(elsewhere.slot(names.intern(name)), Some(Slot::Imported), "{name}");
615 }
616 assert_eq!(elsewhere.slot(names.intern("away")), Some(Slot::Referred));
617 for name in ["exit", "here", "kept", "quiet", "shy", "own", "near"] {
618 assert_eq!(elsewhere.slot(names.intern(name)), None, "{name}");
619 }
620 assert_eq!(Slot::Imported.name("_fmode"), "__imp__fmode");
621 assert_eq!(Slot::Referred.name("away"), ".refptr.away");
622 }
623
624 /// A pointer of the file's own is written only for a name some function still reads, so
625 /// `unread`, which is declared the way `away` is and read by nothing, gets none.
626 #[test]
627 fn a_pointer_is_written_only_for_a_variable_the_code_reads() {
628 let mut names = Interner::new();
629 let mut module = windows(&mut names);
630 module.add_global(Global::new(names.intern("unread"), 4, 4));
631 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
632 assert_eq!(elsewhere.referred(&module), vec![names.intern("away")]);
633 }
634
635 /// Every other format has a table of its own and never asks for either pointer.
636 #[test]
637 fn a_format_with_a_table_has_no_pointers() {
638 let mut names = Interner::new();
639 let module = windows(&mut names);
640 let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
641 for name in ["GetCurrentProcessId", "_fmode", "away"] {
642 assert_eq!(elsewhere.slot(names.intern(name)), None, "{name}");
643 }
644 assert!(elsewhere.referred(&module).is_empty());
645 }
646}