Skip to main content

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}