Skip to main content

rucc_asm/
data.rs

1//! Global variables as the image a file carries and the facts a linker needs about it.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.1, which asks that the text path and the
4//! binary path share one description so they cannot disagree. That is what this is for data: the
5//! walk over a module's globals happens once, here, and what it produces is a list of pieces that
6//! [`crate::att`] writes down as directives and [`Globals::image`] writes down as bytes. A `.long`
7//! in a listing and the four bytes in the object beside it come from the same piece.
8//!
9//! # What a piece is
10//!
11//! As much of an image as one directive says. The four kinds are the four things C can put in an
12//! initializer: a run of zeros, a run of literal bytes, one scalar, and the address of a symbol.
13//! The first three are bytes the compiler knows and the fourth is a hole the linker fills, which
14//! is the only reason data has relocations at all.
15//!
16//! Where a variable goes is worked out here too, from what the variable is rather than from
17//! anything the object format says: a variable nothing writes through goes in a page the loader
18//! can map read only, one whose image is all zeros goes in the section that carries no image, and
19//! one the program named a section for goes where the program said. What those sections are
20//! called is the format's business and is in [`crate::format`].
21//!
22//! # The second names
23//!
24//! [`aliases`] is the same idea for what `__attribute__((alias("target")))` asks for, and is here
25//! for the same reason: `.set b, a` in a listing and a second symbol table entry in an object have
26//! to be saying the same thing. There is no image in one, which is what makes an alias free.
27//!
28//! # What is refused
29//!
30//! A thread-local variable on a format that is not ELF. ELF says one with a section flag and a
31//! symbol type, which is what [`place`] and [`crate::format`] write. Windows hands out an index at
32//! load time and reaches a variable through a table the index names, and Mach-O puts a descriptor
33//! in front of every one and reaches it by calling through the descriptor, so neither is this
34//! written a different way and both are refused by name rather than written out as an ordinary
35//! variable that every thread would share.
36//!
37//! An ifunc, which is the other thing an alias in the IR can be. It is resolved once at program
38//! start by calling a function in the same object, which wants a symbol type and a relocation
39//! neither half of this writes yet.
40
41use rucc_base::{Interner, Symbol};
42use rucc_ir as ir;
43use rucc_ir::{AliasKind, Datum, GlobalId, Linkage, Module, SymbolRef};
44use rucc_object::{Alias, Apart, Binding, Data, Object, Place, Reference, Reloc, Visibility};
45use rucc_target::ObjectFormat;
46
47use crate::Error;
48
49/// Every variable a module defines, laid out.
50#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub struct Globals {
52    /// One entry per definition, in the order the module held them. A declaration is not here,
53    /// because a file says nothing about a variable another file defines beyond the references
54    /// that name it, and those are already in the text.
55    pub vars: Vec<Variable>,
56    /// Every name a declaration wrote `weak` on and this file does not define, in the order the
57    /// module held them.
58    ///
59    /// Not a definition and not bytes of anything, which is why it is a list of names beside the
60    /// variables rather than an entry among them. What it asks for is that the link be allowed to
61    /// leave the name undefined and hand every reference a zero address, which is how a library
62    /// offers a hook a profiler may fill in: the calls are written under `if (hook)` and the test
63    /// is false when nobody filled it in. Without it the link of a file that declares one fails
64    /// on an undefined symbol, which is what zstd's four tracing hooks do.
65    ///
66    /// Every one of them is here whether or not anything in the file refers to it, which is what
67    /// keeps the listing and the object saying the same thing: both read this list and neither
68    /// works out the answer for itself. gcc writes the directive only for the ones something
69    /// refers to, so a file that declares a hook and never calls it gets one undefined weak symbol
70    /// here that gcc does not put in. A linker has nothing to do about an undefined weak symbol
71    /// nothing refers to, which is why that difference is a difference and not a bug.
72    pub weak: Vec<String>,
73    /// The text of each `asm` at file scope with an instruction in it, in the order they were
74    /// written, which goes into the listing as it is. See `rucc_ir::Module::add_file_asm`.
75    ///
76    /// A unit with one is assembled from its listing, since only the assembler knows what the
77    /// template defines, which is why the driver asks [`Globals::kept`] beside
78    /// [`crate::kept`].
79    pub file_asm: Vec<String>,
80}
81
82impl Globals {
83    /// Whether anything here has to be read by the assembler, which today is an `asm` at file
84    /// scope with an instruction in it.
85    #[must_use]
86    pub fn kept(&self) -> bool {
87        !self.file_asm.is_empty()
88    }
89}
90
91/// One global variable, as the pieces of its image and what the linker is told about it.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct Variable {
94    /// Its name, as the C program spelled it. The underscore an Apple symbol carries is added
95    /// when it is written down, because it is a fact about the object format and not about the
96    /// variable.
97    pub name: String,
98    /// How many bytes it occupies, which the pieces add up to.
99    pub size: u64,
100    /// What it has to be aligned to, always a power of two.
101    pub align: u64,
102    /// Which section it goes in.
103    pub place: Place,
104    /// How the linker sees the name.
105    pub binding: Binding,
106    /// How far outside a shared library holding it the name reaches.
107    pub visibility: Visibility,
108    /// Its image, in order.
109    pub pieces: Vec<Piece>,
110}
111
112/// As much of an image as one directive says.
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub enum Piece {
115    /// That many zero bytes, which is the tail of a partly initialized array and the whole of a
116    /// variable with no initializer.
117    Zero(u64),
118    /// Those literal bytes, which is what a string literal and anything already laid out is.
119    Bytes(Vec<u8>),
120    /// One number, in the byte order the module was built for, as many bytes wide as its type.
121    Scalar(Vec<u8>),
122    /// The address of a symbol, which is a hole this compiler leaves and the linker fills.
123    Addr {
124        /// Whose address it is, as the C program spelled it.
125        symbol: String,
126        /// What to add to that address. `&array[2]` is the address of `array` plus eight.
127        addend: i64,
128        /// How many bytes it occupies.
129        bytes: u8,
130    },
131    /// How far a symbol is from these four bytes, which is the same hole with a different
132    /// question in it. `.long target - .` in an `asm` at file scope and nothing else.
133    Away {
134        /// Whose distance it is, as the template spelled it.
135        symbol: String,
136        /// What to add to that address before the distance is taken, which is how far into the
137        /// symbol the place being measured to sits.
138        addend: i64,
139    },
140    /// How far one label is from another, which is a number the writer works out once the code is
141    /// laid out and not a hole for the linker. `.long .L1-.L0` for GNU C's `&&l1 - &&l0`.
142    Apart {
143        /// The label measured to, as the module named it.
144        to: String,
145        /// The label measured from.
146        from: String,
147        /// What to add to the distance.
148        addend: i64,
149        /// How many bytes it occupies.
150        bytes: u8,
151    },
152}
153
154impl Piece {
155    /// How many bytes it contributes to the image.
156    #[must_use]
157    pub fn size(&self) -> u64 {
158        match self {
159            Piece::Zero(bytes) => *bytes,
160            Piece::Bytes(bytes) | Piece::Scalar(bytes) => bytes.len() as u64,
161            Piece::Addr { bytes, .. } | Piece::Apart { bytes, .. } => u64::from(*bytes),
162            Piece::Away { .. } => 4,
163        }
164    }
165}
166
167impl Globals {
168    /// The image of every variable, and where in each one the linker has to write an address.
169    ///
170    /// A variable in a section that carries no image contributes its size and none of its bytes,
171    /// which is what makes a program with a large zeroed array a small file.
172    #[must_use]
173    pub fn image(&self) -> Data {
174        let mut data = Data { weak: self.weak.clone(), ..Data::default() };
175        for var in &self.vars {
176            let mut object = Object {
177                name: var.name.clone(),
178                bytes: Vec::new(),
179                size: var.size,
180                align: var.align,
181                place: var.place.clone(),
182                binding: var.binding,
183                visibility: var.visibility,
184                relocs: Vec::new(),
185            };
186            if matches!(var.place, Place::Zero | Place::Merged | Place::Thread { zero: true }) {
187                data.objects.push(object);
188                continue;
189            }
190            for piece in &var.pieces {
191                match piece {
192                    Piece::Zero(bytes) => {
193                        object.bytes.resize(object.bytes.len() + *bytes as usize, 0);
194                    }
195                    Piece::Bytes(bytes) | Piece::Scalar(bytes) => {
196                        object.bytes.extend_from_slice(bytes);
197                    }
198                    Piece::Addr { symbol, addend, bytes } => {
199                        // The bytes are left zero rather than holding anything, because a linker
200                        // writes the whole hole from the addend and never reads what was there.
201                        object.relocs.push(Reloc {
202                            at: object.bytes.len(),
203                            symbol: symbol.clone(),
204                            kind: Reference::Address { bytes: *bytes },
205                            addend: *addend,
206                            // An image rather than an instruction, so there is nothing after the
207                            // hole for the question to be about.
208                            after: 0,
209                        });
210                        object.bytes.resize(object.bytes.len() + usize::from(*bytes), 0);
211                    }
212                    Piece::Away { symbol, addend } => {
213                        object.relocs.push(Reloc {
214                            at: object.bytes.len(),
215                            symbol: symbol.clone(),
216                            kind: Reference::Away,
217                            addend: *addend,
218                            after: 0,
219                        });
220                        object.bytes.resize(object.bytes.len() + 4, 0);
221                    }
222                    Piece::Apart { to, from, addend, bytes } => {
223                        data.apart.push(Apart {
224                            object: data.objects.len(),
225                            at: object.bytes.len(),
226                            to: to.clone(),
227                            from: from.clone(),
228                            addend: *addend,
229                            bytes: *bytes,
230                        });
231                        object.bytes.resize(object.bytes.len() + usize::from(*bytes), 0);
232                    }
233                }
234            }
235            data.objects.push(object);
236        }
237        data
238    }
239}
240
241/// Every variable a module defines, laid out.
242///
243/// The format is an argument because one question here is the format's rather than the module's:
244/// a thread-local variable is a section flag and a symbol type on ELF and an image and a descriptor
245/// on Mach-O, and is neither on COFF, so which of them is being written decides whether there is
246/// anything to write.
247///
248/// # Errors
249///
250/// [`Error::Thread`] for a thread-local variable on a format that does not spell one this way,
251/// which is a program this compiler is behind on rather than a mistake, and [`Error::Image`] for a
252/// piece of an initializer nothing here can write down. See [`Error`].
253pub fn globals(module: &Module, names: &Interner, format: ObjectFormat) -> Result<Globals, Error> {
254    let mut out = Globals::default();
255    for id in module.globals() {
256        if module[id].is_declaration() {
257            continue;
258        }
259        out.vars.push(variable(module, names, id, format)?);
260    }
261    // The other half, which is names and no bytes. A declaration is not a variable and has no
262    // image, so it is skipped above and picked up here, and only the weak ones are: an ordinary
263    // undefined name needs nothing said about it, since a reference to one is already an
264    // undefined symbol and a link that cannot resolve it is a link that should fail.
265    //
266    // The functions as well as the objects, and the functions are the ones a program actually
267    // writes: a hook a library offers is a function, and `if (hook)` around the call is the test
268    // that reads the zero address a weak reference gets. Both walks are here rather than one in
269    // each caller, for the reason the walk over the definitions above is one walk.
270    for id in module.funcs() {
271        let func = &module[id];
272        if func.is_declaration() && func.linkage == Linkage::Weak {
273            out.weak.push(names.resolve(func.name).to_owned());
274        }
275    }
276    for id in module.globals() {
277        let global = &module[id];
278        if global.is_declaration() && global.linkage == Linkage::Weak {
279            out.weak.push(names.resolve(global.name).to_owned());
280        }
281    }
282    out.file_asm = module.file_asms().to_vec();
283    Ok(out)
284}
285
286/// Every second name a module gives something, in the order it gave them.
287///
288/// One walk for the same reason the one over the globals above is one: `.set b, a` in a listing
289/// and a second symbol table entry in an object have to be saying the same thing, and the way to
290/// be sure of that is for both of them to be reading the same list.
291///
292/// # Errors
293///
294/// [`Error::IFunc`] for an ifunc, which is the other thing this shape of the IR carries and is a
295/// program this compiler is behind on rather than a mistake. See [`Error`].
296pub fn aliases(module: &Module, names: &Interner) -> Result<Vec<Alias>, Error> {
297    let mut out = Vec::new();
298    for id in module.aliases() {
299        let alias = &module[id];
300        let name = names.resolve(alias.name).to_owned();
301        if alias.kind != AliasKind::Alias {
302            return Err(Error::IFunc { name });
303        }
304        out.push(Alias {
305            name,
306            target: names.resolve(alias.target).to_owned(),
307            binding: binding(alias.linkage),
308            visibility: visibility(alias.visibility),
309        });
310    }
311    Ok(out)
312}
313
314/// One variable, laid out.
315fn variable(
316    module: &Module,
317    names: &Interner,
318    id: GlobalId,
319    format: ObjectFormat,
320) -> Result<Variable, Error> {
321    let global = &module[id];
322    let name = names.resolve(global.name).to_owned();
323    if global.tls.is_some() && format == ObjectFormat::Wasm {
324        return Err(Error::Thread { name, format: format.as_str() });
325    }
326    let init = global.init.expect("a definition has an image");
327
328    let mut pieces = Vec::new();
329    // The names the image holds the addresses of, kept as symbols rather than read back off the
330    // pieces, because whether one of them is defined here is a question about this module and the
331    // pieces carry the spelling rather than the name.
332    let mut addrs = Vec::new();
333    let mut written = 0;
334    for datum in &module[init] {
335        let piece = match *datum {
336            Datum::Zero(bytes) => Piece::Zero(bytes),
337            Datum::Bytes(range) => Piece::Bytes(module[range].to_vec()),
338            Datum::Scalar { ty, value } => {
339                if ty.lanes() != 1 {
340                    let why = format!("a {ty} in an initializer");
341                    return Err(Error::Image { name, why });
342                }
343                let bytes = usize::try_from(ty.bits().div_ceil(8)).expect("a scalar this wide");
344                let mut image = module[value].bits().to_le_bytes()[..bytes].to_vec();
345                if !module.datalayout.little_endian {
346                    image.reverse();
347                }
348                Piece::Scalar(image)
349            }
350            // A distance rather than an address, which is the same symbol and addend read a
351            // different way. It is not in `addrs` below, because what that list is for is whether
352            // a read only image needs relocating when it is loaded, and a distance between two
353            // places in the same file is the same number wherever the file is loaded.
354            Datum::Away(idx) => {
355                let reloc = module[idx];
356                if reloc.size != 4 {
357                    let why = format!("a distance {} bytes wide", reloc.size);
358                    return Err(Error::Image { name, why });
359                }
360                let symbol = names.resolve(reloc.symbol).to_owned();
361                Piece::Away { symbol, addend: reloc.addend }
362            }
363            // Both ends are labels of a function in this file, so neither goes in `addrs`: the
364            // distance is the same number wherever the file is loaded.
365            Datum::Apart { to, from } => {
366                let reloc = module[to];
367                let bytes = match reloc.size {
368                    1 | 2 | 4 | 8 => reloc.size as u8,
369                    size => {
370                        let why = format!("a distance {size} bytes wide");
371                        return Err(Error::Image { name, why });
372                    }
373                };
374                let to = names.resolve(reloc.symbol).to_owned();
375                let from = names.resolve(from).to_owned();
376                Piece::Apart { to, from, addend: reloc.addend, bytes }
377            }
378            Datum::Addr(idx) => {
379                let reloc = module[idx];
380                // Four and eight are the widths a machine has a relocation for and a directive
381                // for. Anything else is a module nothing here produced and neither half of the
382                // description could write down, so it is refused rather than rounded to one.
383                let bytes = match reloc.size {
384                    4 | 8 => reloc.size as u8,
385                    size => {
386                        let why = format!("an address {size} bytes wide");
387                        return Err(Error::Image { name, why });
388                    }
389                };
390                let symbol = names.resolve(reloc.symbol).to_owned();
391                addrs.push(reloc.symbol);
392                Piece::Addr { symbol, addend: reloc.addend, bytes }
393            }
394        };
395        written += piece.size();
396        pieces.push(piece);
397    }
398    // An image shorter than the variable is the rest of an array nothing initialized, which the
399    // front end may leave off the end rather than write out as zeros it already said were there.
400    if written < global.size {
401        pieces.push(Piece::Zero(global.size - written));
402    }
403
404    let place = match place(module, names, id, &pieces, &addrs) {
405        // A Windows image has no zeroed half of its thread-local template. Every thread gets a copy
406        // of the one `.tls` section, zeros included, which is what gcc writes there too.
407        Place::Thread { .. } if format == ObjectFormat::Coff => Place::Thread { zero: false },
408        place => place,
409    };
410    let size = global.size.max(written);
411    let binding = binding(global.linkage);
412    let visibility = visibility(global.visibility);
413    Ok(Variable { name, size, align: u64::from(global.align), place, binding, visibility, pieces })
414}
415
416/// What the linker is told about a name, from the linkage the module gave it.
417///
418/// Three of the five, because that is how many an object file can say. Which of the two weak ones
419/// a symbol had is a fact the optimizer needs and the linker does not, and a common one is a
420/// definition every other file may also make, which is a section rather than a binding.
421const fn binding(linkage: Linkage) -> Binding {
422    match linkage {
423        Linkage::Internal => Binding::Local,
424        Linkage::Weak | Linkage::LinkOnce => Binding::Weak,
425        Linkage::External | Linkage::Common => Binding::Global,
426    }
427}
428
429/// What the dynamic linker is told about a name, from the visibility the module gave it.
430///
431/// All three, because ELF says all three, and the two enumerations are the same three answers
432/// written once in a crate that is not allowed to know what an object file is and once in one
433/// that is.
434const fn visibility(visibility: ir::Visibility) -> Visibility {
435    match visibility {
436        ir::Visibility::Default => Visibility::Default,
437        ir::Visibility::Hidden => Visibility::Hidden,
438        ir::Visibility::Protected => Visibility::Protected,
439    }
440}
441
442/// Which section a variable goes in.
443///
444/// The program's answer when it gave one, and otherwise worked out from what the variable is. A
445/// tentative definition is asked of the linker rather than put anywhere, since the whole of what
446/// it says is that the variable exists and that some other file may say so too.
447///
448/// Being constant is not on its own enough to put a variable in a section nothing may ever write.
449/// An image holding the address of something is an image the loader has to write, because an
450/// address is not a number a link knows when everything it links may be moved. So the question
451/// asked of a constant variable is whether its image holds an address, and one that does goes in
452/// the section that is writable for exactly as long as the loader needs it to be.
453///
454/// A variable with no image at all is not zero filled, it is empty, and the two differ. An `asm`
455/// at file scope writes one whenever it puts a label at the end of what it just wrote, and what
456/// that label means is the address after those bytes, so it belongs in the section those bytes
457/// went into. Sending it to the section of zeros instead would move it away from the run it was
458/// written to mark the end of, and the distance a program reads off it would be a different
459/// distance.
460fn place(
461    module: &Module,
462    names: &Interner,
463    id: GlobalId,
464    pieces: &[Piece],
465    addrs: &[Symbol],
466) -> Place {
467    let global = &module[id];
468    // First, because a thread-local variable has to be in one of the two sections a thread gets a
469    // copy of whatever else is true of it. It is never merged, since what `.comm` asks the linker
470    // for is one piece of zeroed space and this wants one per thread, and it is never read only,
471    // since the copy is made by writing it.
472    if global.tls.is_some() {
473        let zero = !pieces.is_empty() && pieces.iter().all(|piece| matches!(piece, Piece::Zero(_)));
474        return Place::Thread { zero };
475    }
476    if let Some(section) = global.section {
477        return Place::Named(names.resolve(section).to_owned());
478    }
479    if global.linkage == Linkage::Common {
480        return Place::Merged;
481    }
482    if !pieces.is_empty() && pieces.iter().all(|piece| matches!(piece, Piece::Zero(_))) {
483        return Place::Zero;
484    }
485    if global.constant {
486        return match addrs {
487            [] => Place::ReadOnly,
488            _ => Place::RelocReadOnly {
489                local: addrs.iter().all(|&symbol| resolved_here(module, symbol)),
490            },
491        };
492    }
493    Place::Written
494}
495
496/// Whether that name is one this file both defines and keeps to itself.
497///
498/// Both halves matter. A name this file does not define is one the link resolves from somewhere
499/// else, and a name this file exports is one another object may define instead, so neither is an
500/// address the first pages of the relocated segment can be laid out around.
501fn resolved_here(module: &Module, symbol: Symbol) -> bool {
502    match module.lookup(symbol) {
503        Some(SymbolRef::Func(id)) => {
504            module[id].linkage == Linkage::Internal && !module[id].is_declaration()
505        }
506        Some(SymbolRef::Global(id)) => {
507            module[id].linkage == Linkage::Internal && !module[id].is_declaration()
508        }
509        Some(SymbolRef::Alias(id)) => module[id].linkage == Linkage::Internal,
510        None => false,
511    }
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517
518    use rucc_ir::{Alias as IrAlias, Global, Imm, Reloc as IrReloc, TlsModel, Type};
519    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
520
521    /// A module for the one target every case here is written for.
522    fn module(names: &mut Interner) -> Module {
523        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
524        Module::new(names.intern("t.c"), &target)
525    }
526
527    /// A four byte variable with that image.
528    fn defined(module: &mut Module, names: &mut Interner, name: &str, data: &[Datum]) -> GlobalId {
529        let list = module.push_data(data);
530        let mut global = Global::new(names.intern(name), 4, 4);
531        global.init = Some(list);
532        module.add_global(global)
533    }
534
535    #[test]
536    fn a_declaration_is_not_a_variable_this_file_defines() {
537        let mut names = Interner::new();
538        let mut module = module(&mut names);
539        module.add_global(Global::new(names.intern("x"), 4, 4));
540        defined(&mut module, &mut names, "y", &[Datum::Zero(4)]);
541        let vars =
542            globals(&module, &names, ObjectFormat::Elf).expect("a module of two globals").vars;
543        assert_eq!(vars.iter().map(|var| var.name.as_str()).collect::<Vec<_>>(), ["y"]);
544    }
545
546    /// A variable's visibility comes through the layout and out the other side of the image.
547    ///
548    /// Two hops rather than one, because a variable is laid out here and then turned into an
549    /// object for the writer a hundred lines further up, and a field that survives the first and
550    /// not the second is a field the object file never hears about.
551    #[test]
552    fn the_visibility_a_variable_asked_for_reaches_the_image() {
553        for (asked, wanted) in [
554            (ir::Visibility::Default, Visibility::Default),
555            (ir::Visibility::Hidden, Visibility::Hidden),
556            (ir::Visibility::Protected, Visibility::Protected),
557        ] {
558            let mut names = Interner::new();
559            let mut module = module(&mut names);
560            let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
561            module[id].visibility = asked;
562            let out = globals(&module, &names, ObjectFormat::Elf).expect("a module of one global");
563            assert_eq!(out.vars[0].visibility, wanted, "{asked:?}");
564            assert_eq!(out.image().objects[0].visibility, wanted, "{asked:?} through the image");
565        }
566    }
567
568    #[test]
569    fn a_number_in_an_image_is_the_bytes_the_machine_reads_it_as() {
570        let mut names = Interner::new();
571        let mut module = module(&mut names);
572        let value = module.add_imm(Imm::int(258, Type::int(32)));
573        defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(32), value }]);
574        let vars =
575            globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
576        assert_eq!(vars[0].pieces, [Piece::Scalar(vec![2, 1, 0, 0])]);
577        // The low byte first, which is what this machine reads and is a fact about the module
578        // rather than about the variable.
579        assert_eq!(vars[0].pieces[0].size(), 4);
580    }
581
582    #[test]
583    fn what_a_variable_is_decides_which_section_it_goes_in() {
584        let mut names = Interner::new();
585        let mut module = module(&mut names);
586        let value = module.add_imm(Imm::int(1, Type::int(32)));
587        let scalar = Datum::Scalar { ty: Type::int(32), value };
588
589        let zeroed = defined(&mut module, &mut names, "zeroed", &[Datum::Zero(4)]);
590        let written = defined(&mut module, &mut names, "written", &[scalar]);
591        let read_only = defined(&mut module, &mut names, "read_only", &[scalar]);
592        module[read_only].constant = true;
593        let named = defined(&mut module, &mut names, "named", &[scalar]);
594        module[named].section = Some(names.intern(".init_array"));
595        let merged = defined(&mut module, &mut names, "merged", &[Datum::Zero(4)]);
596        module[merged].linkage = Linkage::Common;
597
598        let vars =
599            globals(&module, &names, ObjectFormat::Elf).expect("a module of five globals").vars;
600        let places: Vec<&Place> = vars.iter().map(|var| &var.place).collect();
601        assert_eq!(
602            places,
603            [
604                &Place::Zero,
605                &Place::Written,
606                &Place::ReadOnly,
607                &Place::Named(".init_array".to_owned()),
608                &Place::Merged,
609            ]
610        );
611        let _ = (zeroed, written);
612    }
613
614    /// A constant holding an address goes where the loader may write it once, not in `.rodata`.
615    ///
616    /// Three of them, because the question has three answers. One whose address is of something
617    /// this file defines and keeps to itself is local, one whose address is of a name this file
618    /// only declares is not, and one that mixes the two is not either, since it takes only one
619    /// name the link resolves from elsewhere to spoil it. The fourth is the constant with no
620    /// address in it at all, which is the case that has to keep going where it went before.
621    #[test]
622    fn a_constant_holding_an_address_goes_where_the_loader_may_write_it_once() {
623        let mut names = Interner::new();
624        let mut module = module(&mut names);
625        let value = module.add_imm(Imm::int(1, Type::int(32)));
626
627        let mine = defined(&mut module, &mut names, "mine", &[Datum::Zero(4)]);
628        module[mine].linkage = Linkage::Internal;
629        let theirs = module.add_global(Global::new(names.intern("theirs"), 4, 4));
630
631        let to_mine = module.add_reloc(IrReloc { symbol: module[mine].name, addend: 0, size: 8 });
632        let to_theirs =
633            module.add_reloc(IrReloc { symbol: module[theirs].name, addend: 0, size: 8 });
634
635        let plain = defined(
636            &mut module,
637            &mut names,
638            "plain",
639            &[Datum::Scalar { ty: Type::int(32), value }],
640        );
641        module[plain].constant = true;
642        let local = defined(&mut module, &mut names, "local", &[Datum::Addr(to_mine)]);
643        module[local].constant = true;
644        module[local].size = 8;
645        let far = defined(&mut module, &mut names, "far", &[Datum::Addr(to_theirs)]);
646        module[far].constant = true;
647        module[far].size = 8;
648        let both = defined(
649            &mut module,
650            &mut names,
651            "both",
652            &[Datum::Addr(to_mine), Datum::Addr(to_theirs)],
653        );
654        module[both].constant = true;
655        module[both].size = 16;
656
657        let vars =
658            globals(&module, &names, ObjectFormat::Elf).expect("a module of five globals").vars;
659        let places: Vec<(&str, &Place)> =
660            vars.iter().map(|var| (var.name.as_str(), &var.place)).collect();
661        assert_eq!(
662            places,
663            [
664                ("mine", &Place::Zero),
665                ("plain", &Place::ReadOnly),
666                ("local", &Place::RelocReadOnly { local: true }),
667                ("far", &Place::RelocReadOnly { local: false }),
668                ("both", &Place::RelocReadOnly { local: false }),
669            ]
670        );
671    }
672
673    #[test]
674    fn the_rest_of_an_image_the_front_end_left_off_is_zeros() {
675        let mut names = Interner::new();
676        let mut module = module(&mut names);
677        let value = module.add_imm(Imm::int(7, Type::int(8)));
678        let id =
679            defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(8), value }]);
680        module[id].size = 4;
681        let vars =
682            globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
683        assert_eq!(vars[0].pieces, [Piece::Scalar(vec![7]), Piece::Zero(3)]);
684        assert_eq!(vars[0].size, 4);
685    }
686
687    #[test]
688    fn a_variable_holding_an_address_is_a_hole_and_a_name_for_the_linker() {
689        let mut names = Interner::new();
690        let mut module = module(&mut names);
691        let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 16, size: 8 });
692        let id = defined(&mut module, &mut names, "p", &[Datum::Addr(reloc)]);
693        module[id].size = 8;
694        let vars =
695            globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
696        assert_eq!(vars[0].pieces, [Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 }]);
697
698        let data = Globals { vars, ..Globals::default() }.image();
699        assert_eq!(data.objects[0].bytes, vec![0; 8]);
700        assert_eq!(
701            data.objects[0].relocs,
702            [Reloc {
703                at: 0,
704                symbol: "y".to_owned(),
705                kind: Reference::Address { bytes: 8 },
706                addend: 16,
707                after: 0,
708            }]
709        );
710    }
711
712    #[test]
713    fn a_variable_holding_a_distance_is_a_hole_the_linker_measures_from_where_it_is() {
714        let mut names = Interner::new();
715        let mut module = module(&mut names);
716        let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 1, size: 4 });
717        let id = defined(&mut module, &mut names, "d", &[Datum::Away(reloc)]);
718        module[id].size = 4;
719        // Read only rather than relocated at load time, which is the point of writing a table of
720        // distances: what is in the four bytes is the same number wherever the file is loaded.
721        module[id].constant = true;
722        let vars =
723            globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
724        assert_eq!(vars[0].pieces, [Piece::Away { symbol: "y".to_owned(), addend: 1 }]);
725        assert_eq!(vars[0].place, Place::ReadOnly);
726
727        let data = Globals { vars, ..Globals::default() }.image();
728        assert_eq!(data.objects[0].bytes, vec![0; 4]);
729        assert_eq!(
730            data.objects[0].relocs,
731            [Reloc { at: 0, symbol: "y".to_owned(), kind: Reference::Away, addend: 1, after: 0 }]
732        );
733    }
734
735    #[test]
736    fn a_distance_of_a_width_no_relocation_writes_is_refused_by_the_width_it_asked_for() {
737        let mut names = Interner::new();
738        let mut module = module(&mut names);
739        let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 0, size: 8 });
740        let id = defined(&mut module, &mut names, "d", &[Datum::Away(reloc)]);
741        module[id].size = 8;
742        let failed = globals(&module, &names, ObjectFormat::Elf).expect_err("a distance that wide");
743        assert_eq!(
744            failed,
745            Error::Image { name: "d".to_owned(), why: "a distance 8 bytes wide".to_owned() }
746        );
747    }
748
749    #[test]
750    fn a_variable_in_a_section_that_carries_no_image_carries_its_size_and_nothing_else() {
751        let mut names = Interner::new();
752        let mut module = module(&mut names);
753        let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4096)]);
754        module[id].size = 4096;
755        let data =
756            globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").image();
757        assert_eq!(data.objects[0].place, Place::Zero);
758        assert_eq!(data.objects[0].size, 4096);
759        // The point of the section: a program with a large zeroed array is a small file.
760        assert!(data.objects[0].bytes.is_empty());
761    }
762
763    #[test]
764    fn the_linkage_a_variable_had_decides_how_the_linker_sees_the_name() {
765        let mut names = Interner::new();
766        let mut module = module(&mut names);
767        for (index, (linkage, binding)) in [
768            (Linkage::External, Binding::Global),
769            (Linkage::Internal, Binding::Local),
770            (Linkage::Weak, Binding::Weak),
771            (Linkage::LinkOnce, Binding::Weak),
772        ]
773        .into_iter()
774        .enumerate()
775        {
776            let name = format!("x{index}");
777            let id = defined(&mut module, &mut names, &name, &[Datum::Zero(4)]);
778            module[id].linkage = linkage;
779            let vars =
780                globals(&module, &names, ObjectFormat::Elf).expect("a module of globals").vars;
781            assert_eq!(vars[index].binding, binding, "{linkage:?}");
782        }
783    }
784
785    #[test]
786    fn the_linkage_an_alias_had_decides_how_the_linker_sees_the_second_name() {
787        let mut names = Interner::new();
788        let mut module = module(&mut names);
789        let target = names.intern("a");
790        for (index, (linkage, binding)) in [
791            (Linkage::External, Binding::Global),
792            (Linkage::Internal, Binding::Local),
793            (Linkage::Weak, Binding::Weak),
794        ]
795        .into_iter()
796        .enumerate()
797        {
798            let mut alias = IrAlias::new(names.intern(&format!("b{index}")), target);
799            alias.linkage = linkage;
800            module.add_alias(alias);
801            let written = aliases(&module, &names).expect("a module of aliases");
802            assert_eq!(written[index].binding, binding, "{linkage:?}");
803            assert_eq!(written[index].target, "a", "{linkage:?}");
804        }
805    }
806
807    /// A different job from a second name for something, and the wrong answer would be an alias
808    /// pointing at the resolver rather than at what the resolver picks.
809    #[test]
810    fn an_ifunc_is_refused_rather_than_written_as_an_ordinary_second_name() {
811        let mut names = Interner::new();
812        let mut module = module(&mut names);
813        let mut memcpy = IrAlias::new(names.intern("memcpy"), names.intern("pick_memcpy"));
814        memcpy.kind = AliasKind::IFunc;
815        module.add_alias(memcpy);
816        let error = aliases(&module, &names).expect_err("an ifunc");
817        assert_eq!(error, Error::IFunc { name: "memcpy".to_owned() });
818    }
819
820    /// The two sections a thread gets a copy of, told apart the way `.data` and `.bss` are.
821    #[test]
822    fn a_thread_local_variable_goes_in_the_section_a_thread_gets_a_copy_of() {
823        let mut names = Interner::new();
824        let mut module = module(&mut names);
825        let value = module.add_imm(Imm::int(1, Type::int(32)));
826        let written = defined(
827            &mut module,
828            &mut names,
829            "counted",
830            &[Datum::Scalar { ty: Type::int(32), value }],
831        );
832        module[written].tls = Some(TlsModel::GlobalDynamic);
833        let zeroed = defined(&mut module, &mut names, "empty", &[Datum::Zero(4)]);
834        module[zeroed].tls = Some(TlsModel::GlobalDynamic);
835
836        let vars = globals(&module, &names, ObjectFormat::Elf).expect("two thread-locals").vars;
837        assert_eq!(vars[0].place, Place::Thread { zero: false }, ".tdata");
838        assert_eq!(vars[1].place, Place::Thread { zero: true }, ".tbss");
839    }
840
841    /// Being read only loses to being thread-local, because the copy is made by writing it.
842    #[test]
843    fn a_constant_thread_local_is_still_in_the_section_a_thread_gets_a_copy_of() {
844        let mut names = Interner::new();
845        let mut module = module(&mut names);
846        let value = module.add_imm(Imm::int(1, Type::int(32)));
847        let id =
848            defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(32), value }]);
849        module[id].tls = Some(TlsModel::GlobalDynamic);
850        module[id].constant = true;
851
852        let vars = globals(&module, &names, ObjectFormat::Elf).expect("a thread-local").vars;
853        assert_eq!(vars[0].place, Place::Thread { zero: false });
854    }
855
856    /// The image of a thread-local whose image is all zeros costs the file nothing, the same as
857    /// `.bss` does, and the one that is not all zeros carries its bytes.
858    #[test]
859    fn the_image_of_a_thread_local_is_carried_only_when_it_is_not_all_zeros() {
860        let mut names = Interner::new();
861        let mut module = module(&mut names);
862        let value = module.add_imm(Imm::int(258, Type::int(32)));
863        let written = defined(
864            &mut module,
865            &mut names,
866            "counted",
867            &[Datum::Scalar { ty: Type::int(32), value }],
868        );
869        module[written].tls = Some(TlsModel::GlobalDynamic);
870        let zeroed = defined(&mut module, &mut names, "empty", &[Datum::Zero(4)]);
871        module[zeroed].tls = Some(TlsModel::GlobalDynamic);
872
873        let data = globals(&module, &names, ObjectFormat::Elf).expect("two thread-locals").image();
874        assert_eq!(data.objects[0].bytes, [2, 1, 0, 0]);
875        assert_eq!(data.objects[0].size, 4);
876        assert!(data.objects[1].bytes.is_empty(), "a zeroed one carries its size and no bytes");
877        assert_eq!(data.objects[1].size, 4);
878    }
879
880    /// WebAssembly has no thread-local storage this writes, so the variable is refused by name.
881    #[test]
882    fn a_thread_local_variable_is_refused_on_a_format_that_does_not_spell_one_this_way() {
883        let mut names = Interner::new();
884        let mut module = module(&mut names);
885        let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
886        module[id].tls = Some(TlsModel::GlobalDynamic);
887        let format = ObjectFormat::Wasm;
888        let error = globals(&module, &names, format).expect_err("a thread-local variable");
889        assert_eq!(error, Error::Thread { name: "x".to_owned(), format: format.as_str() });
890    }
891
892    /// Windows copies one `.tls` section per thread and has no zeroed half, so a zeroed one is
893    /// written out as zeros.
894    #[test]
895    fn a_zeroed_thread_local_on_windows_is_written_out() {
896        let mut names = Interner::new();
897        let mut module = module(&mut names);
898        let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
899        module[id].tls = Some(TlsModel::GlobalDynamic);
900        let vars = globals(&module, &names, ObjectFormat::Coff).expect("a thread-local variable");
901        let data = vars.image();
902        assert_eq!(data.objects[0].place, Place::Thread { zero: false });
903        assert_eq!(data.objects[0].bytes, [0, 0, 0, 0]);
904    }
905}