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/// A memory addressing mode, as the instruction holds it.
172///
173/// The registers are the indices of the operands holding them rather than the registers
174/// themselves, because an address register is a register the allocator has to see and rewrite,
175/// and the only thing it looks at is the operand vector. [`Mem`] is the same thing written the
176/// way a caller writes it, and [`crate::InstBuilder::mem`] turns one into the other.
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub struct Amode {
179 /// The operand holding the base register.
180 pub base: Option<u8>,
181 /// The operand holding the index register.
182 pub index: Option<u8>,
183 /// What the index is multiplied by, which is 1 when there is no index.
184 pub scale: u8,
185 /// The constant added to the address.
186 pub disp: i32,
187 /// The symbol the address is relative to, for an access to a global.
188 pub symbol: Option<Symbol>,
189 /// Whether the address is read out of the global offset table rather than worked out from the
190 /// instruction pointer. See [`Mem::got`].
191 pub got: bool,
192 /// Which storage the address is counted from, when it is not the flat one. See [`Segment`].
193 pub segment: Option<Segment>,
194}
195
196impl Amode {
197 /// The addressing mode naming no register and no symbol, at offset zero.
198 pub const NOTHING: Self = Self {
199 base: None,
200 index: None,
201 scale: 1,
202 disp: 0,
203 symbol: None,
204 got: false,
205 segment: None,
206 };
207}
208
209/// A memory addressing mode as a caller writes one down.
210///
211/// The difference from [`Amode`] is that the registers are here rather than in the operand
212/// vector, which is what [`crate::InstBuilder::mem`] fixes. Keeping the two apart is what lets
213/// the operand indices in an [`Amode`] be an invariant of the builder rather than something
214/// every caller has to get right.
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
216pub struct Mem {
217 /// The base register, which the instruction reads.
218 pub base: Option<Operand>,
219 /// The index register, which the instruction reads.
220 pub index: Option<Operand>,
221 /// What the index is multiplied by.
222 pub scale: u8,
223 /// The constant added to the address.
224 pub disp: i32,
225 /// The symbol the address is relative to.
226 pub symbol: Option<Symbol>,
227 /// Whether the address is read out of the global offset table rather than worked out from the
228 /// instruction pointer. See [`Self::got`].
229 pub got: bool,
230 /// Which storage the address is counted from, when it is not the flat one. See [`Segment`].
231 pub segment: Option<Segment>,
232}
233
234impl Mem {
235 /// The address in that register.
236 #[must_use]
237 pub const fn at(base: Operand) -> Self {
238 Self {
239 base: Some(base),
240 index: None,
241 scale: 1,
242 disp: 0,
243 symbol: None,
244 got: false,
245 segment: None,
246 }
247 }
248
249 /// The address of that symbol.
250 #[must_use]
251 pub const fn of(symbol: Symbol) -> Self {
252 Self {
253 base: None,
254 index: None,
255 scale: 1,
256 disp: 0,
257 symbol: Some(symbol),
258 got: false,
259 segment: None,
260 }
261 }
262
263 /// That many bytes into a thread's own block of words, which names no register at all.
264 ///
265 /// The whole address is the constant, because where the block is is something only the machine
266 /// knows: the segment register is what holds it and nothing loads one. See [`Segment`].
267 #[must_use]
268 pub const fn in_segment(segment: Segment, disp: i32) -> Self {
269 Self {
270 base: None,
271 index: None,
272 scale: 1,
273 disp,
274 symbol: None,
275 got: false,
276 segment: Some(segment),
277 }
278 }
279
280 /// The slot of the global offset table holding that symbol's address.
281 ///
282 /// Not the same thing as [`Self::of`] and not an optimization of it. `sym(%rip)` is the
283 /// address worked out from where the instruction is, which is only the right address when the
284 /// symbol is in this same object, and the linker refuses it in a position independent
285 /// executable when the symbol may turn out to be in a shared library. `sym@GOTPCREL(%rip)` is
286 /// a slot the linker fills in with the one address everybody agrees on, so it is a load rather
287 /// than an arithmetic, and whatever reads it gets an address rather than a place.
288 ///
289 /// The linker relaxes it back into the arithmetic when the symbol turns out to be in this
290 /// program after all, which is why nothing is lost by asking for it.
291 #[must_use]
292 pub const fn got(symbol: Symbol) -> Self {
293 Self { got: true, ..Self::of(symbol) }
294 }
295
296 /// The same address with an index register scaled by that much.
297 #[must_use]
298 pub const fn indexed(mut self, index: Operand, scale: u8) -> Self {
299 self.index = Some(index);
300 self.scale = scale;
301 self
302 }
303
304 /// The same address, that many bytes along.
305 #[must_use]
306 pub const fn plus(mut self, disp: i32) -> Self {
307 self.disp = disp;
308 self
309 }
310}
311
312/// One arm of a terminator: where it goes, and what it takes with it.
313///
314/// The arguments are the values the target block's parameters arrive as, so this is the edge on
315/// which a phi would otherwise sit. After allocation the parameters are physical registers and
316/// these arguments have become the moves that write them, which is the point at which MIR stops
317/// being in SSA form.
318#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct BlockCall {
320 /// The block it goes to.
321 pub block: Block,
322 /// What its parameters arrive as, one for one.
323 pub args: Vec<Reg>,
324}
325
326impl BlockCall {
327 /// A jump to that block carrying nothing.
328 #[must_use]
329 pub const fn to(block: Block) -> Self {
330 Self { block, args: Vec::new() }
331 }
332
333 /// A jump to that block carrying those registers.
334 #[must_use]
335 pub fn with(block: Block, args: Vec<Reg>) -> Self {
336 Self { block, args }
337 }
338}
339
340/// One parameter of a block: the register the value arrives in, and its class.
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub struct Param {
343 /// What the value arrives as, virtual until the allocator has run.
344 pub reg: Reg,
345 /// The class it is drawn from.
346 pub class: RegClass,
347}
348
349/// One instruction.
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351pub struct InstData {
352 /// Which instruction this is.
353 pub opcode: Opcode,
354 /// Its operands, defs first and then uses, with the registers a memory operand names last.
355 /// The order is what the printer and the parser agree on, and [`crate::InstBuilder`] is
356 /// what keeps it.
357 pub operands: OperandList,
358 /// Its immediate, if it has one.
359 pub imm: Option<ImmRef>,
360 /// Its memory operand, if it has one.
361 pub mem: Option<MemRef>,
362 /// The symbol it names, which is the callee of a direct call and the target of a direct
363 /// jump to another function.
364 pub symbol: Option<Symbol>,
365}
366
367impl InstData {
368 /// An instruction with that opcode and nothing else.
369 #[must_use]
370 pub const fn new(opcode: Opcode) -> Self {
371 Self { opcode, operands: OperandList::EMPTY, imm: None, mem: None, symbol: None }
372 }
373}
374
375/// Where an instruction sits: which block it is in, and what is either side of it.
376#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
377pub(crate) struct InstLayout {
378 pub(crate) block: Option<Block>,
379 pub(crate) prev: Option<Inst>,
380 pub(crate) next: Option<Inst>,
381}
382
383/// One block: what arrives in it, what is in it, and where it goes.
384#[derive(Debug, Clone, Default, PartialEq, Eq)]
385pub struct BlockData {
386 /// The values that arrive in it, which are the function's arguments in the entry block.
387 pub params: Vec<Param>,
388 /// Where its terminator goes, in the order the terminator's arms run.
389 pub succs: Vec<BlockCall>,
390 pub(crate) first_inst: Option<Inst>,
391 pub(crate) last_inst: Option<Inst>,
392 pub(crate) prev: Option<Block>,
393 pub(crate) next: Option<Block>,
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn an_instruction_is_the_size_the_design_says() {
402 assert_eq!(size_of::<InstData>(), 24);
403 assert_eq!(size_of::<Operand>(), 8);
404 }
405
406 #[test]
407 fn a_virtual_register_is_its_own_number() {
408 let reg = Reg::virtual_reg(7);
409 assert!(reg.is_virtual());
410 assert_eq!(reg.number(), Some(7));
411 assert_eq!(reg.phys(), None);
412 }
413
414 #[test]
415 fn a_physical_register_is_not_a_virtual_one_of_the_same_number() {
416 let reg = Reg::physical(PhysReg::new(7));
417 assert!(!reg.is_virtual());
418 assert_eq!(reg.number(), None);
419 assert_eq!(reg.phys(), Some(PhysReg::new(7)));
420 assert_ne!(reg, Reg::virtual_reg(7));
421 }
422
423 #[test]
424 fn an_operand_keeps_what_it_was_constrained_to() {
425 let class = RegClass::new(0);
426 let plain = Operand::write(Reg::virtual_reg(1), class);
427 assert_eq!(plain.role, Role::Def);
428 assert_eq!(plain.constraint, Constraint::Reg);
429 let tied = plain.with(Constraint::Reuse(1));
430 assert_eq!(tied.constraint, Constraint::Reuse(1));
431 assert_eq!(tied.reg, plain.reg);
432 assert!(tied.role.is_def());
433 assert!(!Operand::read(Reg::virtual_reg(1), class).role.is_def());
434 }
435}