rucc_target/operand.rs
1//! What an instruction does with an operand, and where the operand is allowed to live.
2//!
3//! Design: `spec/10-backend.md` sections 10.1 and 10.4.
4//!
5//! These are the two facts the register allocator reads about an operand and they are the whole
6//! of what it reads. The opcode is a name to everything except the encoder, so an allocator
7//! never has to know what any particular target's instructions mean, and a target says what its
8//! instructions do to their operands by describing them here.
9//!
10//! They live in this crate rather than in `rucc-mir` because a target's instruction description
11//! is data and it is written down before there is any machine IR to put it in. `rucc-mir`
12//! re-exports both, so the machine IR is still where a pass reads them from.
13//!
14//! The vocabulary is regalloc2's, which `spec/10-backend.md` section 10.4 says the allocator
15//! interface follows.
16
17use crate::regs::{PhysReg, RegClass};
18
19/// What an instruction does with an operand.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub enum Role {
22 /// Reads it.
23 Use,
24 /// Writes it, at the point the instruction finishes, so it may share a register with an
25 /// operand the instruction reads.
26 Def,
27 /// Writes it before the instruction has finished reading, so it may not share a register
28 /// with anything the instruction reads. This is what a target says about an instruction
29 /// that clobbers its destination partway through.
30 EarlyDef,
31}
32
33impl Role {
34 /// Whether it writes the operand, early or late.
35 #[must_use]
36 pub const fn is_def(self) -> bool {
37 matches!(self, Role::Def | Role::EarlyDef)
38 }
39}
40
41/// Where an operand is allowed to live.
42///
43/// [`Constraint::Reg`] is the default rather than [`Constraint::Any`] because a machine
44/// instruction wants its operands in registers unless it has said otherwise, and a default that
45/// permits a stack slot would turn every rule that forgot to say so into a spill.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub enum Constraint {
48 /// Any register of its class.
49 Reg,
50 /// A register or a stack slot, whichever the allocator prefers.
51 Any,
52 /// A stack slot, which is what an operand too large for a register asks for.
53 Stack,
54 /// That register and no other, which is how a call says where an argument goes and how a
55 /// division says where its dividend goes.
56 Fixed(PhysReg),
57 /// The same register as the operand at that index, which is what a two-address form on
58 /// x86-64 needs: the destination is the first source, and the allocator is the one that has
59 /// to make that true.
60 Reuse(u8),
61}
62
63/// One operand of one instruction, as a target's description of that instruction writes it.
64///
65/// The difference from an operand in the machine IR is the register: there is none here,
66/// because a description is about every instruction of that opcode and a register belongs to
67/// one of them.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct OperandDesc {
70 /// The class the operand is drawn from.
71 pub class: RegClass,
72 /// Whether the instruction reads it or writes it.
73 pub role: Role,
74 /// Where it is allowed to live.
75 pub constraint: Constraint,
76}
77
78impl OperandDesc {
79 /// An operand the instruction reads, in any register of its class.
80 #[must_use]
81 pub const fn read(class: RegClass) -> Self {
82 Self { class, role: Role::Use, constraint: Constraint::Reg }
83 }
84
85 /// An operand the instruction writes as it finishes.
86 #[must_use]
87 pub const fn write(class: RegClass) -> Self {
88 Self { class, role: Role::Def, constraint: Constraint::Reg }
89 }
90
91 /// An operand the instruction writes before it has finished reading, which is what a
92 /// register the instruction destroys on its way through is.
93 #[must_use]
94 pub const fn write_early(class: RegClass) -> Self {
95 Self { class, role: Role::EarlyDef, constraint: Constraint::Reg }
96 }
97
98 /// The same operand, constrained.
99 #[must_use]
100 pub const fn with(mut self, constraint: Constraint) -> Self {
101 self.constraint = constraint;
102 self
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn a_role_that_writes_says_so_however_early_it_writes() {
112 assert!(Role::Def.is_def());
113 assert!(Role::EarlyDef.is_def());
114 assert!(!Role::Use.is_def());
115 }
116
117 #[test]
118 fn a_described_operand_keeps_what_it_was_constrained_to() {
119 let class = RegClass::new(0);
120 let plain = OperandDesc::write(class);
121 assert_eq!(plain.constraint, Constraint::Reg);
122 let tied = plain.with(Constraint::Reuse(1));
123 assert_eq!(tied.constraint, Constraint::Reuse(1));
124 assert_eq!(tied.role, Role::Def);
125 assert_eq!(OperandDesc::read(class).role, Role::Use);
126 assert_eq!(OperandDesc::write_early(class).role, Role::EarlyDef);
127 }
128}