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
//! Define HashX's register file, and how it's created and digested.
use crate::siphash::{siphash24_ctr, SipState};
use arrayvec::ArrayVec;
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
}
/// Create an iterator over all RegisterId
#[inline(always)]
pub(crate) fn all() -> impl Iterator<Item = RegisterId> {
(0_u8..(NUM_REGISTERS as u8)).map(RegisterId)
}
}
/// 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
/// we want to optimize for an index() implementation that doesn't branch.
/// This uses a fixed-capacity array of registers in-set, always sorted.
#[derive(Default, Clone, Eq, PartialEq)]
pub(crate) struct RegisterSet(ArrayVec<RegisterId, NUM_REGISTERS>);
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 {
/// Number of registers still contained in this set
#[inline(always)]
pub(crate) fn len(&self) -> usize {
self.0.len()
}
/// Test if a register is contained in the set.
#[inline(always)]
pub(crate) fn contains(&self, id: RegisterId) -> bool {
self.0.contains(&id)
}
/// Build a new RegisterSet from each register for which a predicate
/// function returns `true`.
#[inline(always)]
pub(crate) fn from_filter<P: FnMut(RegisterId) -> bool>(mut predicate: P) -> Self {
let mut result: Self = Default::default();
for r in RegisterId::all() {
if predicate(r) {
result.0.push(r);
}
}
result
}
/// 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, index: usize) -> RegisterId {
self.0[index]
}
}
/// 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]
}
}