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//!
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        Self::read(module, &self.referred)
281    }
282
283    /// The variables a declaration said are in a DLL and that some function here still takes the
284    /// address of, in the order the module has them, which are the ones read through
285    /// [`Slot::Imported`].
286    ///
287    /// Asked once the functions have been compiled, for the reason [`Self::referred`] is. The
288    /// driver hands these to the linker by name on Microsoft's side, which is tamnd/rucc#2182:
289    /// when the static C runtime defines such a variable, `lld-link` makes the pointer itself and
290    /// then throws the variable away as unreferenced, so the pointer holds the image base.
291    #[must_use]
292    pub fn imported_variables(&self, module: &Module) -> Vec<Symbol> {
293        Self::read(module, &self.imported)
294    }
295
296    /// The variables among `names` whose address some function here takes.
297    fn read(module: &Module, names: &HashSet<Symbol>) -> Vec<Symbol> {
298        if names.is_empty() {
299            return Vec::new();
300        }
301        let mut read = HashSet::new();
302        for id in module.funcs() {
303            let func = &module[id];
304            for block in func.blocks() {
305                for inst in func.insts(block) {
306                    let data = &func[inst];
307                    if let (Opcode::GlobalAddr, Extra::Symbol(name)) = (data.opcode, data.extra) {
308                        read.insert(name);
309                    }
310                }
311            }
312        }
313        module
314            .globals()
315            .map(|id| module[id].name)
316            .filter(|name| names.contains(name) && read.contains(name))
317            .collect()
318    }
319
320    /// Whether that name is a variable every thread has its own copy of.
321    ///
322    /// Asked before [`Self::holds`] and not instead of it, because the two answers are about
323    /// different things: a thread-local variable that another object may define is still reached
324    /// the same way, since the table slot holds an offset that is the same for every copy and the
325    /// question of whose copy is answered by the segment register rather than by the link.
326    #[must_use]
327    pub fn thread(&self, name: Symbol) -> bool {
328        self.threads.contains(&name)
329    }
330
331    /// Whether a call to that name may come back more than once, because a declaration of it said
332    /// `returns_twice`.
333    ///
334    /// Not a question about addresses like the two above, but it is the same kind of fact: it is
335    /// about the module, the function it changes is a different one from the function it is
336    /// written on, and the code generator sees one function at a time. See
337    /// [`crate::tail::comes_back`] for what the caller does with it.
338    #[must_use]
339    pub fn twice(&self, name: Symbol) -> bool {
340        self.twice.contains(&name)
341    }
342
343    /// Whether a thread-local variable is reached by calling through its descriptor, which is how
344    /// Mach-O does it on both architectures.
345    ///
346    /// The slot the table holds for such a variable is the address of the descriptor rather than an
347    /// offset from the thread pointer, and the first word of the descriptor is a function that takes
348    /// that address and gives back this thread's copy. So there is no thread pointer to add to,
349    /// and the answer is the value the call returns.
350    #[must_use]
351    pub const fn described(&self) -> bool {
352        self.described
353    }
354
355    /// Whether a thread-local variable is reached through the array of `.tls` copies a Windows
356    /// thread keeps, which is how COFF does it. See `crate::select::Indexed`.
357    #[must_use]
358    pub const fn indexed(&self) -> bool {
359        self.indexed
360    }
361}
362
363/// The same set, written out by hand.
364///
365/// [`Elsewhere::of`] is how the driver builds one and is the only way a compilation does. This is
366/// for a test that wants to lower one function and say what is outside the file without building a
367/// module for it to be outside of.
368impl FromIterator<Symbol> for Elsewhere {
369    fn from_iter<T: IntoIterator<Item = Symbol>>(names: T) -> Self {
370        Self { names: names.into_iter().collect(), ..Self::default() }
371    }
372}
373
374impl Elsewhere {
375    /// The same set with those names said to be thread-local, for a test that lowers one function.
376    #[must_use]
377    pub fn with_threads<T: IntoIterator<Item = Symbol>>(mut self, threads: T) -> Self {
378        self.threads = threads.into_iter().collect();
379        self
380    }
381
382    /// The same set with thread-locals reached through a descriptor, for a test that lowers one
383    /// function the way Mach-O would.
384    #[must_use]
385    pub const fn with_descriptors(mut self) -> Self {
386        self.described = true;
387        self
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    use rucc_base::Interner;
396    use rucc_ir::{
397        Alias, Builder, Func, Global, InstData, Linkage, Signature, TlsModel, Visibility,
398    };
399    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
400
401    /// A module with one of everything: a function with a body and one without, a variable with an
402    /// image and one without, a `static`, a hidden export, an alias and a thread-local.
403    fn module(names: &mut Interner) -> Module {
404        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
405        let mut module = Module::new(names.intern("test.c"), &target);
406        let mut defined = Func::new(names.intern("here"), Signature::new());
407        defined.create_block();
408        module.add_func(defined);
409        module.add_func(Func::new(names.intern("exit"), Signature::new()));
410
411        let mut kept = Global::new(names.intern("kept"), 4, 4);
412        kept.init = Some(module.push_data(&[]));
413        module.add_global(kept);
414        module.add_global(Global::new(names.intern("away"), 4, 4));
415
416        let mut quiet = Global::new(names.intern("quiet"), 4, 4);
417        quiet.init = Some(module.push_data(&[]));
418        quiet.linkage = Linkage::Internal;
419        module.add_global(quiet);
420
421        let mut shy = Global::new(names.intern("shy"), 4, 4);
422        shy.init = Some(module.push_data(&[]));
423        shy.visibility = Visibility::Hidden;
424        module.add_global(shy);
425
426        let mut own = Global::new(names.intern("own"), 4, 4);
427        own.init = Some(module.push_data(&[]));
428        own.tls = Some(TlsModel::GlobalDynamic);
429        module.add_global(own);
430
431        module.add_alias(Alias::new(names.intern("second"), names.intern("here")));
432        module
433    }
434
435    #[test]
436    fn a_variable_every_thread_has_its_own_copy_of_is_one() {
437        let mut names = Interner::new();
438        let module = module(&mut names);
439        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
440        assert!(elsewhere.thread(names.intern("own")));
441    }
442
443    /// The question the other five ask is a different question, and a variable that is not
444    /// thread-local answering yes to this one would put an offset where an address belongs.
445    #[test]
446    fn an_ordinary_variable_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        for name in ["kept", "away", "quiet", "shy", "here"] {
451            assert!(!elsewhere.thread(names.intern(name)), "{name} was called thread-local");
452        }
453    }
454
455    #[test]
456    fn a_function_this_file_only_declares_is_reached_through_the_table() {
457        let mut names = Interner::new();
458        let module = module(&mut names);
459        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
460        assert!(elsewhere.holds(names.intern("exit")));
461    }
462
463    #[test]
464    fn a_function_this_file_defines_is_not() {
465        let mut names = Interner::new();
466        let module = module(&mut names);
467        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
468        assert!(!elsewhere.holds(names.intern("here")));
469    }
470
471    #[test]
472    fn a_name_the_module_does_not_carry_at_all_is_not() {
473        let mut names = Interner::new();
474        let module = module(&mut names);
475        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
476        assert!(!elsewhere.holds(names.intern("nowhere")));
477    }
478
479    /// The whole of what an executable pays, which is one entry for the one function it calls in a
480    /// library. Every variable is reached from the instruction pointer, the one it does not define
481    /// included, because the linker copies that one in here.
482    #[test]
483    fn an_executable_pays_for_the_functions_and_for_nothing_else() {
484        let mut names = Interner::new();
485        let module = module(&mut names);
486        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
487        for name in ["kept", "away", "quiet", "shy", "second"] {
488            assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
489        }
490    }
491
492    /// A weak variable nothing defines may be at zero, which no distance from the code reaches.
493    #[test]
494    fn a_weak_variable_this_file_only_declares_is_reached_through_the_table() {
495        let mut names = Interner::new();
496        let mut module = module(&mut names);
497        let mut maybe = Global::new(names.intern("maybe"), 4, 4);
498        maybe.linkage = Linkage::Weak;
499        module.add_global(maybe);
500        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
501        assert!(elsewhere.holds(names.intern("maybe")));
502    }
503
504    /// Mach-O never copies a variable into the executable, so the one this file only declares is
505    /// read through the table even in a program, and the ones it defines are still reached
506    /// directly.
507    #[test]
508    fn a_mach_o_executable_pays_for_the_variables_it_does_not_define_as_well() {
509        let mut names = Interner::new();
510        let mut module = module(&mut names);
511        let mut near = Global::new(names.intern("near"), 4, 4);
512        near.visibility = Visibility::Hidden;
513        module.add_global(near);
514        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::MachO, true);
515        assert!(elsewhere.holds(names.intern("away")));
516        for name in ["kept", "quiet", "shy", "near"] {
517            assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
518        }
519    }
520
521    /// An AArch64 executable pays for a variable it only declares, because the linker there makes
522    /// no copy for an `adrp` and refuses one in a PIE. bzip2 reading `stderr` is what found it.
523    #[test]
524    fn an_executable_that_gets_no_copies_pays_for_the_variables_it_does_not_define() {
525        let mut names = Interner::new();
526        let module = module(&mut names);
527        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, false);
528        assert!(elsewhere.holds(names.intern("away")));
529        for name in ["kept", "quiet", "shy"] {
530            assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
531        }
532        let copied = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
533        assert!(!copied.holds(names.intern("away")));
534    }
535
536    /// A library pays for every name it exports, defined here or not, because the definition the
537    /// process uses may be in another object however plainly this file defines it.
538    #[test]
539    fn a_library_pays_for_every_name_something_else_may_define() {
540        let mut names = Interner::new();
541        let module = module(&mut names);
542        let elsewhere = Elsewhere::of(&module, Pic::Library, ObjectFormat::Elf, true);
543        for name in ["here", "exit", "kept", "away", "second"] {
544            assert!(elsewhere.holds(names.intern(name)), "{name} was not in the table");
545        }
546    }
547
548    /// A format with no table asks nothing of anybody, which is not the same as asking and being
549    /// told no. The name of a function this file only declares stands for an address in the image
550    /// on this format whether the link finds it in another object or in an import library, so the
551    /// instruction pointer reaches it and there is nothing left over to put in a table. gcc writes
552    /// the same `leaq other(%rip)` for the same declaration.
553    #[test]
554    fn a_format_with_no_table_puts_nothing_in_one() {
555        let mut names = Interner::new();
556        let module = module(&mut names);
557        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
558        for name in ["here", "exit", "kept", "away", "quiet", "shy", "second"] {
559            assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
560        }
561    }
562
563    /// And the flag that fills the table on the other format does not fill it here either, since
564    /// there is no interposition on this one for it to be about.
565    #[test]
566    fn a_format_with_no_table_does_not_grow_one_under_the_library_flag() {
567        let mut names = Interner::new();
568        let module = module(&mut names);
569        let elsewhere = Elsewhere::of(&module, Pic::Library, ObjectFormat::Coff, true);
570        for name in ["here", "exit", "kept", "away", "second"] {
571            assert!(!elsewhere.holds(names.intern(name)), "{name} was in the table");
572        }
573    }
574
575    /// The other question this type answers is not the table's, so it keeps its answer whatever the
576    /// format. What a target with no thread-local storage does about it is the writer's refusal
577    /// rather than a name quietly left out here.
578    #[test]
579    fn a_format_with_no_table_still_says_which_variable_every_thread_has_a_copy_of() {
580        let mut names = Interner::new();
581        let module = module(&mut names);
582        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
583        assert!(elsewhere.thread(names.intern("own")));
584    }
585
586    /// And not for the names nothing outside can reach, which is what makes `-fvisibility=hidden`
587    /// worth writing next to it.
588    #[test]
589    fn a_library_pays_nothing_for_a_name_nothing_outside_it_can_see() {
590        let mut names = Interner::new();
591        let module = module(&mut names);
592        let elsewhere = Elsewhere::of(&module, Pic::Library, ObjectFormat::Elf, true);
593        assert!(!elsewhere.holds(names.intern("quiet")));
594        assert!(!elsewhere.holds(names.intern("shy")));
595    }
596
597    /// The module above with the names a Windows program has: a function and a variable a
598    /// declaration said are in a DLL, a variable it only declares and one it said is hidden, and
599    /// a function that reads every variable in it.
600    fn windows(names: &mut Interner) -> Module {
601        let mut module = module(names);
602        let mut pid = Func::new(names.intern("GetCurrentProcessId"), Signature::new());
603        pid.dll = Dll::Import;
604        module.add_func(pid);
605        let mut mode = Global::new(names.intern("_fmode"), 4, 4);
606        mode.dll = Dll::Import;
607        module.add_global(mode);
608        let mut near = Global::new(names.intern("near"), 4, 4);
609        near.visibility = Visibility::Hidden;
610        module.add_global(near);
611        let mut reader = Func::new(names.intern("reader"), Signature::new());
612        let block = reader.create_block();
613        for name in ["kept", "away", "quiet", "_fmode", "near"] {
614            let symbol = names.intern(name);
615            let data =
616                InstData { extra: Extra::Symbol(symbol), ..InstData::new(Opcode::GlobalAddr) };
617            Builder::new(&mut reader, block).value(data, rucc_ir::Type::PTR);
618        }
619        module.add_func(reader);
620        module
621    }
622
623    /// What a declaration said is in a DLL is reached through the pointer the loader fills in,
624    /// whether it is a function or a variable, and what it said nothing about keeps the plain name
625    /// for a function and gets a pointer of the file's own for a variable.
626    #[test]
627    fn a_name_in_a_dll_is_reached_through_the_pointer_the_loader_fills_in() {
628        let mut names = Interner::new();
629        let module = windows(&mut names);
630        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
631        for name in ["GetCurrentProcessId", "_fmode"] {
632            assert_eq!(elsewhere.slot(names.intern(name)), Some(Slot::Imported), "{name}");
633        }
634        assert_eq!(elsewhere.slot(names.intern("away")), Some(Slot::Referred));
635        for name in ["exit", "here", "kept", "quiet", "shy", "own", "near"] {
636            assert_eq!(elsewhere.slot(names.intern(name)), None, "{name}");
637        }
638        assert_eq!(Slot::Imported.name("_fmode"), "__imp__fmode");
639        assert_eq!(Slot::Referred.name("away"), ".refptr.away");
640    }
641
642    /// A pointer of the file's own is written only for a name some function still reads, so
643    /// `unread`, which is declared the way `away` is and read by nothing, gets none.
644    #[test]
645    fn a_pointer_is_written_only_for_a_variable_the_code_reads() {
646        let mut names = Interner::new();
647        let mut module = windows(&mut names);
648        module.add_global(Global::new(names.intern("unread"), 4, 4));
649        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Coff, true);
650        assert_eq!(elsewhere.referred(&module), vec![names.intern("away")]);
651    }
652
653    /// Every other format has a table of its own and never asks for either pointer.
654    #[test]
655    fn a_format_with_a_table_has_no_pointers() {
656        let mut names = Interner::new();
657        let module = windows(&mut names);
658        let elsewhere = Elsewhere::of(&module, Pic::Executable, ObjectFormat::Elf, true);
659        for name in ["GetCurrentProcessId", "_fmode", "away"] {
660            assert_eq!(elsewhere.slot(names.intern(name)), None, "{name}");
661        }
662        assert!(elsewhere.referred(&module).is_empty());
663    }
664}