rucc-target 0.3.5

Target descriptions, register files and ABI data for the rucc C compiler.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
//! The register file: what registers a target has, and what classes they fall into.
//!
//! Design: `spec/10-backend.md` section 10.8.
//!
//! A register file is data rather than code, which is the same claim the rest of this crate
//! makes and the one `M10` puts a number on. A class is a set of registers that an operand of
//! that class may be assigned to, and a physical register is its number inside its class, so
//! the allocator works in dense small integers and only the printer and the parser ever deal in
//! names.
//!
//! The file lives here rather than in `rucc-mir` because more than one thing reads it. The
//! machine IR needs it to print, the allocator needs the set it may assign from, and the ABI
//! description needs to name the registers arguments arrive in. All three are above this crate,
//! and the alternative is the register file living in whichever of them happens to be lowest,
//! which is how a layering ends up describing itself as historical.
//!
//! Names are unique across the whole file, not merely inside a class. That is what lets a
//! register be written `$rax` in a dump rather than `$gpr.0`, and it is a real constraint on a
//! target that gives one register two classes: it has to say which class it is in, or use two
//! names. [`RegFile::duplicate`] is what a target's own test asks to find out.

use std::fmt;

/// One class of registers, and the registers in it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClassInfo {
    /// What the class is called in a dump, such as `gpr`.
    pub name: &'static str,
    /// How wide one of its registers is, in bits.
    pub bits: u32,
    /// The registers, in the order their numbers run, without the sigil a dump writes.
    pub regs: &'static [&'static str],
}

/// Which class a register or an operand belongs to.
///
/// A number into the file's classes rather than a name, because it is on every operand of every
/// instruction and it is compared far more often than it is printed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RegClass(u8);

impl RegClass {
    /// The class with that number.
    #[must_use]
    pub const fn new(number: u8) -> Self {
        Self(number)
    }

    /// Its number, which is what indexes the file.
    #[must_use]
    pub const fn number(self) -> u8 {
        self.0
    }
}

/// One physical register, as its number inside its class.
///
/// The class is not in here. An operand carries its class already, and a fixed-register
/// constraint is a constraint on an operand, so repeating the class would be a second copy of
/// something that can disagree with the first.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PhysReg(u8);

impl PhysReg {
    /// The register with that number in its class.
    #[must_use]
    pub const fn new(number: u8) -> Self {
        Self(number)
    }

    /// Its number inside its class.
    #[must_use]
    pub const fn number(self) -> u8 {
        self.0
    }
}

/// Every register a target has.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegFile {
    classes: &'static [ClassInfo],
}

impl RegFile {
    /// The file of a target whose registers nothing has described yet.
    ///
    /// A target reaches 1.0 with a real one. Until it has one, the honest answer to what
    /// registers it has is that nobody has written them down, and that is a file with no
    /// classes in it rather than a panic or a plausible guess.
    pub const EMPTY: Self = Self::new(&[]);

    /// A file made of those classes, numbered in the order they are given.
    #[must_use]
    pub const fn new(classes: &'static [ClassInfo]) -> Self {
        Self { classes }
    }

    /// Its classes, each with the number it is known by.
    pub fn classes(&self) -> impl Iterator<Item = (RegClass, &'static ClassInfo)> + use<> {
        self.classes.iter().enumerate().map(|(number, info)| (RegClass::new(number as u8), info))
    }

    /// What is in one class.
    #[must_use]
    pub fn class(&self, class: RegClass) -> Option<&'static ClassInfo> {
        self.classes.get(usize::from(class.number()))
    }

    /// The class of that name, such as `gpr`.
    #[must_use]
    pub fn class_named(&self, name: &str) -> Option<RegClass> {
        self.classes().find(|(_, info)| info.name == name).map(|(class, _)| class)
    }

    /// How many registers are in a class, which is one past the largest number in it.
    #[must_use]
    pub fn len(&self, class: RegClass) -> usize {
        self.class(class).map_or(0, |info| info.regs.len())
    }

    /// Whether the file has no classes at all, which is a target that has not described one.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.classes.is_empty()
    }

    /// What one register is called.
    #[must_use]
    pub fn name(&self, class: RegClass, reg: PhysReg) -> Option<&'static str> {
        self.class(class)?.regs.get(usize::from(reg.number())).copied()
    }

    /// The register of that name, and the class it is in.
    ///
    /// The name is written without the sigil, so `rax` rather than `$rax`.
    #[must_use]
    pub fn reg_named(&self, name: &str) -> Option<(RegClass, PhysReg)> {
        for (class, info) in self.classes() {
            if let Some(number) = info.regs.iter().position(|&reg| reg == name) {
                return Some((class, PhysReg::new(number as u8)));
            }
        }
        None
    }

    /// A name this file gives to two registers, if it gives one to two.
    ///
    /// Reading a dump back needs every name to say which register it means, and a target that
    /// breaks that produces text that cannot be parsed rather than an error at the point of the
    /// mistake. So every target's own test asks this, which is why it is here and public.
    #[must_use]
    pub fn duplicate(&self) -> Option<&'static str> {
        let mut seen: Vec<&'static str> = Vec::new();
        for (_, info) in self.classes() {
            for &reg in info.regs {
                if seen.contains(&reg) {
                    return Some(reg);
                }
                seen.push(reg);
            }
        }
        None
    }
}

/// Which registers a calling convention gives which job.
///
/// This is the second half of a target description and it is separate from [`RegFile`] because
/// the two do not vary together. x86-64 has one register file and two conventions over it, and
/// they disagree about nearly everything below: `rdi` is where the first argument arrives on
/// SysV and a register a callee has to preserve on Windows, and a Windows caller reserves
/// thirty two bytes below the call that a SysV caller does not.
///
/// The allocation order is here rather than on a class because it is a consequence of what a
/// call clobbers. A value that does not live across a call belongs in a register the callee is
/// free to destroy, because putting it in a preserved one costs a push and a pop in the
/// prologue of whichever function ends up owning it.
///
/// Every register named here is a register of the file the same target describes, and each list
/// is in the order the convention uses them, so the fourth integer argument is `int_args[3]` and
/// nothing has to count.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CallRegs {
    /// The class the general purpose registers named here are in.
    ///
    /// A register is a number inside its class, so a list of them says nothing about which
    /// registers they are without this. Everything else could get the class from the operand it
    /// came off, and a frame cannot, because a saved register is not an operand of anything.
    pub int_class: RegClass,
    /// The class the vector registers named here are in.
    pub sse_class: RegClass,
    /// The general purpose registers integer arguments arrive in, in order.
    pub int_args: &'static [PhysReg],
    /// The vector registers floating point arguments arrive in, in order.
    ///
    /// Whether an argument's position counts against both lists or only against its own is
    /// [`CallRegs::shared_positions`].
    pub sse_args: &'static [PhysReg],
    /// Whether an argument's position counts against both argument lists or only against its own.
    ///
    /// False on SysV, which counts each separately, so a `double` after six integers is still in
    /// `xmm0`. True on Windows, which counts one position for both, so a `double` in the third
    /// position is in `xmm2` and `r8` is skipped.
    pub shared_positions: bool,
    /// The general purpose registers an integer return value comes back in.
    pub int_returns: &'static [PhysReg],
    /// The vector registers a floating point return value comes back in.
    pub sse_returns: &'static [PhysReg],
    /// The x87 registers a `long double` comes back in, which is empty on a target whose
    /// `long double` is a `double`.
    pub x87_returns: &'static [PhysReg],
    /// The general purpose registers a call leaves alone, so a value in one survives it.
    pub int_saved: &'static [PhysReg],
    /// The vector registers a call leaves alone, which is none of them on SysV.
    pub sse_saved: &'static [PhysReg],
    /// The general purpose registers the allocator may hand out, in the order it prefers them.
    ///
    /// The stack pointer is never in this list, and neither is the frame pointer, which a
    /// target could allocate when nothing needs a frame and which nothing here does yet.
    pub int_order: &'static [PhysReg],
    /// The vector registers the allocator may hand out, in the order it prefers them.
    pub sse_order: &'static [PhysReg],
    /// The stack pointer.
    pub stack_pointer: PhysReg,
    /// The frame pointer, which is the register a prologue puts the old stack pointer in.
    pub frame_pointer: PhysReg,
    /// Where a variadic call says how many vector registers it passed arguments in, when the
    /// convention makes it say.
    ///
    /// SysV puts the count in `al` and a variadic callee reads it to decide whether to save the
    /// vector argument registers at all, which is what makes a call to `printf` with no
    /// floating point argument cheap.
    pub vector_count: Option<PhysReg>,
    /// How many bytes below the stack pointer a leaf function may use without moving it.
    ///
    /// A hundred and twenty eight on SysV and nothing on Windows. It is nothing in kernel code
    /// on either, because an interrupt handler runs on the interrupted stack and writes over
    /// exactly this, which is what `-mno-red-zone` is for.
    pub red_zone: u32,
    /// How many bytes a caller reserves below the call for the callee to spill its register
    /// arguments into, which is thirty two on Windows and nothing on SysV.
    pub shadow: u32,
    /// What the stack pointer has to be a multiple of at the instruction that makes a call.
    ///
    /// Sixteen on every convention here, and it is a real obligation rather than a preference,
    /// because a callee is entitled to use an aligned vector store on its own frame and gets a
    /// fault rather than a wrong answer when a caller got this wrong.
    pub stack_align: u32,
    /// How many bytes the call instruction itself pushes before the callee starts running.
    ///
    /// Eight on x86-64, where the return address is on the stack, and nothing on a machine that
    /// leaves it in a register. It is what makes the stack pointer misaligned on entry by
    /// exactly one word, which every frame layout has to undo.
    pub return_address: u32,
    /// How many bytes one general purpose register takes when it is saved on the stack.
    pub word: u32,
}

impl CallRegs {
    /// Whether a call preserves that general purpose register.
    #[must_use]
    pub fn preserves_int(&self, reg: PhysReg) -> bool {
        self.int_saved.contains(&reg)
    }

    /// Whether a call preserves that vector register.
    #[must_use]
    pub fn preserves_sse(&self, reg: PhysReg) -> bool {
        self.sse_saved.contains(&reg)
    }
}

/// Where one of the values a call passes is.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Where {
    /// In that register.
    Reg(PhysReg),
    /// That many bytes up the argument area, which is where the stack pointer points at the
    /// instruction that makes the call and is one word above the return address in the callee.
    Stack(u32),
}

/// Where the values a call passes are, worked out one after another.
///
/// [`crate::abi::Call`] answers a different question: whether a value travels in registers at all
/// and in how many, which is what decides the shape of a signature and is settled before the IR
/// for a function exists. This answers the question after it. Given values in the order the
/// signature holds them, it says which register each one is in and how far up the argument area
/// the ones that got no register are. Both count registers, and they agree about how many fit
/// because they read the same lists, but they run at opposite ends of the compiler and neither
/// can be the other.
///
/// Ask about each value in the order the signature holds them. Asking out of order answers about
/// a different signature, because where a value is depends on every value before it.
#[derive(Debug, Clone)]
pub struct Places<'a> {
    regs: &'a CallRegs,
    int: usize,
    sse: usize,
    stack: u32,
}

impl<'a> Places<'a> {
    /// Where the first value is, for a call under that convention.
    #[must_use]
    pub fn new(regs: &'a CallRegs) -> Self {
        Self { regs, int: 0, sse: 0, stack: regs.shadow }
    }

    /// Where the next value is, when it travels in a general purpose register.
    pub fn integer(&mut self) -> Where {
        match self.regs.int_args.get(self.position(false)) {
            Some(&reg) => {
                self.int += 1;
                Where::Reg(reg)
            }
            None => self.on_stack(self.regs.word, self.regs.word),
        }
    }

    /// Where the next value is, when it travels in a vector register.
    pub fn float(&mut self) -> Where {
        match self.regs.sse_args.get(self.position(true)) {
            Some(&reg) => {
                self.sse += 1;
                Where::Reg(reg)
            }
            None => self.on_stack(self.regs.word, self.regs.word),
        }
    }

    /// Where the next value is, when it travels in memory whatever is left.
    ///
    /// Every argument area is a run of whole words, so a value narrower than one still takes one
    /// and a value that is not a whole number of them is rounded up. An alignment wider than a
    /// word is respected, which is what a sixteen byte aligned structure passed by value needs.
    pub fn on_stack(&mut self, size: u32, align: u32) -> Where {
        let word = self.regs.word;
        let at = self.stack.next_multiple_of(align.max(word));
        self.stack = at.saturating_add(size.max(word).next_multiple_of(word));
        Where::Stack(at)
    }

    /// How many bytes of argument area the values so far need, shadow space included.
    #[must_use]
    pub fn size(&self) -> u32 {
        self.stack
    }

    /// The position the next value of a kind is at.
    fn position(&self, sse: bool) -> usize {
        if self.regs.shared_positions {
            self.int + self.sse
        } else if sse {
            self.sse
        } else {
            self.int
        }
    }
}

impl fmt::Display for RegFile {
    /// The file as a dump reads it, one class to a line.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (_, info) in self.classes() {
            writeln!(f, "class {} : i{} = {}", info.name, info.bits, info.regs.join(", "))?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    static GPR: [&str; 3] = ["rax", "rcx", "rdx"];
    static XMM: [&str; 2] = ["xmm0", "xmm1"];
    static CLASSES: [ClassInfo; 2] = [
        ClassInfo { name: "gpr", bits: 64, regs: &GPR },
        ClassInfo { name: "xmm", bits: 128, regs: &XMM },
    ];
    static FILE: RegFile = RegFile::new(&CLASSES);

    #[test]
    fn a_class_is_found_by_its_name() {
        let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
        assert_eq!(FILE.len(gpr), 3);
        assert_eq!(FILE.class(gpr).map(|info| info.bits), Some(64));
        assert_eq!(FILE.class_named("vec"), None);
    }

    #[test]
    fn a_register_is_found_by_its_name_and_names_itself_back() {
        let (class, reg) = FILE.reg_named("xmm1").expect("the file has xmm1");
        assert_eq!(FILE.class(class).map(|info| info.name), Some("xmm"));
        assert_eq!(reg.number(), 1);
        assert_eq!(FILE.name(class, reg), Some("xmm1"));
        assert_eq!(FILE.reg_named("r15"), None);
    }

    #[test]
    fn a_number_past_the_end_of_a_class_has_no_name() {
        let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
        assert_eq!(FILE.name(gpr, PhysReg::new(3)), None);
        assert_eq!(FILE.name(RegClass::new(7), PhysReg::new(0)), None);
    }

    #[test]
    fn a_file_that_names_two_registers_alike_says_so() {
        assert_eq!(FILE.duplicate(), None);
        static BOTH: [ClassInfo; 2] = [
            ClassInfo { name: "gpr", bits: 64, regs: &GPR },
            ClassInfo { name: "shadow", bits: 64, regs: &GPR },
        ];
        assert_eq!(RegFile::new(&BOTH).duplicate(), Some("rax"));
    }

    #[test]
    fn the_file_prints_one_class_to_a_line() {
        assert_eq!(
            FILE.to_string(),
            "class gpr : i64 = rax, rcx, rdx\nclass xmm : i128 = xmm0, xmm1\n"
        );
    }

    /// Two integer registers, two vector registers and nothing else, so running out of them takes
    /// three arguments rather than seven and the interesting case is the one being tested.
    fn convention(shared: bool, shadow: u32) -> CallRegs {
        static INT: [PhysReg; 2] = [PhysReg::new(0), PhysReg::new(1)];
        static SSE: [PhysReg; 2] = [PhysReg::new(10), PhysReg::new(11)];
        static NONE: [PhysReg; 0] = [];
        CallRegs {
            int_class: RegClass::new(0),
            sse_class: RegClass::new(1),
            int_args: &INT,
            sse_args: &SSE,
            shared_positions: shared,
            int_returns: &INT,
            sse_returns: &SSE,
            x87_returns: &NONE,
            int_saved: &NONE,
            sse_saved: &NONE,
            int_order: &INT,
            sse_order: &SSE,
            stack_pointer: PhysReg::new(4),
            frame_pointer: PhysReg::new(5),
            vector_count: None,
            red_zone: 0,
            shadow,
            stack_align: 16,
            return_address: 8,
            word: 8,
        }
    }

    #[test]
    fn counting_each_kind_separately_leaves_the_first_vector_register_to_the_first_float() {
        let regs = convention(false, 0);
        let mut places = Places::new(&regs);
        assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
        assert_eq!(places.integer(), Where::Reg(PhysReg::new(1)));
        // Two integers went past, and a convention that counts separately has not spent a vector
        // register on either of them.
        assert_eq!(places.float(), Where::Reg(PhysReg::new(10)));
        assert_eq!(places.size(), 0);
    }

    #[test]
    fn counting_one_position_for_both_skips_the_register_the_other_kind_would_have_used() {
        let regs = convention(true, 0);
        let mut places = Places::new(&regs);
        assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
        // The second position, so the second vector register, and the second integer register is
        // spent whether anything is in it or not.
        assert_eq!(places.float(), Where::Reg(PhysReg::new(11)));
        assert_eq!(places.integer(), Where::Stack(0));
    }

    #[test]
    fn running_out_of_one_kind_of_register_does_not_touch_the_other() {
        let regs = convention(false, 0);
        let mut places = Places::new(&regs);
        assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
        assert_eq!(places.integer(), Where::Reg(PhysReg::new(1)));
        assert_eq!(places.integer(), Where::Stack(0));
        assert_eq!(places.float(), Where::Reg(PhysReg::new(10)));
        assert_eq!(places.size(), 8);
    }

    #[test]
    fn the_argument_area_starts_above_the_shadow_space_and_keeps_every_value_aligned() {
        let regs = convention(false, 32);
        let mut places = Places::new(&regs);
        // A Windows caller reserves this whether it passes anything on the stack or not, which is
        // why an empty area is thirty two bytes rather than none.
        assert_eq!(places.size(), 32);
        assert_eq!(places.on_stack(4, 4), Where::Stack(32));
        // Sixteen byte alignment skips the word at 40, which is what a vector or an over-aligned
        // structure passed by value asks for. The four byte value before it still took a whole
        // word, which is why the skipped word is there to skip.
        assert_eq!(places.on_stack(16, 16), Where::Stack(48));
        assert_eq!(places.on_stack(8, 8), Where::Stack(64));
        assert_eq!(places.size(), 72);
    }
}