Skip to main content

rucc_mir/
inst.rs

1//! What one machine instruction is, and what an operand is.
2//!
3//! Design: `spec/10-backend.md` section 10.1.
4//!
5//! An instruction is an opcode, a run of operands, and the three things an opcode may carry
6//! besides its operands: an immediate, a memory addressing mode, and a symbol. Twenty-four
7//! bytes, all of it either a small number or an index into a table the function owns, so
8//! walking a function is walking one dense array and nothing in it is separately freed.
9//!
10//! An operand is a register, the class it is drawn from, whether the instruction reads or
11//! writes it, and any constraint on where it may live. That is what the allocator reads and it
12//! is all the allocator reads, which is the point: the opcode is a name to everything except
13//! the encoder, and the allocator never has to know what any particular target's instructions
14//! mean.
15//!
16//! # Where the other pieces are
17//!
18//! [`Role`] and [`Constraint`] are in `rucc-target`, and this crate re-exports them. A target
19//! says what its instructions do to their operands before there is any machine IR to say it in,
20//! and both the selector that builds the IR and the encoder that reads it need the answer, so
21//! the two of them live below both.
22//!
23//! Successors are on the block rather than on the terminator, in the order the terminator's own
24//! arms run. That is regalloc2's arrangement, which `spec/10-backend.md` section 10.4 says the
25//! allocator interface follows, and it keeps a branch's arguments out of the operand vector
26//! where they would otherwise be uses the allocator has to be told to treat differently.
27//!
28//! The source location is a parallel array in the function, reached by [`crate::Func::span`],
29//! for the same reason `rucc-ir` puts it there: it is read when a diagnostic is being made and
30//! at no other time, so it does not belong on the row that every pass walks.
31
32use rucc_base::{Idx, IdxRange, Symbol};
33use rucc_target::{Constraint, PhysReg, RegClass, Role, Segment};
34
35/// One instruction, in the function that owns it.
36pub type Inst = Idx<InstData>;
37/// One basic block, in the function that owns it.
38pub type Block = Idx<BlockData>;
39/// A run of operands, which is what an instruction's operand vector is.
40pub type OperandList = IdxRange<Operand>;
41/// One immediate, in the function's immediate table.
42pub type ImmRef = Idx<Imm>;
43/// One addressing mode, in the function's table of them.
44pub type MemRef = Idx<Amode>;
45
46/// Which instruction this is.
47///
48/// A name rather than a variant of an enum. `spec/10-backend.md` section 10.8 says no pipeline
49/// crate holds target-specific code, and an enum of every x86-64 opcode in the crate every
50/// target's MIR passes through is exactly that. The opcodes a target has are data: they come out
51/// of its rule set, which is what the selector was compiled from, and this crate never asks what
52/// one of them means. The encoder does, against the same description the rules were written
53/// against.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub struct Opcode(Symbol);
56
57impl Opcode {
58    /// The opcode of that name.
59    #[must_use]
60    pub const fn new(name: Symbol) -> Self {
61        Self(name)
62    }
63
64    /// Its name, which needs the interner it was made with to read.
65    #[must_use]
66    pub const fn name(self) -> Symbol {
67        self.0
68    }
69}
70
71/// A register, either one the allocator has still to place or one it has placed.
72///
73/// The two are one type and four bytes because every operand holds one and because a pass that
74/// runs both before and after allocation should not be two passes. Which of the two it is, is
75/// the top bit, so a virtual register is its own number and nothing has to be masked to compare
76/// two of them.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
78pub struct Reg(u32);
79
80impl Reg {
81    /// The bit that says the rest is a physical register rather than a virtual one.
82    const PHYSICAL: u32 = 1 << 31;
83
84    /// The virtual register with that number.
85    ///
86    /// # Panics
87    ///
88    /// Panics if the number is two billion or more, which no function reaches.
89    #[must_use]
90    pub const fn virtual_reg(number: u32) -> Self {
91        assert!(number < Self::PHYSICAL, "a function with two billion virtual registers");
92        Self(number)
93    }
94
95    /// The physical register, once one has been chosen.
96    #[must_use]
97    pub const fn physical(reg: PhysReg) -> Self {
98        Self(Self::PHYSICAL | reg.number() as u32)
99    }
100
101    /// Whether the allocator has still to place it.
102    #[must_use]
103    pub const fn is_virtual(self) -> bool {
104        self.0 & Self::PHYSICAL == 0
105    }
106
107    /// Its number as a virtual register, or `None` once it is a physical one.
108    #[must_use]
109    pub const fn number(self) -> Option<u32> {
110        if self.is_virtual() { Some(self.0) } else { None }
111    }
112
113    /// The physical register it is, or `None` while it is still virtual.
114    ///
115    /// Which class the register is in is on the operand rather than here, because an operand
116    /// carries its class already and a second copy of it is a thing that can disagree.
117    #[must_use]
118    pub const fn phys(self) -> Option<PhysReg> {
119        if self.is_virtual() { None } else { Some(PhysReg::new((self.0 & 0xff) as u8)) }
120    }
121}
122
123/// One operand of one instruction.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub struct Operand {
126    /// The register, virtual until the allocator has run.
127    pub reg: Reg,
128    /// The class it is drawn from.
129    pub class: RegClass,
130    /// Whether the instruction reads it or writes it.
131    pub role: Role,
132    /// Where it is allowed to live.
133    pub constraint: Constraint,
134}
135
136impl Operand {
137    /// An operand the instruction reads.
138    #[must_use]
139    pub const fn read(reg: Reg, class: RegClass) -> Self {
140        Self { reg, class, role: Role::Use, constraint: Constraint::Reg }
141    }
142
143    /// An operand the instruction writes as it finishes.
144    #[must_use]
145    pub const fn write(reg: Reg, class: RegClass) -> Self {
146        Self { reg, class, role: Role::Def, constraint: Constraint::Reg }
147    }
148
149    /// An operand the instruction writes before it has finished reading.
150    #[must_use]
151    pub const fn write_early(reg: Reg, class: RegClass) -> Self {
152        Self { reg, class, role: Role::EarlyDef, constraint: Constraint::Reg }
153    }
154
155    /// The same operand, constrained.
156    #[must_use]
157    pub const fn with(mut self, constraint: Constraint) -> Self {
158        self.constraint = constraint;
159        self
160    }
161}
162
163/// One immediate.
164///
165/// Signed and sixty-four bits, which every immediate field of every target we have is narrower
166/// than. What fits in the field the encoder is about to write is the encoder's question, and it
167/// is one it can only answer per opcode, so nothing here tries to.
168#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
169pub struct Imm(pub i64);
170
171/// What the four bytes beside a symbol in an address hold.
172///
173/// Three different numbers written in the same place, and nothing about the instruction says which
174/// one it is: `movq sym(%rip)`, `movq sym@GOTPCREL(%rip)` and `movq sym@GOTTPOFF(%rip)` are the
175/// same opcode with the same operands, and the only thing that tells them apart is the relocation
176/// the assembler leaves behind. So the difference has to be carried here, beside the symbol, rather
177/// than being read back out of the shape of the address.
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
179pub enum Reach {
180    /// The distance to the symbol itself, which is the ordinary case. `R_X86_64_PC32`.
181    #[default]
182    Itself,
183    /// The distance to the slot of the global offset table holding the symbol's address, which is
184    /// a load rather than an arithmetic. See [`Mem::got`].
185    Table,
186    /// The distance to the slot of the global offset table holding the symbol's offset inside a
187    /// thread's own block of storage. See [`Mem::thread`].
188    Thread,
189}
190
191/// A memory addressing mode, as the instruction holds it.
192///
193/// The registers are the indices of the operands holding them rather than the registers
194/// themselves, because an address register is a register the allocator has to see and rewrite,
195/// and the only thing it looks at is the operand vector. [`Mem`] is the same thing written the
196/// way a caller writes it, and [`crate::InstBuilder::mem`] turns one into the other.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub struct Amode {
199    /// The operand holding the base register.
200    pub base: Option<u8>,
201    /// The operand holding the index register.
202    pub index: Option<u8>,
203    /// What the index is multiplied by, which is 1 when there is no index.
204    pub scale: u8,
205    /// The constant added to the address.
206    pub disp: i32,
207    /// The symbol the address is relative to, for an access to a global.
208    pub symbol: Option<Symbol>,
209    /// The block of this function the address is of, for the address of a label. See
210    /// [`Mem::block`].
211    pub block: Option<Block>,
212    /// What the four bytes beside the symbol hold, when there is a symbol. See [`Reach`].
213    pub reach: Reach,
214    /// Which storage the address is counted from, when it is not the flat one. See [`Segment`].
215    pub segment: Option<Segment>,
216}
217
218impl Amode {
219    /// The addressing mode naming no register and no symbol, at offset zero.
220    pub const NOTHING: Self = Self {
221        base: None,
222        index: None,
223        scale: 1,
224        disp: 0,
225        symbol: None,
226        block: None,
227        reach: Reach::Itself,
228        segment: None,
229    };
230}
231
232/// A memory addressing mode as a caller writes one down.
233///
234/// The difference from [`Amode`] is that the registers are here rather than in the operand
235/// vector, which is what [`crate::InstBuilder::mem`] fixes. Keeping the two apart is what lets
236/// the operand indices in an [`Amode`] be an invariant of the builder rather than something
237/// every caller has to get right.
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
239pub struct Mem {
240    /// The base register, which the instruction reads.
241    pub base: Option<Operand>,
242    /// The index register, which the instruction reads.
243    pub index: Option<Operand>,
244    /// What the index is multiplied by.
245    pub scale: u8,
246    /// The constant added to the address.
247    pub disp: i32,
248    /// The symbol the address is relative to.
249    pub symbol: Option<Symbol>,
250    /// The block of this function the address is of. See [`Mem::block`].
251    pub block: Option<Block>,
252    /// What the four bytes beside the symbol hold, when there is a symbol. See [`Reach`].
253    pub reach: Reach,
254    /// Which storage the address is counted from, when it is not the flat one. See [`Segment`].
255    pub segment: Option<Segment>,
256}
257
258impl Mem {
259    /// The address in that register.
260    #[must_use]
261    pub const fn at(base: Operand) -> Self {
262        Self {
263            base: Some(base),
264            index: None,
265            scale: 1,
266            disp: 0,
267            symbol: None,
268            block: None,
269            reach: Reach::Itself,
270            segment: None,
271        }
272    }
273
274    /// The address of that symbol.
275    #[must_use]
276    pub const fn of(symbol: Symbol) -> Self {
277        Self {
278            base: None,
279            index: None,
280            scale: 1,
281            disp: 0,
282            symbol: Some(symbol),
283            block: None,
284            reach: Reach::Itself,
285            segment: None,
286        }
287    }
288
289    /// The address of that block of this function, which is what GNU's `&&label` is.
290    ///
291    /// A block rather than a symbol because the block it names is in this same function and has no
292    /// name outside it. What the assembler is given is the local label the block already carries,
293    /// which is a name the object file need not keep, and what the object writer is given is
294    /// nothing at all: the distance is between two places in one section and both of them are
295    /// known once the blocks have been laid out, so it is filled in here rather than left to a
296    /// linker the way the distance to a global is.
297    #[must_use]
298    pub const fn block(block: Block) -> Self {
299        Self {
300            base: None,
301            index: None,
302            scale: 1,
303            disp: 0,
304            symbol: None,
305            block: Some(block),
306            reach: Reach::Itself,
307            segment: None,
308        }
309    }
310
311    /// That many bytes into a thread's own block of words, which names no register at all.
312    ///
313    /// The whole address is the constant, because where the block is is something only the machine
314    /// knows: the segment register is what holds it and nothing loads one. See [`Segment`].
315    #[must_use]
316    pub const fn in_segment(segment: Segment, disp: i32) -> Self {
317        Self {
318            base: None,
319            index: None,
320            scale: 1,
321            disp,
322            symbol: None,
323            block: None,
324            reach: Reach::Itself,
325            segment: Some(segment),
326        }
327    }
328
329    /// The slot of the global offset table holding that symbol's address.
330    ///
331    /// Not the same thing as [`Self::of`] and not an optimization of it. `sym(%rip)` is the
332    /// address worked out from where the instruction is, which is only the right address when the
333    /// symbol is in this same object, and the linker refuses it in a position independent
334    /// executable when the symbol may turn out to be in a shared library. `sym@GOTPCREL(%rip)` is
335    /// a slot the linker fills in with the one address everybody agrees on, so it is a load rather
336    /// than an arithmetic, and whatever reads it gets an address rather than a place.
337    ///
338    /// The linker relaxes it back into the arithmetic when the symbol turns out to be in this
339    /// program after all, which is why nothing is lost by asking for it.
340    #[must_use]
341    pub const fn got(symbol: Symbol) -> Self {
342        Self { reach: Reach::Table, ..Self::of(symbol) }
343    }
344
345    /// The slot of the global offset table holding that symbol's offset inside a thread's block.
346    ///
347    /// A thread-local variable has no one address, since every thread has a copy of it, so there is
348    /// nothing for [`Self::of`] to be the distance to and a linker refuses one aimed at such a
349    /// symbol. What every copy does share is where it sits inside the block a thread gets, and that
350    /// offset is the number this slot holds: add it to the address of the running thread's block,
351    /// which the machine keeps in `%fs`, and the result is this thread's copy.
352    ///
353    /// The offset is a slot rather than a constant because how big the blocks in front of this
354    /// object's are is only known once the program is linked together, and in a shared library only
355    /// once it is loaded. The linker writes the constant into the instruction instead when it is
356    /// making an executable, where it does know, so this costs nothing in the case that is common.
357    #[must_use]
358    pub const fn thread(symbol: Symbol) -> Self {
359        Self { reach: Reach::Thread, ..Self::of(symbol) }
360    }
361
362    /// The same address with an index register scaled by that much.
363    #[must_use]
364    pub const fn indexed(mut self, index: Operand, scale: u8) -> Self {
365        self.index = Some(index);
366        self.scale = scale;
367        self
368    }
369
370    /// The same address, that many bytes along.
371    #[must_use]
372    pub const fn plus(mut self, disp: i32) -> Self {
373        self.disp = disp;
374        self
375    }
376}
377
378/// How often something happens, next to once for every time the function is entered.
379///
380/// Ten thousand is once, which is the scale the block frequencies in `rucc_opt` are worked out
381/// in. The numbers here are those carried down rather than worked out again: by the time the
382/// blocks are laid out the loops the frequency came from are branches and there is nothing left
383/// to work one out from.
384///
385/// Nothing keeps these in step with the graph afterwards. A pass that makes a block says how
386/// often the block runs, and a pass that does not is one whose new blocks run as often as the
387/// function does, which is what [`Weight::ONCE`] is and is the only answer available to something
388/// that was never told. They are a layout heuristic, nothing reads them for anything a wrong
389/// answer could make incorrect, and the worst a stale one costs is a jump where a fall-through
390/// would have done.
391#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
392pub struct Weight(u64);
393
394impl Weight {
395    /// The scale: how many parts one run of the function is divided into.
396    ///
397    /// Ten thousand rather than one, because a block inside three conditionals runs a fraction of
398    /// a time per call and a fraction is not an integer. It is the same scale `rucc_opt` uses,
399    /// which is what makes carrying a frequency down here a copy rather than a conversion.
400    pub const SCALE: u64 = 10_000;
401
402    /// Once for every time the function is entered.
403    pub const ONCE: Self = Self(Self::SCALE);
404
405    /// Never.
406    pub const NEVER: Self = Self(0);
407
408    /// That many parts of [`Weight::SCALE`].
409    #[must_use]
410    pub const fn parts(parts: u64) -> Self {
411        Self(parts)
412    }
413
414    /// How many parts of [`Weight::SCALE`] it is.
415    #[must_use]
416    pub const fn raw(self) -> u64 {
417        self.0
418    }
419
420    /// What fraction of `whole` this is, in parts of [`Weight::SCALE`].
421    ///
422    /// A whole of nothing answers nothing, since a block that never runs has no arm that is taken
423    /// more often than any other and the question has no answer rather than an arbitrary one.
424    #[must_use]
425    pub const fn out_of(self, whole: Self) -> u64 {
426        if whole.0 == 0 {
427            return 0;
428        }
429        // Saturating rather than wrapping, for the same reason a frequency saturates: a nest of
430        // loops multiplies, and a number that wrapped would read as cold where it is hottest.
431        match self.0.checked_mul(Self::SCALE) {
432            Some(scaled) => scaled / whole.0,
433            None => (self.0 / whole.0).saturating_mul(Self::SCALE),
434        }
435    }
436}
437
438impl Default for Weight {
439    /// Once, which is what a block nobody worked a number out for runs as often as.
440    fn default() -> Self {
441        Self::ONCE
442    }
443}
444
445/// One arm of a terminator: where it goes, and what it takes with it.
446///
447/// The arguments are the values the target block's parameters arrive as, so this is the edge on
448/// which a phi would otherwise sit. After allocation the parameters are physical registers and
449/// these arguments have become the moves that write them, which is the point at which MIR stops
450/// being in SSA form.
451#[derive(Debug, Clone, PartialEq, Eq)]
452pub struct BlockCall {
453    /// The block it goes to.
454    pub block: Block,
455    /// What its parameters arrive as, one for one.
456    pub args: Vec<Reg>,
457    /// How often the edge is taken, next to how often the function is entered. See [`Weight`].
458    pub weight: Weight,
459}
460
461impl BlockCall {
462    /// A jump to that block carrying nothing.
463    #[must_use]
464    pub const fn to(block: Block) -> Self {
465        Self { block, args: Vec::new(), weight: Weight::ONCE }
466    }
467
468    /// A jump to that block carrying those registers.
469    #[must_use]
470    pub fn with(block: Block, args: Vec<Reg>) -> Self {
471        Self { block, args, weight: Weight::ONCE }
472    }
473
474    /// The same arm, taken that often.
475    #[must_use]
476    pub fn taken(mut self, weight: Weight) -> Self {
477        self.weight = weight;
478        self
479    }
480}
481
482/// One parameter of a block: the register the value arrives in, and its class.
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484pub struct Param {
485    /// What the value arrives as, virtual until the allocator has run.
486    pub reg: Reg,
487    /// The class it is drawn from.
488    pub class: RegClass,
489}
490
491/// One instruction.
492#[derive(Debug, Clone, Copy, PartialEq, Eq)]
493pub struct InstData {
494    /// Which instruction this is.
495    pub opcode: Opcode,
496    /// Its operands, defs first and then uses, with the registers a memory operand names last.
497    /// The order is what the printer and the parser agree on, and [`crate::InstBuilder`] is
498    /// what keeps it.
499    pub operands: OperandList,
500    /// Its immediate, if it has one.
501    pub imm: Option<ImmRef>,
502    /// Its memory operand, if it has one.
503    pub mem: Option<MemRef>,
504    /// The symbol it names, which is the callee of a direct call and the target of a direct
505    /// jump to another function.
506    pub symbol: Option<Symbol>,
507}
508
509impl InstData {
510    /// An instruction with that opcode and nothing else.
511    #[must_use]
512    pub const fn new(opcode: Opcode) -> Self {
513        Self { opcode, operands: OperandList::EMPTY, imm: None, mem: None, symbol: None }
514    }
515}
516
517/// Where an instruction sits: which block it is in, and what is either side of it.
518#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
519pub(crate) struct InstLayout {
520    pub(crate) block: Option<Block>,
521    pub(crate) prev: Option<Inst>,
522    pub(crate) next: Option<Inst>,
523}
524
525/// One block: what arrives in it, what is in it, and where it goes.
526#[derive(Debug, Clone, Default, PartialEq, Eq)]
527pub struct BlockData {
528    /// The values that arrive in it, which are the function's arguments in the entry block.
529    pub params: Vec<Param>,
530    /// Where its terminator goes, in the order the terminator's arms run.
531    pub succs: Vec<BlockCall>,
532    /// How often the block runs, next to how often the function is entered. See [`Weight`].
533    pub weight: Weight,
534    pub(crate) first_inst: Option<Inst>,
535    pub(crate) last_inst: Option<Inst>,
536    pub(crate) prev: Option<Block>,
537    pub(crate) next: Option<Block>,
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543
544    #[test]
545    fn an_instruction_is_the_size_the_design_says() {
546        assert_eq!(size_of::<InstData>(), 24);
547        assert_eq!(size_of::<Operand>(), 8);
548    }
549
550    #[test]
551    fn a_virtual_register_is_its_own_number() {
552        let reg = Reg::virtual_reg(7);
553        assert!(reg.is_virtual());
554        assert_eq!(reg.number(), Some(7));
555        assert_eq!(reg.phys(), None);
556    }
557
558    #[test]
559    fn a_physical_register_is_not_a_virtual_one_of_the_same_number() {
560        let reg = Reg::physical(PhysReg::new(7));
561        assert!(!reg.is_virtual());
562        assert_eq!(reg.number(), None);
563        assert_eq!(reg.phys(), Some(PhysReg::new(7)));
564        assert_ne!(reg, Reg::virtual_reg(7));
565    }
566
567    #[test]
568    fn an_operand_keeps_what_it_was_constrained_to() {
569        let class = RegClass::new(0);
570        let plain = Operand::write(Reg::virtual_reg(1), class);
571        assert_eq!(plain.role, Role::Def);
572        assert_eq!(plain.constraint, Constraint::Reg);
573        let tied = plain.with(Constraint::Reuse(1));
574        assert_eq!(tied.constraint, Constraint::Reuse(1));
575        assert_eq!(tied.reg, plain.reg);
576        assert!(tied.role.is_def());
577        assert!(!Operand::read(Reg::virtual_reg(1), class).role.is_def());
578    }
579}