215 lines
5.4 KiB
Rust
215 lines
5.4 KiB
Rust
//! Deterministic primitives shared across the whole framework: stable ids,
|
|
//! a stable content hash, and a deterministic RNG. Everything here is
|
|
//! reproducible from a seed so that replay is bit-exact.
|
|
|
|
use std::fmt;
|
|
|
|
/// Number of independent world domains. The spec mandates >= 8.
|
|
pub const NUM_DOMAINS: usize = 8;
|
|
/// Observed value lanes per domain.
|
|
pub const LANES: usize = 4;
|
|
/// Hidden (unobserved) value lanes per domain. These create the
|
|
/// hidden/observed state divergence the spec requires.
|
|
pub const HIDDEN_LANES: usize = 2;
|
|
|
|
macro_rules! id_type {
|
|
($name:ident, $inner:ty) => {
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct $name(pub $inner);
|
|
impl fmt::Debug for $name {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}({})", stringify!($name), self.0)
|
|
}
|
|
}
|
|
impl fmt::Display for $name {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
id_type!(WorldId, u64);
|
|
id_type!(ProgramId, u64);
|
|
id_type!(ContractId, u64);
|
|
|
|
/// Identifies one of the [`NUM_DOMAINS`] domains.
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct DomainId(pub u8);
|
|
|
|
impl fmt::Debug for DomainId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "DomainId({})", self.0)
|
|
}
|
|
}
|
|
|
|
/// A stable 64-bit content hash. Used for canonical comparison, replay
|
|
/// hashes, and behavior fingerprints. Implemented with FNV-1a so the value
|
|
/// is identical across machines and runs.
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
|
pub struct Hash(pub u64);
|
|
|
|
impl fmt::Debug for Hash {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "Hash({:016x})", self.0)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Hash {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{:016x}", self.0)
|
|
}
|
|
}
|
|
|
|
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
|
|
const FNV_PRIME: u64 = 0x100000001b3;
|
|
|
|
/// Streaming stable hasher (FNV-1a, 64 bit).
|
|
#[derive(Clone, Copy)]
|
|
pub struct Hasher {
|
|
state: u64,
|
|
}
|
|
|
|
impl Default for Hasher {
|
|
fn default() -> Self {
|
|
Hasher { state: FNV_OFFSET }
|
|
}
|
|
}
|
|
|
|
impl Hasher {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn write_u8(&mut self, b: u8) {
|
|
self.state ^= b as u64;
|
|
self.state = self.state.wrapping_mul(FNV_PRIME);
|
|
}
|
|
|
|
#[inline]
|
|
pub fn write_u64(&mut self, v: u64) {
|
|
for i in 0..8 {
|
|
self.write_u8(((v >> (i * 8)) & 0xff) as u8);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn write_i64(&mut self, v: i64) {
|
|
self.write_u64(v as u64);
|
|
}
|
|
|
|
#[inline]
|
|
pub fn write_usize(&mut self, v: usize) {
|
|
self.write_u64(v as u64);
|
|
}
|
|
|
|
#[inline]
|
|
pub fn write_bytes(&mut self, bytes: &[u8]) {
|
|
for &b in bytes {
|
|
self.write_u8(b);
|
|
}
|
|
}
|
|
|
|
/// Mix in a label so structurally different streams that happen to share
|
|
/// numbers do not collide.
|
|
#[inline]
|
|
pub fn write_tag(&mut self, tag: &str) {
|
|
self.write_bytes(tag.as_bytes());
|
|
self.write_u8(0xff);
|
|
}
|
|
|
|
#[inline]
|
|
pub fn finish(&self) -> Hash {
|
|
Hash(self.state)
|
|
}
|
|
}
|
|
|
|
/// Hash a slice of i64 with a tag.
|
|
pub fn hash_i64_slice(tag: &str, vals: &[i64]) -> Hash {
|
|
let mut h = Hasher::new();
|
|
h.write_tag(tag);
|
|
h.write_usize(vals.len());
|
|
for &v in vals {
|
|
h.write_i64(v);
|
|
}
|
|
h.finish()
|
|
}
|
|
|
|
/// Combine several hashes into one (order sensitive).
|
|
pub fn combine_hashes(tag: &str, hashes: &[Hash]) -> Hash {
|
|
let mut h = Hasher::new();
|
|
h.write_tag(tag);
|
|
for hh in hashes {
|
|
h.write_u64(hh.0);
|
|
}
|
|
h.finish()
|
|
}
|
|
|
|
/// Deterministic SplitMix64 RNG. Fully reproducible from a seed.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct Rng {
|
|
state: u64,
|
|
}
|
|
|
|
impl Rng {
|
|
pub fn new(seed: u64) -> Self {
|
|
// Avoid the trivial all-zero state.
|
|
Rng {
|
|
state: seed ^ 0x9e3779b97f4a7c15,
|
|
}
|
|
}
|
|
|
|
/// Derive a sub-stream from a seed and a label, so independent concerns
|
|
/// never accidentally share a stream.
|
|
pub fn derive(seed: u64, tag: &str) -> Self {
|
|
let mut h = Hasher::new();
|
|
h.write_tag(tag);
|
|
h.write_u64(seed);
|
|
Rng::new(h.finish().0)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn next_u64(&mut self) -> u64 {
|
|
self.state = self.state.wrapping_add(0x9e3779b97f4a7c15);
|
|
let mut z = self.state;
|
|
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
|
|
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
|
|
z ^ (z >> 31)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn next_i64(&mut self) -> i64 {
|
|
self.next_u64() as i64
|
|
}
|
|
|
|
/// Uniform-ish integer in `[0, n)`.
|
|
#[inline]
|
|
pub fn below(&mut self, n: usize) -> usize {
|
|
if n == 0 {
|
|
return 0;
|
|
}
|
|
(self.next_u64() % (n as u64)) as usize
|
|
}
|
|
|
|
/// Integer in `[lo, hi]` inclusive.
|
|
#[inline]
|
|
pub fn range_i64(&mut self, lo: i64, hi: i64) -> i64 {
|
|
if hi <= lo {
|
|
return lo;
|
|
}
|
|
let span = (hi - lo) as u64 + 1;
|
|
lo + (self.next_u64() % span) as i64
|
|
}
|
|
|
|
#[inline]
|
|
pub fn chance(&mut self, p: f64) -> bool {
|
|
(self.next_u64() as f64 / u64::MAX as f64) < p
|
|
}
|
|
|
|
#[inline]
|
|
pub fn next_bool(&mut self) -> bool {
|
|
self.next_u64() & 1 == 1
|
|
}
|
|
}
|