1 //! Pulley registers.
2
3 use crate::U6;
4 use core::hash::Hash;
5 use core::marker::PhantomData;
6 use core::{fmt, ops::Range};
7
8 use cranelift_bitset::ScalarBitSet;
9
10 /// Trait for common register operations.
11 pub trait Reg: Sized + Copy + Eq + Ord + Hash + Into<AnyReg> + fmt::Debug + fmt::Display {
12 /// Range of valid register indices.
13 const RANGE: Range<u8>;
14
15 /// Convert a register index to a register, without bounds checking.
new_unchecked(index: u8) -> Self16 unsafe fn new_unchecked(index: u8) -> Self;
17
18 /// Convert a register index to a register, with bounds checking.
new(index: u8) -> Option<Self>19 fn new(index: u8) -> Option<Self> {
20 if Self::RANGE.contains(&index) {
21 Some(unsafe { Self::new_unchecked(index) })
22 } else {
23 None
24 }
25 }
26
27 /// Convert a register to its index.
to_u8(self) -> u828 fn to_u8(self) -> u8;
29
30 /// Convert a register to its index.
index(self) -> usize31 fn index(self) -> usize {
32 self.to_u8().into()
33 }
34 }
35
36 macro_rules! impl_reg {
37 ($reg_ty:ty, $any:ident, $range:expr) => {
38 impl From<$reg_ty> for AnyReg {
39 fn from(r: $reg_ty) -> Self {
40 AnyReg::$any(r)
41 }
42 }
43
44 impl fmt::Display for $reg_ty {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 fmt::Debug::fmt(&self, f)
47 }
48 }
49
50 impl Reg for $reg_ty {
51 const RANGE: Range<u8> = $range;
52
53 unsafe fn new_unchecked(index: u8) -> Self {
54 unsafe { core::mem::transmute(index) }
55 }
56
57 fn to_u8(self) -> u8 {
58 self as u8
59 }
60 }
61 };
62 }
63
64 /// An `x` register: integers.
65 #[repr(u8)]
66 #[derive(Debug,Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
68 #[expect(missing_docs, reason = "self-describing variants")]
69 #[expect(non_camel_case_types, reason = "matching in-asm register names")]
70 #[rustfmt::skip]
71 pub enum XReg {
72 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9,
73 x10, x11, x12, x13, x14, x15, x16, x17, x18, x19,
74 x20, x21, x22, x23, x24, x25, x26, x27, x28, x29,
75
76 /// The special `sp` stack pointer register.
77 sp,
78
79 /// The special `spilltmp0` scratch register.
80 spilltmp0,
81
82 }
83
84 impl XReg {
85 /// Index of the first "special" register.
86 pub const SPECIAL_START: u8 = XReg::sp as u8;
87
88 /// Is this `x` register a special register?
is_special(self) -> bool89 pub fn is_special(self) -> bool {
90 matches!(self, Self::sp | Self::spilltmp0)
91 }
92 }
93
94 #[test]
assert_special_start_is_right()95 fn assert_special_start_is_right() {
96 for i in 0..XReg::SPECIAL_START {
97 assert!(!XReg::new(i).unwrap().is_special());
98 }
99 for i in XReg::SPECIAL_START.. {
100 match XReg::new(i) {
101 Some(r) => assert!(r.is_special()),
102 None => break,
103 }
104 }
105 }
106
107 /// An `f` register: floats.
108 #[repr(u8)]
109 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
110 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
111 #[expect(missing_docs, reason = "self-describing variants")]
112 #[expect(non_camel_case_types, reason = "matching in-asm register names")]
113 #[rustfmt::skip]
114 pub enum FReg {
115 f0, f1, f2, f3, f4, f5, f6, f7, f8, f9,
116 f10, f11, f12, f13, f14, f15, f16, f17, f18, f19,
117 f20, f21, f22, f23, f24, f25, f26, f27, f28, f29,
118 f30, f31,
119 }
120
121 /// A `v` register: vectors.
122 #[repr(u8)]
123 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
124 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
125 #[expect(missing_docs, reason = "self-describing variants")]
126 #[expect(non_camel_case_types, reason = "matching in-asm register names")]
127 #[rustfmt::skip]
128 pub enum VReg {
129 v0, v1, v2, v3, v4, v5, v6, v7, v8, v9,
130 v10, v11, v12, v13, v14, v15, v16, v17, v18, v19,
131 v20, v21, v22, v23, v24, v25, v26, v27, v28, v29,
132 v30, v31,
133 }
134
135 impl_reg!(XReg, X, 0..32);
136 impl_reg!(FReg, F, 0..32);
137 impl_reg!(VReg, V, 0..32);
138
139 /// Any register, regardless of class.
140 ///
141 /// Never appears inside an instruction -- instructions always name a particular
142 /// class of register -- but this is useful for testing and things like that.
143 #[expect(missing_docs, reason = "self-describing variants")]
144 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
145 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
146 pub enum AnyReg {
147 X(XReg),
148 F(FReg),
149 V(VReg),
150 }
151
152 impl fmt::Display for AnyReg {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 fmt::Debug::fmt(self, f)
155 }
156 }
157
158 impl fmt::Debug for AnyReg {
fmt(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result {
160 match self {
161 AnyReg::X(r) => fmt::Debug::fmt(r, f),
162 AnyReg::F(r) => fmt::Debug::fmt(r, f),
163 AnyReg::V(r) => fmt::Debug::fmt(r, f),
164 }
165 }
166 }
167
168 /// Operands to a binary operation, packed into a 16-bit word (5 bits per register).
169 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
170 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
171 pub struct BinaryOperands<D, S1 = D, S2 = D> {
172 /// The destination register, packed in bits 0..5.
173 pub dst: D,
174 /// The first source register, packed in bits 5..10.
175 pub src1: S1,
176 /// The second source register, packed in bits 10..15.
177 pub src2: S2,
178 }
179
180 impl<D, S1, S2> BinaryOperands<D, S1, S2> {
181 /// Convenience constructor for applying `Into`
new(dst: impl Into<D>, src1: impl Into<S1>, src2: impl Into<S2>) -> Self182 pub fn new(dst: impl Into<D>, src1: impl Into<S1>, src2: impl Into<S2>) -> Self {
183 Self {
184 dst: dst.into(),
185 src1: src1.into(),
186 src2: src2.into(),
187 }
188 }
189 }
190
191 impl<D: Reg, S1: Reg, S2: Reg> BinaryOperands<D, S1, S2> {
192 /// Convert to dense 16 bit encoding.
to_bits(self) -> u16193 pub fn to_bits(self) -> u16 {
194 let dst = self.dst.to_u8();
195 let src1 = self.src1.to_u8();
196 let src2 = self.src2.to_u8();
197 (dst as u16) | ((src1 as u16) << 5) | ((src2 as u16) << 10)
198 }
199
200 /// Convert from dense 16 bit encoding. The topmost bit is ignored.
from_bits(bits: u16) -> Self201 pub fn from_bits(bits: u16) -> Self {
202 Self {
203 dst: D::new((bits & 0b11111) as u8).unwrap(),
204 src1: S1::new(((bits >> 5) & 0b11111) as u8).unwrap(),
205 src2: S2::new(((bits >> 10) & 0b11111) as u8).unwrap(),
206 }
207 }
208 }
209
210 impl<D: Reg, S1: Reg> BinaryOperands<D, S1, U6> {
211 /// Convert to dense 16 bit encoding.
to_bits(self) -> u16212 pub fn to_bits(self) -> u16 {
213 let dst = self.dst.to_u8();
214 let src1 = self.src1.to_u8();
215 let src2 = u8::from(self.src2);
216 (dst as u16) | ((src1 as u16) << 5) | ((src2 as u16) << 10)
217 }
218
219 /// Convert from dense 16 bit encoding. The topmost bit is ignored.
from_bits(bits: u16) -> Self220 pub fn from_bits(bits: u16) -> Self {
221 Self {
222 dst: D::new((bits & 0b11111) as u8).unwrap(),
223 src1: S1::new(((bits >> 5) & 0b11111) as u8).unwrap(),
224 src2: U6::new(((bits >> 10) & 0b111111) as u8).unwrap(),
225 }
226 }
227 }
228
229 /// A set of "upper half" registers, packed into a 16-bit bitset.
230 ///
231 /// Registers stored in this bitset are offset by 16 and represent the upper
232 /// half of the 32 registers for each class.
233 pub struct UpperRegSet<R> {
234 bitset: ScalarBitSet<u16>,
235 phantom: PhantomData<R>,
236 }
237
238 impl<R: Reg> UpperRegSet<R> {
239 /// Create a `RegSet` from a `ScalarBitSet`.
from_bitset(bitset: ScalarBitSet<u16>) -> Self240 pub fn from_bitset(bitset: ScalarBitSet<u16>) -> Self {
241 Self {
242 bitset,
243 phantom: PhantomData,
244 }
245 }
246
247 /// Convert a `UpperRegSet` into a `ScalarBitSet`.
to_bitset(self) -> ScalarBitSet<u16>248 pub fn to_bitset(self) -> ScalarBitSet<u16> {
249 self.bitset
250 }
251 }
252
253 impl<R: Reg> From<ScalarBitSet<u16>> for UpperRegSet<R> {
from(bitset: ScalarBitSet<u16>) -> Self254 fn from(bitset: ScalarBitSet<u16>) -> Self {
255 Self {
256 bitset,
257 phantom: PhantomData,
258 }
259 }
260 }
261
262 impl<R: Reg> From<UpperRegSet<R>> for ScalarBitSet<u16> {
from(upper: UpperRegSet<R>) -> ScalarBitSet<u16>263 fn from(upper: UpperRegSet<R>) -> ScalarBitSet<u16> {
264 upper.bitset
265 }
266 }
267
268 impl<R: Reg> IntoIterator for UpperRegSet<R> {
269 type Item = R;
270 type IntoIter = UpperRegSetIntoIter<R>;
271
into_iter(self) -> Self::IntoIter272 fn into_iter(self) -> Self::IntoIter {
273 UpperRegSetIntoIter {
274 iter: self.bitset.into_iter(),
275 _marker: PhantomData,
276 }
277 }
278 }
279
280 /// Returned iterator from `UpperRegSet::into_iter`
281 pub struct UpperRegSetIntoIter<R> {
282 iter: cranelift_bitset::scalar::Iter<u16>,
283 _marker: PhantomData<R>,
284 }
285
286 impl<R: Reg> Iterator for UpperRegSetIntoIter<R> {
287 type Item = R;
next(&mut self) -> Option<R>288 fn next(&mut self) -> Option<R> {
289 Some(R::new(self.iter.next()? + 16).unwrap())
290 }
291 }
292
293 impl<R: Reg> DoubleEndedIterator for UpperRegSetIntoIter<R> {
next_back(&mut self) -> Option<R>294 fn next_back(&mut self) -> Option<R> {
295 Some(R::new(self.iter.next_back()? + 16).unwrap())
296 }
297 }
298
299 impl<R: Reg> Default for UpperRegSet<R> {
default() -> Self300 fn default() -> Self {
301 Self {
302 bitset: Default::default(),
303 phantom: Default::default(),
304 }
305 }
306 }
307
308 impl<R: Reg> Copy for UpperRegSet<R> {}
309 impl<R: Reg> Clone for UpperRegSet<R> {
clone(&self) -> Self310 fn clone(&self) -> Self {
311 *self
312 }
313 }
314
315 impl<R: Reg> PartialEq for UpperRegSet<R> {
eq(&self, other: &Self) -> bool316 fn eq(&self, other: &Self) -> bool {
317 self.bitset == other.bitset
318 }
319 }
320 impl<R: Reg> Eq for UpperRegSet<R> {}
321
322 impl<R: Reg> fmt::Debug for UpperRegSet<R> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 f.debug_set().entries(*self).finish()
325 }
326 }
327
328 #[cfg(feature = "arbitrary")]
329 impl<'a, R: Reg> arbitrary::Arbitrary<'a> for UpperRegSet<R> {
arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>330 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
331 ScalarBitSet::arbitrary(u).map(Self::from)
332 }
333 }
334
335 /// Immediate used for the "o32" addressing mode.
336 ///
337 /// This addressing mode represents a host address stored in `self.addr` which
338 /// is byte-offset by `self.offset`.
339 ///
340 /// This addressing mode cannot generate a trap.
341 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
342 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
343 pub struct AddrO32 {
344 /// The base address of memory.
345 pub addr: XReg,
346 /// A byte offset from `addr`.
347 pub offset: i32,
348 }
349
350 /// Immediate used for the "z" addressing mode.
351 ///
352 /// This addressing mode represents a host address stored in `self.addr` which
353 /// is byte-offset by `self.offset`.
354 ///
355 /// If the `addr` specified is NULL then operating on this value will generate a
356 /// trap.
357 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
358 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
359 pub struct AddrZ {
360 /// The base address of memory, or NULL.
361 pub addr: XReg,
362 /// A byte offset from `addr`.
363 pub offset: i32,
364 }
365
366 /// Immediate used for the "g32" addressing mode.
367 ///
368 /// This addressing mode represents the computation of a WebAssembly address for
369 /// a 32-bit linear memory. This automatically folds a bounds-check into the
370 /// address computation to generate a trap if the address is out-of-bounds.
371 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
372 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
373 pub struct AddrG32 {
374 /// The register holding the base address of the linear memory that is being
375 /// accessed.
376 pub host_heap_base: XReg,
377
378 /// The register holding the byte bound limit of the heap being accessed.
379 pub host_heap_bound: XReg,
380
381 /// The register holding a 32-bit WebAssembly address into linear memory.
382 ///
383 /// This is zero-extended on 64-bit platforms when performing the bounds
384 /// check.
385 pub wasm_addr: XReg,
386
387 /// A static byte offset from `host_heap_base` that is added to `wasm_addr`
388 /// when computing the bounds check.
389 pub offset: u16,
390 }
391
392 impl AddrG32 {
393 /// Decodes this immediate from a 32-bit integer.
from_bits(bits: u32) -> AddrG32394 pub fn from_bits(bits: u32) -> AddrG32 {
395 let host_heap_base = XReg::new(((bits >> 26) & 0b11111) as u8).unwrap();
396 let bound_reg = XReg::new(((bits >> 21) & 0b11111) as u8).unwrap();
397 let wasm_addr = XReg::new(((bits >> 16) & 0b11111) as u8).unwrap();
398 AddrG32 {
399 host_heap_base,
400 host_heap_bound: bound_reg,
401 wasm_addr,
402 offset: bits as u16,
403 }
404 }
405
406 /// Encodes this immediate into a 32-bit integer.
to_bits(&self) -> u32407 pub fn to_bits(&self) -> u32 {
408 u32::from(self.offset)
409 | (u32::from(self.wasm_addr.to_u8()) << 16)
410 | (u32::from(self.host_heap_bound.to_u8()) << 21)
411 | (u32::from(self.host_heap_base.to_u8()) << 26)
412 }
413 }
414
415 /// Similar structure to the [`AddrG32`] addressing mode but "g32bne" also
416 /// represents that the bound to linear memory is stored itself in memory.
417 ///
418 /// This instruction will load the heap bound from memory and then perform the
419 /// same bounds check that [`AddrG32`] does.
420 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
421 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
422 pub struct AddrG32Bne {
423 /// The register holding the base address of the linear memory that is being
424 /// accessed.
425 pub host_heap_base: XReg,
426
427 /// The register holding the address of where the heap bound is located in
428 /// host memory.
429 pub host_heap_bound_addr: XReg,
430
431 /// The static offset from `self.host_heap_bound_addr` that the bound is
432 /// located at.
433 pub host_heap_bound_offset: u8,
434
435 /// The register holding a 32-bit WebAssembly address into linear memory.
436 ///
437 /// This is zero-extended on 64-bit platforms when performing the bounds
438 /// check.
439 pub wasm_addr: XReg,
440
441 /// A static byte offset from `host_heap_base` that is added to `wasm_addr`
442 /// when computing the bounds check.
443 ///
444 /// Note that this is an 8-bit immediate instead of a 16-bit immediate
445 /// unlike [`AddrG32`]. That's just to pack this structure into a 32-bit
446 /// value for now but otherwise should be reasonable to extend to a larger
447 /// width in the future if necessary.
448 pub offset: u8,
449 }
450
451 impl AddrG32Bne {
452 /// Decodes [`AddrG32Bne`] from the 32-bit immediate provided.
from_bits(bits: u32) -> AddrG32Bne453 pub fn from_bits(bits: u32) -> AddrG32Bne {
454 let host_heap_base = XReg::new(((bits >> 26) & 0b11111) as u8).unwrap();
455 let bound_reg = XReg::new(((bits >> 21) & 0b11111) as u8).unwrap();
456 let wasm_addr = XReg::new(((bits >> 16) & 0b11111) as u8).unwrap();
457 AddrG32Bne {
458 host_heap_base,
459 host_heap_bound_addr: bound_reg,
460 host_heap_bound_offset: (bits >> 8) as u8,
461 wasm_addr,
462 offset: bits as u8,
463 }
464 }
465
466 /// Encodes this immediate into a 32-bit integer.
to_bits(&self) -> u32467 pub fn to_bits(&self) -> u32 {
468 u32::from(self.offset)
469 | (u32::from(self.host_heap_bound_offset) << 8)
470 | (u32::from(self.wasm_addr.to_u8()) << 16)
471 | (u32::from(self.host_heap_bound_addr.to_u8()) << 21)
472 | (u32::from(self.host_heap_base.to_u8()) << 26)
473 }
474 }
475
476 #[cfg(test)]
477 mod tests {
478 use super::*;
479
480 #[test]
special_x_regs()481 fn special_x_regs() {
482 assert!(XReg::sp.is_special());
483 assert!(XReg::spilltmp0.is_special());
484 }
485
486 #[test]
not_special_x_regs()487 fn not_special_x_regs() {
488 for i in 0..27 {
489 assert!(!XReg::new(i).unwrap().is_special());
490 }
491 }
492
493 #[test]
494 #[cfg_attr(miri, ignore)] // takes 30s+ in miri
binary_operands()495 fn binary_operands() {
496 let mut i = 0;
497 for src2 in XReg::RANGE {
498 for src1 in XReg::RANGE {
499 for dst in XReg::RANGE {
500 let operands = BinaryOperands {
501 dst: XReg::new(dst).unwrap(),
502 src1: XReg::new(src1).unwrap(),
503 src2: XReg::new(src2).unwrap(),
504 };
505 assert_eq!(operands.to_bits(), i);
506 assert_eq!(BinaryOperands::<XReg>::from_bits(i), operands);
507 assert_eq!(BinaryOperands::<XReg>::from_bits(0x8000 | i), operands);
508 i += 1;
509 }
510 }
511 }
512 }
513 }
514