1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
//! Define HashX's register file, and how it's created and digested.
use crate::siphash::{siphash24_ctr, SipState};
use std::fmt;
/// Number of virtual registers in the HashX machine
pub(crate) const NUM_REGISTERS: usize = 8;
/// Register `R5`
///
/// Most HashX registers have no special properties, so we don't even
/// bother naming them. Register R5 is the exception, HashX defines a
/// specific constraint there for the benefit of x86_64 code generation.
pub(crate) const R5: RegisterId = RegisterId(5);
/// Identify one register (R0 - R7) in HashX's virtual machine
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) struct RegisterId(u8);
impl fmt::Debug for RegisterId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "R{}", self.0)
}
}
impl RegisterId {
/// Cast this RegisterId into a plain usize
#[inline(always)]
pub(crate) fn as_usize(&self) -> usize {
self.0 as usize
}
}
/// Identify a set of RegisterIds
///
/// This could be done compactly as a u8 bitfield for storage purposes, but
/// in our program generator this is never stored long-term. Instead, we want
/// something the optimizer can reason about as effectively as possible, and
/// let's inline as much as possible in order to resolve special cases in
/// the program generator at compile time.
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) struct RegisterSet {
/// Number of registers in the set
len: usize,
/// Array indexed by register Id, indicating registers we've excluded
reg_not_in_set: [bool; 8],
}
impl fmt::Debug for RegisterSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[")?;
for n in 0..self.len() {
if n != 0 {
write!(f, ",")?;
}
self.index(n).fmt(f)?;
}
write!(f, "]")
}
}
impl RegisterSet {
/// Construct the set of all registers.
///
/// This is the main way to construct a new RegisterId, starting with
/// all available registers and filtering them repeatedly.
#[inline(always)]
pub(crate) fn all() -> Self {
Self {
len: NUM_REGISTERS,
reg_not_in_set: Default::default(),
}
}
/// Number of registers still contained in this set
#[inline(always)]
pub(crate) fn len(&self) -> usize {
self.len
}
/// Test if a register is contained in the set.
#[inline(always)]
pub(crate) fn contains(&self, id: RegisterId) -> bool {
!self.reg_not_in_set[id.0 as usize]
}
/// Filter this register set through a predicate.
///
/// Invokes the predicate only for registers in this set, and returns the
/// set of registers for which it returned true.
#[inline(always)]
pub(crate) fn filter<P: FnMut(RegisterId) -> bool>(&self, mut predicate: P) -> Self {
let mut result = Self {
len: 0,
reg_not_in_set: Default::default(),
};
self.filter_impl(0, &mut predicate, &mut result);
self.filter_impl(1, &mut predicate, &mut result);
self.filter_impl(2, &mut predicate, &mut result);
self.filter_impl(3, &mut predicate, &mut result);
self.filter_impl(4, &mut predicate, &mut result);
self.filter_impl(5, &mut predicate, &mut result);
self.filter_impl(6, &mut predicate, &mut result);
self.filter_impl(7, &mut predicate, &mut result);
result
}
/// Internal implementation to be unrolled by `filter`
#[inline(always)]
fn filter_impl<P: FnMut(RegisterId) -> bool>(
&self,
id: usize,
predicate: &mut P,
result: &mut Self,
) {
if self.reg_not_in_set[id] {
result.reg_not_in_set[id] = true;
} else if predicate(RegisterId(id as u8)) {
result.len += 1;
} else {
result.reg_not_in_set[id] = true;
}
}
/// Return a particular register within this set, counting from R0 to R7.
///
/// The supplied index must be less than the [`Self::len()`] of this set.
/// Panics if the index is out of range.
#[inline(always)]
pub(crate) fn index(&self, mut index: usize) -> RegisterId {
if let Some(result) = self.index_impl(0, &mut index) {
return result;
}
if let Some(result) = self.index_impl(1, &mut index) {
return result;
}
if let Some(result) = self.index_impl(2, &mut index) {
return result;
}
if let Some(result) = self.index_impl(3, &mut index) {
return result;
}
if let Some(result) = self.index_impl(4, &mut index) {
return result;
}
if let Some(result) = self.index_impl(5, &mut index) {
return result;
}
if let Some(result) = self.index_impl(6, &mut index) {
return result;
}
if let Some(result) = self.index_impl(7, &mut index) {
return result;
}
unreachable!();
}
/// Internal implementation to be unrolled by `index`
#[inline(always)]
fn index_impl(&self, id: usize, index: &mut usize) -> Option<RegisterId> {
if self.reg_not_in_set[id] {
None
} else if *index == 0 {
Some(RegisterId(id as u8))
} else {
*index -= 1;
None
}
}
}
/// Values for all registers in the HashX machine
///
/// Guaranteed to have a `repr(C)` layout that includes each register in order
/// with no padding and no extra fields. The compiled runtime will produce
/// functions that read or write a `RegisterFile` directly.
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct RegisterFile([u64; NUM_REGISTERS]);
impl RegisterFile {
/// Load a word from the register file.
#[inline(always)]
pub(crate) fn load(&self, id: RegisterId) -> u64 {
self.0[id.as_usize()]
}
/// Store a word into the register file.
#[inline(always)]
pub(crate) fn store(&mut self, id: RegisterId, value: u64) {
self.0[id.as_usize()] = value;
}
/// Initialize a new HashX register file, given a key (derived from
/// the seed) and the user-specified hash input word.
#[inline(always)]
pub(crate) fn new(key: SipState, input: u64) -> Self {
RegisterFile(siphash24_ctr(key, input))
}
/// Finalize the state of the register file and generate up to 4 words of
/// output in HashX's final result format.
///
/// This splits the register file into two halves, mixes in the siphash
/// keys again to "remove bias toward 0 caused by multiplications", and
/// runs one siphash round on each half before recombining them.
#[inline(always)]
pub(crate) fn digest(&self, key: SipState) -> [u64; 4] {
let mut x = SipState {
v0: self.0[0].wrapping_add(key.v0),
v1: self.0[1].wrapping_add(key.v1),
v2: self.0[2],
v3: self.0[3],
};
let mut y = SipState {
v0: self.0[4],
v1: self.0[5],
v2: self.0[6].wrapping_add(key.v2),
v3: self.0[7].wrapping_add(key.v3),
};
x.sip_round();
y.sip_round();
[x.v0 ^ y.v0, x.v1 ^ y.v1, x.v2 ^ y.v2, x.v3 ^ y.v3]
}
}
#[cfg(test)]
mod test {
use super::RegisterSet;
#[test]
fn register_set() {
let r = RegisterSet::all().filter(|_reg| true);
assert_eq!(r.len(), 8);
assert_eq!(r.index(7).as_usize(), 7);
assert_eq!(r.index(0).as_usize(), 0);
let r = r.filter(|reg| (reg.as_usize() & 1) != 0);
assert_eq!(r.len(), 4);
assert_eq!(r.index(0).as_usize(), 1);
assert_eq!(r.index(1).as_usize(), 3);
assert_eq!(r.index(2).as_usize(), 5);
assert_eq!(r.index(3).as_usize(), 7);
let r = r.filter(|reg| (reg.as_usize() & 2) != 0);
assert_eq!(r.index(0).as_usize(), 3);
assert_eq!(r.index(1).as_usize(), 7);
let r = r.filter(|_reg| true);
assert_eq!(r.len(), 2);
let r = r.filter(|_reg| false);
assert_eq!(r.len(), 0);
}
}
|