1 //! AArch64 register definition.
2 
3 use crate::isa::reg::Reg;
4 use crate::regset::RegBitSet;
5 use regalloc2::{PReg, RegClass};
6 
7 /// FPR index bound.
8 const MAX_FPR: u32 = 32;
9 /// FPR index bound.
10 const MAX_GPR: u32 = 32;
11 
12 /// Construct a X-register from an index.
xreg(num: u8) -> Reg13 pub(crate) const fn xreg(num: u8) -> Reg {
14     assert!((num as u32) < MAX_GPR);
15     Reg::new(PReg::new(num as usize, RegClass::Int))
16 }
17 
18 /// Construct a V-register from an index.
vreg(num: u8) -> Reg19 pub(crate) const fn vreg(num: u8) -> Reg {
20     assert!((num as u32) < MAX_FPR);
21     Reg::new(PReg::new(num as usize, RegClass::Float))
22 }
23 
24 /// Scratch register.
25 /// Intra-procedure-call corruptible register.
ip0() -> Reg26 pub(crate) const fn ip0() -> Reg {
27     xreg(16)
28 }
29 
30 // Alias to register v31.
float_scratch() -> Reg31 const fn float_scratch() -> Reg {
32     vreg(31)
33 }
34 
35 /// Scratch register.
36 /// Intra-procedure-call corruptible register.
ip1() -> Reg37 pub(crate) const fn ip1() -> Reg {
38     xreg(17)
39 }
40 
41 /// Register used to carry platform state.
platform() -> Reg42 const fn platform() -> Reg {
43     xreg(18)
44 }
45 
46 /// Frame pointer register.
fp() -> Reg47 pub(crate) const fn fp() -> Reg {
48     xreg(29)
49 }
50 
51 /// Link register for function calls.
lr() -> Reg52 pub(crate) const fn lr() -> Reg {
53     xreg(30)
54 }
55 
56 /// Zero register.
zero() -> Reg57 pub(crate) const fn zero() -> Reg {
58     xreg(31)
59 }
60 
61 /// The VM context register.
vmctx() -> Reg62 pub(crate) const fn vmctx() -> Reg {
63     xreg(9)
64 }
65 
66 /// Stack pointer register.
67 ///
68 /// In aarch64 the zero and stack pointer registers are contextually
69 /// different but have the same hardware encoding; to differentiate
70 /// them, we are following Cranelift's encoding and representing it as
71 /// 31 + 32.  Ref:
72 /// https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/codegen/src/isa/aarch64/inst/regs.rs#L70
sp() -> Reg73 pub(crate) const fn sp() -> Reg {
74     Reg::new(PReg::new(31 + 32, RegClass::Int))
75 }
76 
77 /// Shadow stack pointer register.
78 ///
79 /// The shadow stack pointer (SSP) is used as the base for memory addressing
80 /// to workaround Aarch64's constraint on the stack pointer 16-byte
81 /// alignment for memory addressing. This allows word-size loads and
82 /// stores.  It's always assumed that the real stack pointer (SP) is
83 /// 16-byte unaligned; the only exceptions to this assumption are:
84 ///
85 /// * The function prologue and epilogue in which we use SP
86 ///   for addressing, assuming that the 16-byte alignment is respected.
87 /// * Call sites, in which the code generation process explicitly ensures that
88 ///   the stack pointer is 16-byte aligned.
89 /// * Code that could result in signal handling, like loads/stores.
90 ///
91 /// SSP is utilized for space allocation. After each allocation, its value is
92 /// copied to SP, ensuring that SP accurately reflects the allocated space.
93 /// Accessing memory below SP may lead to undefined behavior, as this memory can
94 /// be overwritten by interrupts and signal handlers.
95 ///
96 /// This approach requires copying the value of SSP into SP every time SSP
97 /// changes, more explicitly, this happens at three main locations:
98 ///
99 /// 1. After space is allocated, to respect the requirement of avoiding
100 ///    addressing space below SP.
101 /// 2. At function epilogue.
102 /// 3. After explicit SP is emitted (code that could result in signal handling).
103 ///
104 /// +-----------+      Prologue:
105 /// |           |      * Save SSP (callee-saved)
106 /// +-----------+----- * SP at function entry (after prologue, slots for FP and LR)
107 /// |           |      * Copy the value of SP to SSP
108 /// |           |
109 /// +-----------+----- SSP after reserving stack space for locals and arguments
110 /// |           |      Copy the value of SSP to SP
111 /// |           |
112 /// +-----------+----- SSP after a push
113 /// |           |      Copy the value of SSP to SP
114 /// |           |
115 /// |           |
116 /// |           |
117 /// |           |      Epilogue:
118 /// |           |      * Copy SSP to SP
119 /// +-----------+----- * Restore SSP (callee-saved)
120 /// +-----------+
121 ///
122 /// In summary, the following invariants must be respected:
123 ///
124 /// * SSP is considered primary, and must be used to allocate and deallocate
125 ///   stack space(e.g. push, pop). This operation must always be followed by
126 ///   a copy of SSP to SP.
127 /// * SP must never be used to address memory except when we are certain that
128 ///   the required alignment is respected (e.g.  during the prologue and epilogue)
129 /// * SP must be explicitly aligned when code could result in signal handling.
130 /// * The value of SP is copied to SSP when entering a function.
131 /// * The value of SSP doesn't change between
132 ///   function calls (as it's callee saved), compliant with
133 ///   Aarch64's ABI.
134 /// * SSP is not available during register allocation.
shadow_sp() -> Reg135 pub(crate) const fn shadow_sp() -> Reg {
136     xreg(28)
137 }
138 
139 /// Bitmask for non-allocatable GPR.
140 const NON_ALLOCATABLE_GPR: u32 = (1 << ip0().hw_enc())
141     | (1 << ip1().hw_enc())
142     | (1 << platform().hw_enc())
143     | (1 << fp().hw_enc())
144     | (1 << lr().hw_enc())
145     | (1 << zero().hw_enc())
146     | (1 << shadow_sp().hw_enc())
147     | (1 << vmctx().hw_enc());
148 /// Bitmask to represent the available general purpose registers.
149 const ALLOCATABLE_GPR: u32 = u32::MAX & !NON_ALLOCATABLE_GPR;
150 
151 /// Bitmask for non-allocatable FPR.
152 /// All FPRs are allocatable, v0..=v7 are generally used for params and results.
153 
154 const NON_ALLOCATABLE_FPR: u32 = 1 << float_scratch().hw_enc();
155 /// Bitmask to represent the available floating point registers.
156 const ALLOCATABLE_FPR: u32 = u32::MAX & !NON_ALLOCATABLE_FPR;
157 
158 /// Allocatable scratch general purpose registers.
159 const ALLOCATABLE_SCRATCH_GPR: u32 = (1 << ip0().hw_enc()) | (1 << ip1().hw_enc());
160 /// Non-allocatable scratch general purpose registers.
161 const NON_ALLOCATABLE_SCRATCH_GPR: u32 = u32::MAX & !ALLOCATABLE_SCRATCH_GPR;
162 
163 const ALLOCATABLE_SCRATCH_FPR: u32 = 1 << float_scratch().hw_enc();
164 /// Non-allocatable scratch general purpose registers.
165 const NON_ALLOCATABLE_SCRATCH_FPR: u32 = u32::MAX & !ALLOCATABLE_SCRATCH_FPR;
166 
167 /// Bitset for allocatable general purpose registers.
gpr_bit_set() -> RegBitSet168 pub fn gpr_bit_set() -> RegBitSet {
169     RegBitSet::int(
170         ALLOCATABLE_GPR.into(),
171         NON_ALLOCATABLE_GPR.into(),
172         usize::try_from(MAX_GPR).unwrap(),
173     )
174 }
175 
176 /// Bitset for allocatable floating point registers.
fpr_bit_set() -> RegBitSet177 pub fn fpr_bit_set() -> RegBitSet {
178     RegBitSet::float(
179         ALLOCATABLE_FPR.into(),
180         NON_ALLOCATABLE_FPR.into(),
181         usize::try_from(MAX_FPR).unwrap(),
182     )
183 }
184 
185 /// Bitset for allocatable scratch general purpose registers.
scratch_gpr_bitset() -> RegBitSet186 pub fn scratch_gpr_bitset() -> RegBitSet {
187     RegBitSet::int(
188         ALLOCATABLE_SCRATCH_GPR.into(),
189         NON_ALLOCATABLE_SCRATCH_GPR.into(),
190         usize::try_from(MAX_GPR).unwrap(),
191     )
192 }
193 
194 /// Bitset for allocatable scratch floating point registers.
scratch_fpr_bitset() -> RegBitSet195 pub fn scratch_fpr_bitset() -> RegBitSet {
196     RegBitSet::float(
197         ALLOCATABLE_SCRATCH_FPR.into(),
198         NON_ALLOCATABLE_SCRATCH_FPR.into(),
199         usize::try_from(MAX_FPR).unwrap(),
200     )
201 }
202