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//! # What is refused
23//!
24//! A thread-local variable. Reaching one is a call to `__tls_get_addr` or a load from the thread
25//! pointer depending on the model, none of which the back end builds yet, so a file with one in it
26//! is refused by name rather than written out as an ordinary variable that every thread would
27//! share.
28
29use rucc_base::Interner;
30use rucc_ir::{Datum, GlobalId, Linkage, Module};
31use rucc_object::{Binding, Data, Object, Place, Reference, Reloc};
32
33use crate::Error;
34
35/// Every variable a module defines, laid out.
36#[derive(Debug, Clone, Default, PartialEq, Eq)]
37pub struct Globals {
38    /// One entry per definition, in the order the module held them. A declaration is not here,
39    /// because a file says nothing about a variable another file defines beyond the references
40    /// that name it, and those are already in the text.
41    pub vars: Vec<Variable>,
42}
43
44/// One global variable, as the pieces of its image and what the linker is told about it.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct Variable {
47    /// Its name, as the C program spelled it. The underscore an Apple symbol carries is added
48    /// when it is written down, because it is a fact about the object format and not about the
49    /// variable.
50    pub name: String,
51    /// How many bytes it occupies, which the pieces add up to.
52    pub size: u64,
53    /// What it has to be aligned to, always a power of two.
54    pub align: u64,
55    /// Which section it goes in.
56    pub place: Place,
57    /// How the linker sees the name.
58    pub binding: Binding,
59    /// Its image, in order.
60    pub pieces: Vec<Piece>,
61}
62
63/// As much of an image as one directive says.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum Piece {
66    /// That many zero bytes, which is the tail of a partly initialized array and the whole of a
67    /// variable with no initializer.
68    Zero(u64),
69    /// Those literal bytes, which is what a string literal and anything already laid out is.
70    Bytes(Vec<u8>),
71    /// One number, in the byte order the module was built for, as many bytes wide as its type.
72    Scalar(Vec<u8>),
73    /// The address of a symbol, which is a hole this compiler leaves and the linker fills.
74    Addr {
75        /// Whose address it is, as the C program spelled it.
76        symbol: String,
77        /// What to add to that address. `&array[2]` is the address of `array` plus eight.
78        addend: i64,
79        /// How many bytes it occupies.
80        bytes: u8,
81    },
82}
83
84impl Piece {
85    /// How many bytes it contributes to the image.
86    #[must_use]
87    pub fn size(&self) -> u64 {
88        match self {
89            Piece::Zero(bytes) => *bytes,
90            Piece::Bytes(bytes) | Piece::Scalar(bytes) => bytes.len() as u64,
91            Piece::Addr { bytes, .. } => u64::from(*bytes),
92        }
93    }
94}
95
96impl Globals {
97    /// The image of every variable, and where in each one the linker has to write an address.
98    ///
99    /// A variable in a section that carries no image contributes its size and none of its bytes,
100    /// which is what makes a program with a large zeroed array a small file.
101    #[must_use]
102    pub fn image(&self) -> Data {
103        let mut data = Data::default();
104        for var in &self.vars {
105            let mut object = Object {
106                name: var.name.clone(),
107                bytes: Vec::new(),
108                size: var.size,
109                align: var.align,
110                place: var.place.clone(),
111                binding: var.binding,
112                relocs: Vec::new(),
113            };
114            if matches!(var.place, Place::Zero | Place::Merged) {
115                data.objects.push(object);
116                continue;
117            }
118            for piece in &var.pieces {
119                match piece {
120                    Piece::Zero(bytes) => {
121                        object.bytes.resize(object.bytes.len() + *bytes as usize, 0);
122                    }
123                    Piece::Bytes(bytes) | Piece::Scalar(bytes) => {
124                        object.bytes.extend_from_slice(bytes);
125                    }
126                    Piece::Addr { symbol, addend, bytes } => {
127                        // The bytes are left zero rather than holding anything, because a linker
128                        // writes the whole hole from the addend and never reads what was there.
129                        object.relocs.push(Reloc {
130                            at: object.bytes.len(),
131                            symbol: symbol.clone(),
132                            kind: Reference::Address { bytes: *bytes },
133                            addend: *addend,
134                        });
135                        object.bytes.resize(object.bytes.len() + usize::from(*bytes), 0);
136                    }
137                }
138            }
139            data.objects.push(object);
140        }
141        data
142    }
143}
144
145/// Every variable a module defines, laid out.
146///
147/// # Errors
148///
149/// [`Error::Thread`] for a thread-local variable, which is a program this compiler is behind on
150/// rather than a mistake, and [`Error::Image`] for a piece of an initializer nothing here can
151/// write down. See [`Error`].
152pub fn globals(module: &Module, names: &Interner) -> Result<Globals, Error> {
153    let mut out = Globals::default();
154    for id in module.globals() {
155        if module[id].is_declaration() {
156            continue;
157        }
158        out.vars.push(variable(module, names, id)?);
159    }
160    Ok(out)
161}
162
163/// One variable, laid out.
164fn variable(module: &Module, names: &Interner, id: GlobalId) -> Result<Variable, Error> {
165    let global = &module[id];
166    let name = names.resolve(global.name).to_owned();
167    if global.tls.is_some() {
168        return Err(Error::Thread { name });
169    }
170    let init = global.init.expect("a definition has an image");
171
172    let mut pieces = Vec::new();
173    let mut written = 0;
174    for datum in &module[init] {
175        let piece = match *datum {
176            Datum::Zero(bytes) => Piece::Zero(bytes),
177            Datum::Bytes(range) => Piece::Bytes(module[range].to_vec()),
178            Datum::Scalar { ty, value } => {
179                if ty.lanes() != 1 {
180                    let why = format!("a {ty} in an initializer");
181                    return Err(Error::Image { name, why });
182                }
183                let bytes = usize::try_from(ty.bits().div_ceil(8)).expect("a scalar this wide");
184                let mut image = module[value].bits().to_le_bytes()[..bytes].to_vec();
185                if !module.datalayout.little_endian {
186                    image.reverse();
187                }
188                Piece::Scalar(image)
189            }
190            Datum::Addr(idx) => {
191                let reloc = module[idx];
192                // Four and eight are the widths a machine has a relocation for and a directive
193                // for. Anything else is a module nothing here produced and neither half of the
194                // description could write down, so it is refused rather than rounded to one.
195                let bytes = match reloc.size {
196                    4 | 8 => reloc.size as u8,
197                    size => {
198                        let why = format!("an address {size} bytes wide");
199                        return Err(Error::Image { name, why });
200                    }
201                };
202                let symbol = names.resolve(reloc.symbol).to_owned();
203                Piece::Addr { symbol, addend: reloc.addend, bytes }
204            }
205        };
206        written += piece.size();
207        pieces.push(piece);
208    }
209    // An image shorter than the variable is the rest of an array nothing initialized, which the
210    // front end may leave off the end rather than write out as zeros it already said were there.
211    if written < global.size {
212        pieces.push(Piece::Zero(global.size - written));
213    }
214
215    let place = place(module, names, id, &pieces);
216    let binding = match global.linkage {
217        Linkage::Internal => Binding::Local,
218        Linkage::Weak | Linkage::LinkOnce => Binding::Weak,
219        Linkage::External | Linkage::Common => Binding::Global,
220    };
221    let size = global.size.max(written);
222    Ok(Variable { name, size, align: u64::from(global.align), place, binding, pieces })
223}
224
225/// Which section a variable goes in.
226///
227/// The program's answer when it gave one, and otherwise worked out from what the variable is. A
228/// tentative definition is asked of the linker rather than put anywhere, since the whole of what
229/// it says is that the variable exists and that some other file may say so too.
230fn place(module: &Module, names: &Interner, id: GlobalId, pieces: &[Piece]) -> Place {
231    let global = &module[id];
232    if let Some(section) = global.section {
233        return Place::Named(names.resolve(section).to_owned());
234    }
235    if global.linkage == Linkage::Common {
236        return Place::Merged;
237    }
238    if pieces.iter().all(|piece| matches!(piece, Piece::Zero(_))) {
239        return Place::Zero;
240    }
241    if global.constant {
242        return Place::ReadOnly;
243    }
244    Place::Written
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    use rucc_ir::{Global, Imm, Reloc as IrReloc, TlsModel, Type};
252    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
253
254    /// A module for the one target every case here is written for.
255    fn module(names: &mut Interner) -> Module {
256        let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
257        Module::new(names.intern("t.c"), &target)
258    }
259
260    /// A four byte variable with that image.
261    fn defined(module: &mut Module, names: &mut Interner, name: &str, data: &[Datum]) -> GlobalId {
262        let list = module.push_data(data);
263        let mut global = Global::new(names.intern(name), 4, 4);
264        global.init = Some(list);
265        module.add_global(global)
266    }
267
268    #[test]
269    fn a_declaration_is_not_a_variable_this_file_defines() {
270        let mut names = Interner::new();
271        let mut module = module(&mut names);
272        module.add_global(Global::new(names.intern("x"), 4, 4));
273        defined(&mut module, &mut names, "y", &[Datum::Zero(4)]);
274        let vars = globals(&module, &names).expect("a module of two globals").vars;
275        assert_eq!(vars.iter().map(|var| var.name.as_str()).collect::<Vec<_>>(), ["y"]);
276    }
277
278    #[test]
279    fn a_number_in_an_image_is_the_bytes_the_machine_reads_it_as() {
280        let mut names = Interner::new();
281        let mut module = module(&mut names);
282        let value = module.add_imm(Imm::int(258, Type::int(32)));
283        defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(32), value }]);
284        let vars = globals(&module, &names).expect("a module of one global").vars;
285        assert_eq!(vars[0].pieces, [Piece::Scalar(vec![2, 1, 0, 0])]);
286        // The low byte first, which is what this machine reads and is a fact about the module
287        // rather than about the variable.
288        assert_eq!(vars[0].pieces[0].size(), 4);
289    }
290
291    #[test]
292    fn what_a_variable_is_decides_which_section_it_goes_in() {
293        let mut names = Interner::new();
294        let mut module = module(&mut names);
295        let value = module.add_imm(Imm::int(1, Type::int(32)));
296        let scalar = Datum::Scalar { ty: Type::int(32), value };
297
298        let zeroed = defined(&mut module, &mut names, "zeroed", &[Datum::Zero(4)]);
299        let written = defined(&mut module, &mut names, "written", &[scalar]);
300        let read_only = defined(&mut module, &mut names, "read_only", &[scalar]);
301        module[read_only].constant = true;
302        let named = defined(&mut module, &mut names, "named", &[scalar]);
303        module[named].section = Some(names.intern(".init_array"));
304        let merged = defined(&mut module, &mut names, "merged", &[Datum::Zero(4)]);
305        module[merged].linkage = Linkage::Common;
306
307        let vars = globals(&module, &names).expect("a module of five globals").vars;
308        let places: Vec<&Place> = vars.iter().map(|var| &var.place).collect();
309        assert_eq!(
310            places,
311            [
312                &Place::Zero,
313                &Place::Written,
314                &Place::ReadOnly,
315                &Place::Named(".init_array".to_owned()),
316                &Place::Merged,
317            ]
318        );
319        let _ = (zeroed, written);
320    }
321
322    #[test]
323    fn the_rest_of_an_image_the_front_end_left_off_is_zeros() {
324        let mut names = Interner::new();
325        let mut module = module(&mut names);
326        let value = module.add_imm(Imm::int(7, Type::int(8)));
327        let id =
328            defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(8), value }]);
329        module[id].size = 4;
330        let vars = globals(&module, &names).expect("a module of one global").vars;
331        assert_eq!(vars[0].pieces, [Piece::Scalar(vec![7]), Piece::Zero(3)]);
332        assert_eq!(vars[0].size, 4);
333    }
334
335    #[test]
336    fn a_variable_holding_an_address_is_a_hole_and_a_name_for_the_linker() {
337        let mut names = Interner::new();
338        let mut module = module(&mut names);
339        let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 16, size: 8 });
340        let id = defined(&mut module, &mut names, "p", &[Datum::Addr(reloc)]);
341        module[id].size = 8;
342        let vars = globals(&module, &names).expect("a module of one global").vars;
343        assert_eq!(vars[0].pieces, [Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 }]);
344
345        let data = Globals { vars }.image();
346        assert_eq!(data.objects[0].bytes, vec![0; 8]);
347        assert_eq!(
348            data.objects[0].relocs,
349            [Reloc {
350                at: 0,
351                symbol: "y".to_owned(),
352                kind: Reference::Address { bytes: 8 },
353                addend: 16,
354            }]
355        );
356    }
357
358    #[test]
359    fn a_variable_in_a_section_that_carries_no_image_carries_its_size_and_nothing_else() {
360        let mut names = Interner::new();
361        let mut module = module(&mut names);
362        let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4096)]);
363        module[id].size = 4096;
364        let data = globals(&module, &names).expect("a module of one global").image();
365        assert_eq!(data.objects[0].place, Place::Zero);
366        assert_eq!(data.objects[0].size, 4096);
367        // The point of the section: a program with a large zeroed array is a small file.
368        assert!(data.objects[0].bytes.is_empty());
369    }
370
371    #[test]
372    fn the_linkage_a_variable_had_decides_how_the_linker_sees_the_name() {
373        let mut names = Interner::new();
374        let mut module = module(&mut names);
375        for (index, (linkage, binding)) in [
376            (Linkage::External, Binding::Global),
377            (Linkage::Internal, Binding::Local),
378            (Linkage::Weak, Binding::Weak),
379            (Linkage::LinkOnce, Binding::Weak),
380        ]
381        .into_iter()
382        .enumerate()
383        {
384            let name = format!("x{index}");
385            let id = defined(&mut module, &mut names, &name, &[Datum::Zero(4)]);
386            module[id].linkage = linkage;
387            let vars = globals(&module, &names).expect("a module of globals").vars;
388            assert_eq!(vars[index].binding, binding, "{linkage:?}");
389        }
390    }
391
392    #[test]
393    fn a_thread_local_variable_is_refused_rather_than_shared_between_every_thread() {
394        let mut names = Interner::new();
395        let mut module = module(&mut names);
396        let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
397        module[id].tls = Some(TlsModel::GlobalDynamic);
398        let error = globals(&module, &names).expect_err("a thread-local variable");
399        assert_eq!(error, Error::Thread { name: "x".to_owned() });
400    }
401}