hashx/register.rs
1//! Define HashX's register file, and how it's created and digested.
2
3use crate::siphash::{SipState, siphash24_ctr};
4use arrayvec::ArrayVec;
5use std::fmt;
6
7/// Number of virtual registers in the HashX machine
8pub(crate) const NUM_REGISTERS: usize = 8;
9
10/// Register `R5`
11///
12/// Most HashX registers have no special properties, so we don't even
13/// bother naming them. Register R5 is the exception, HashX defines a
14/// specific constraint there for the benefit of x86_64 code generation.
15pub(crate) const R5: RegisterId = RegisterId(5);
16
17/// Identify one register (R0 - R7) in HashX's virtual machine
18#[derive(Clone, Copy, Eq, PartialEq)]
19pub(crate) struct RegisterId(u8);
20
21impl fmt::Debug for RegisterId {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "R{}", self.0)
24 }
25}
26
27impl RegisterId {
28 /// Cast this RegisterId into a plain usize
29 #[inline(always)]
30 pub(crate) fn as_usize(&self) -> usize {
31 self.0 as usize
32 }
33
34 /// Return the underlying u8 for this RegisterId.
35 ///
36 /// (Recall that hashx has 8 virtual registers,
37 /// so the output of this method is always in range 0..=7.)
38 #[inline(always)]
39 #[cfg(feature = "compiler")]
40 pub(crate) fn as_u8(&self) -> u8 {
41 self.0
42 }
43
44 /// Create an iterator over all RegisterId
45 #[inline(always)]
46 pub(crate) fn all() -> impl Iterator<Item = RegisterId> {
47 (0_u8..(NUM_REGISTERS as u8)).map(RegisterId)
48 }
49}
50
51/// Identify a set of RegisterIds
52///
53/// This could be done compactly as a u8 bitfield for storage purposes, but
54/// in our program generator this is never stored long-term. Instead, we want
55/// something the optimizer can reason about as effectively as possible, and
56/// we want to optimize for an index() implementation that doesn't branch.
57/// This uses a fixed-capacity array of registers in-set, always sorted.
58#[derive(Default, Clone, Eq, PartialEq)]
59pub(crate) struct RegisterSet(ArrayVec<RegisterId, NUM_REGISTERS>);
60
61impl fmt::Debug for RegisterSet {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "[")?;
64 for n in 0..self.len() {
65 if n != 0 {
66 write!(f, ",")?;
67 }
68 self.index(n).fmt(f)?;
69 }
70 write!(f, "]")
71 }
72}
73
74impl RegisterSet {
75 /// Number of registers still contained in this set
76 #[inline(always)]
77 pub(crate) fn len(&self) -> usize {
78 self.0.len()
79 }
80
81 /// Test if a register is contained in the set.
82 #[inline(always)]
83 pub(crate) fn contains(&self, id: RegisterId) -> bool {
84 self.0.contains(&id)
85 }
86
87 /// Build a new RegisterSet from each register for which a predicate
88 /// function returns `true`.
89 #[inline(always)]
90 pub(crate) fn from_filter<P: FnMut(RegisterId) -> bool>(mut predicate: P) -> Self {
91 let mut result: Self = Default::default();
92 for r in RegisterId::all() {
93 if predicate(r) {
94 result.0.push(r);
95 }
96 }
97 result
98 }
99
100 /// Return a particular register within this set, counting from R0 to R7.
101 ///
102 /// The supplied index must be less than the [`Self::len()`] of this set.
103 /// Panics if the index is out of range.
104 #[inline(always)]
105 pub(crate) fn index(&self, index: usize) -> RegisterId {
106 self.0[index]
107 }
108}
109
110/// Values for all registers in the HashX machine
111///
112/// Guaranteed to have a `repr(C)` layout that includes each register in order
113/// with no padding and no extra fields. The compiled runtime will produce
114/// functions that read or write a `RegisterFile` directly.
115
116#[derive(Debug, Clone, Eq, PartialEq)]
117#[repr(C)]
118pub(crate) struct RegisterFile([u64; NUM_REGISTERS]);
119
120impl RegisterFile {
121 /// Load a word from the register file.
122 #[inline(always)]
123 pub(crate) fn load(&self, id: RegisterId) -> u64 {
124 self.0[id.as_usize()]
125 }
126
127 /// Store a word into the register file.
128 #[inline(always)]
129 pub(crate) fn store(&mut self, id: RegisterId, value: u64) {
130 self.0[id.as_usize()] = value;
131 }
132
133 /// Initialize a new HashX register file, given a key (derived from
134 /// the seed) and the user-specified hash input word.
135 #[inline(always)]
136 pub(crate) fn new(key: SipState, input: u64) -> Self {
137 RegisterFile(siphash24_ctr(key, input))
138 }
139
140 /// Finalize the state of the register file and generate up to 4 words of
141 /// output in HashX's final result format.
142 ///
143 /// This splits the register file into two halves, mixes in the siphash
144 /// keys again to "remove bias toward 0 caused by multiplications", and
145 /// runs one siphash round on each half before recombining them.
146 #[inline(always)]
147 pub(crate) fn digest(&self, key: SipState) -> [u64; 4] {
148 let mut x = SipState {
149 v0: self.0[0].wrapping_add(key.v0),
150 v1: self.0[1].wrapping_add(key.v1),
151 v2: self.0[2],
152 v3: self.0[3],
153 };
154 let mut y = SipState {
155 v0: self.0[4],
156 v1: self.0[5],
157 v2: self.0[6].wrapping_add(key.v2),
158 v3: self.0[7].wrapping_add(key.v3),
159 };
160 x.sip_round();
161 y.sip_round();
162 [x.v0 ^ y.v0, x.v1 ^ y.v1, x.v2 ^ y.v2, x.v3 ^ y.v3]
163 }
164}