//! 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 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 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 { 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); } }