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//! Successors are on the block rather than on the terminator, in the order the terminator's own
19//! arms run. That is regalloc2's arrangement, which `spec/10-backend.md` section 10.4 says the
20//! allocator interface follows, and it keeps a branch's arguments out of the operand vector
21//! where they would otherwise be uses the allocator has to be told to treat differently.
22//!
23//! The source location is a parallel array in the function, reached by [`crate::Func::span`],
24//! for the same reason `rucc-ir` puts it there: it is read when a diagnostic is being made and
25//! at no other time, so it does not belong on the row that every pass walks.
26
27use rucc_base::{Idx, IdxRange, Symbol};
28use rucc_target::{PhysReg, RegClass};
29
30/// One instruction, in the function that owns it.
31pub type Inst = Idx<InstData>;
32/// One basic block, in the function that owns it.
33pub type Block = Idx<BlockData>;
34/// A run of operands, which is what an instruction's operand vector is.
35pub type OperandList = IdxRange<Operand>;
36/// One immediate, in the function's immediate table.
37pub type ImmRef = Idx<Imm>;
38/// One addressing mode, in the function's table of them.
39pub type MemRef = Idx<Amode>;
40
41/// Which instruction this is.
42///
43/// A name rather than a variant of an enum. `spec/10-backend.md` section 10.8 says no pipeline
44/// crate holds target-specific code, and an enum of every x86-64 opcode in the crate every
45/// target's MIR passes through is exactly that. The opcodes a target has are data: they come out
46/// of its rule set, which is what the selector was compiled from, and this crate never asks what
47/// one of them means. The encoder does, against the same description the rules were written
48/// against.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct Opcode(Symbol);
51
52impl Opcode {
53 /// The opcode of that name.
54 #[must_use]
55 pub const fn new(name: Symbol) -> Self {
56 Self(name)
57 }
58
59 /// Its name, which needs the interner it was made with to read.
60 #[must_use]
61 pub const fn name(self) -> Symbol {
62 self.0
63 }
64}
65
66/// A register, either one the allocator has still to place or one it has placed.
67///
68/// The two are one type and four bytes because every operand holds one and because a pass that
69/// runs both before and after allocation should not be two passes. Which of the two it is, is
70/// the top bit, so a virtual register is its own number and nothing has to be masked to compare
71/// two of them.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
73pub struct Reg(u32);
74
75impl Reg {
76 /// The bit that says the rest is a physical register rather than a virtual one.
77 const PHYSICAL: u32 = 1 << 31;
78
79 /// The virtual register with that number.
80 ///
81 /// # Panics
82 ///
83 /// Panics if the number is two billion or more, which no function reaches.
84 #[must_use]
85 pub const fn virtual_reg(number: u32) -> Self {
86 assert!(number < Self::PHYSICAL, "a function with two billion virtual registers");
87 Self(number)
88 }
89
90 /// The physical register, once one has been chosen.
91 #[must_use]
92 pub const fn physical(reg: PhysReg) -> Self {
93 Self(Self::PHYSICAL | reg.number() as u32)
94 }
95
96 /// Whether the allocator has still to place it.
97 #[must_use]
98 pub const fn is_virtual(self) -> bool {
99 self.0 & Self::PHYSICAL == 0
100 }
101
102 /// Its number as a virtual register, or `None` once it is a physical one.
103 #[must_use]
104 pub const fn number(self) -> Option<u32> {
105 if self.is_virtual() { Some(self.0) } else { None }
106 }
107
108 /// The physical register it is, or `None` while it is still virtual.
109 ///
110 /// Which class the register is in is on the operand rather than here, because an operand
111 /// carries its class already and a second copy of it is a thing that can disagree.
112 #[must_use]
113 pub const fn phys(self) -> Option<PhysReg> {
114 if self.is_virtual() { None } else { Some(PhysReg::new((self.0 & 0xff) as u8)) }
115 }
116}
117
118/// What an instruction does with an operand.
119#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
120pub enum Role {
121 /// Reads it.
122 Use,
123 /// Writes it, at the point the instruction finishes, so it may share a register with an
124 /// operand the instruction reads.
125 Def,
126 /// Writes it before the instruction has finished reading, so it may not share a register
127 /// with anything the instruction reads. This is what a target says about an instruction
128 /// that clobbers its destination partway through.
129 EarlyDef,
130}
131
132impl Role {
133 /// Whether it writes the operand, early or late.
134 #[must_use]
135 pub const fn is_def(self) -> bool {
136 matches!(self, Role::Def | Role::EarlyDef)
137 }
138}
139
140/// Where an operand is allowed to live.
141///
142/// The set is regalloc2's, which `spec/10-backend.md` section 10.4 says the allocator interface
143/// follows. [`Constraint::Reg`] is the default rather than [`Constraint::Any`] because a machine
144/// instruction wants its operands in registers unless it has said otherwise, and a default that
145/// permits a stack slot would turn every rule that forgot to say so into a spill.
146#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
147pub enum Constraint {
148 /// Any register of its class.
149 Reg,
150 /// A register or a stack slot, whichever the allocator prefers.
151 Any,
152 /// A stack slot, which is what an operand too large for a register asks for.
153 Stack,
154 /// That register and no other, which is how a call says where an argument goes and how a
155 /// division says where its dividend goes.
156 Fixed(PhysReg),
157 /// The same register as the operand at that index, which is what a two-address form on
158 /// x86-64 needs: the destination is the first source, and the allocator is the one that has
159 /// to make that true.
160 Reuse(u8),
161}
162
163/// One operand of one instruction.
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct Operand {
166 /// The register, virtual until the allocator has run.
167 pub reg: Reg,
168 /// The class it is drawn from.
169 pub class: RegClass,
170 /// Whether the instruction reads it or writes it.
171 pub role: Role,
172 /// Where it is allowed to live.
173 pub constraint: Constraint,
174}
175
176impl Operand {
177 /// An operand the instruction reads.
178 #[must_use]
179 pub const fn read(reg: Reg, class: RegClass) -> Self {
180 Self { reg, class, role: Role::Use, constraint: Constraint::Reg }
181 }
182
183 /// An operand the instruction writes as it finishes.
184 #[must_use]
185 pub const fn write(reg: Reg, class: RegClass) -> Self {
186 Self { reg, class, role: Role::Def, constraint: Constraint::Reg }
187 }
188
189 /// An operand the instruction writes before it has finished reading.
190 #[must_use]
191 pub const fn write_early(reg: Reg, class: RegClass) -> Self {
192 Self { reg, class, role: Role::EarlyDef, constraint: Constraint::Reg }
193 }
194
195 /// The same operand, constrained.
196 #[must_use]
197 pub const fn with(mut self, constraint: Constraint) -> Self {
198 self.constraint = constraint;
199 self
200 }
201}
202
203/// One immediate.
204///
205/// Signed and sixty-four bits, which every immediate field of every target we have is narrower
206/// than. What fits in the field the encoder is about to write is the encoder's question, and it
207/// is one it can only answer per opcode, so nothing here tries to.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
209pub struct Imm(pub i64);
210
211/// A memory addressing mode, as the instruction holds it.
212///
213/// The registers are the indices of the operands holding them rather than the registers
214/// themselves, because an address register is a register the allocator has to see and rewrite,
215/// and the only thing it looks at is the operand vector. [`Mem`] is the same thing written the
216/// way a caller writes it, and [`crate::InstBuilder::mem`] turns one into the other.
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub struct Amode {
219 /// The operand holding the base register.
220 pub base: Option<u8>,
221 /// The operand holding the index register.
222 pub index: Option<u8>,
223 /// What the index is multiplied by, which is 1 when there is no index.
224 pub scale: u8,
225 /// The constant added to the address.
226 pub disp: i32,
227 /// The symbol the address is relative to, for an access to a global.
228 pub symbol: Option<Symbol>,
229}
230
231impl Amode {
232 /// The addressing mode naming no register and no symbol, at offset zero.
233 pub const NOTHING: Self = Self { base: None, index: None, scale: 1, disp: 0, symbol: None };
234}
235
236/// A memory addressing mode as a caller writes one down.
237///
238/// The difference from [`Amode`] is that the registers are here rather than in the operand
239/// vector, which is what [`crate::InstBuilder::mem`] fixes. Keeping the two apart is what lets
240/// the operand indices in an [`Amode`] be an invariant of the builder rather than something
241/// every caller has to get right.
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
243pub struct Mem {
244 /// The base register, which the instruction reads.
245 pub base: Option<Operand>,
246 /// The index register, which the instruction reads.
247 pub index: Option<Operand>,
248 /// What the index is multiplied by.
249 pub scale: u8,
250 /// The constant added to the address.
251 pub disp: i32,
252 /// The symbol the address is relative to.
253 pub symbol: Option<Symbol>,
254}
255
256impl Mem {
257 /// The address in that register.
258 #[must_use]
259 pub const fn at(base: Operand) -> Self {
260 Self { base: Some(base), index: None, scale: 1, disp: 0, symbol: None }
261 }
262
263 /// The address of that symbol.
264 #[must_use]
265 pub const fn of(symbol: Symbol) -> Self {
266 Self { base: None, index: None, scale: 1, disp: 0, symbol: Some(symbol) }
267 }
268
269 /// The same address with an index register scaled by that much.
270 #[must_use]
271 pub const fn indexed(mut self, index: Operand, scale: u8) -> Self {
272 self.index = Some(index);
273 self.scale = scale;
274 self
275 }
276
277 /// The same address, that many bytes along.
278 #[must_use]
279 pub const fn plus(mut self, disp: i32) -> Self {
280 self.disp = disp;
281 self
282 }
283}
284
285/// One arm of a terminator: where it goes, and what it takes with it.
286///
287/// The arguments are the values the target block's parameters arrive as, so this is the edge on
288/// which a phi would otherwise sit. After allocation the parameters are physical registers and
289/// these arguments have become the moves that write them, which is the point at which MIR stops
290/// being in SSA form.
291#[derive(Debug, Clone, PartialEq, Eq)]
292pub struct BlockCall {
293 /// The block it goes to.
294 pub block: Block,
295 /// What its parameters arrive as, one for one.
296 pub args: Vec<Reg>,
297}
298
299impl BlockCall {
300 /// A jump to that block carrying nothing.
301 #[must_use]
302 pub const fn to(block: Block) -> Self {
303 Self { block, args: Vec::new() }
304 }
305
306 /// A jump to that block carrying those registers.
307 #[must_use]
308 pub fn with(block: Block, args: Vec<Reg>) -> Self {
309 Self { block, args }
310 }
311}
312
313/// One parameter of a block: the register the value arrives in, and its class.
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub struct Param {
316 /// What the value arrives as, virtual until the allocator has run.
317 pub reg: Reg,
318 /// The class it is drawn from.
319 pub class: RegClass,
320}
321
322/// One instruction.
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub struct InstData {
325 /// Which instruction this is.
326 pub opcode: Opcode,
327 /// Its operands, defs first and then uses, with the registers a memory operand names last.
328 /// The order is what the printer and the parser agree on, and [`crate::InstBuilder`] is
329 /// what keeps it.
330 pub operands: OperandList,
331 /// Its immediate, if it has one.
332 pub imm: Option<ImmRef>,
333 /// Its memory operand, if it has one.
334 pub mem: Option<MemRef>,
335 /// The symbol it names, which is the callee of a direct call and the target of a direct
336 /// jump to another function.
337 pub symbol: Option<Symbol>,
338}
339
340impl InstData {
341 /// An instruction with that opcode and nothing else.
342 #[must_use]
343 pub const fn new(opcode: Opcode) -> Self {
344 Self { opcode, operands: OperandList::EMPTY, imm: None, mem: None, symbol: None }
345 }
346}
347
348/// Where an instruction sits: which block it is in, and what is either side of it.
349#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
350pub(crate) struct InstLayout {
351 pub(crate) block: Option<Block>,
352 pub(crate) prev: Option<Inst>,
353 pub(crate) next: Option<Inst>,
354}
355
356/// One block: what arrives in it, what is in it, and where it goes.
357#[derive(Debug, Clone, Default, PartialEq, Eq)]
358pub struct BlockData {
359 /// The values that arrive in it, which are the function's arguments in the entry block.
360 pub params: Vec<Param>,
361 /// Where its terminator goes, in the order the terminator's arms run.
362 pub succs: Vec<BlockCall>,
363 pub(crate) first_inst: Option<Inst>,
364 pub(crate) last_inst: Option<Inst>,
365 pub(crate) prev: Option<Block>,
366 pub(crate) next: Option<Block>,
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn an_instruction_is_the_size_the_design_says() {
375 assert_eq!(size_of::<InstData>(), 24);
376 assert_eq!(size_of::<Operand>(), 8);
377 }
378
379 #[test]
380 fn a_virtual_register_is_its_own_number() {
381 let reg = Reg::virtual_reg(7);
382 assert!(reg.is_virtual());
383 assert_eq!(reg.number(), Some(7));
384 assert_eq!(reg.phys(), None);
385 }
386
387 #[test]
388 fn a_physical_register_is_not_a_virtual_one_of_the_same_number() {
389 let reg = Reg::physical(PhysReg::new(7));
390 assert!(!reg.is_virtual());
391 assert_eq!(reg.number(), None);
392 assert_eq!(reg.phys(), Some(PhysReg::new(7)));
393 assert_ne!(reg, Reg::virtual_reg(7));
394 }
395
396 #[test]
397 fn an_operand_keeps_what_it_was_constrained_to() {
398 let class = RegClass::new(0);
399 let plain = Operand::write(Reg::virtual_reg(1), class);
400 assert_eq!(plain.role, Role::Def);
401 assert_eq!(plain.constraint, Constraint::Reg);
402 let tied = plain.with(Constraint::Reuse(1));
403 assert_eq!(tied.constraint, Constraint::Reuse(1));
404 assert_eq!(tied.reg, plain.reg);
405 assert!(tied.role.is_def());
406 assert!(!Operand::read(Reg::virtual_reg(1), class).role.is_def());
407 }
408}