///|
/// The process-wide seed every hashed container starts from, drawn once from
/// the platform's entropy on first use. A program that builds no hash table
/// draws nothing.
///
/// A per-process seed makes the bucket layout, and with it the iteration order,
/// differ between runs, so a key set chosen to collide in one run does not
/// collide in the next. Pass `hash_seed=0`, or any fixed value, when a run has
/// to be reproducible.
///
/// ```mbt check
/// test {
/// let a = @spec.hash_seed()
/// let b = @spec.hash_seed()
/// @test.assert_eq(a, b)
/// }
/// ```
pub fn hash_seed() -> Int32 {
match seed_state.value {
Some(seed) => seed
None => {
let drawn : Result[Int32, Unit] = Ok(seed_from_entropy()) catch {
_ => Err(())
}
match drawn {
Ok(seed) => {
seed_state.value = Some(seed)
seed_state.random = true
seed
}
Err(_) => {
seed_state.value = Some(0)
seed_state.random = false
0
}
}
}
}
}
///|
/// Whether this platform gave real entropy to `hash_seed`. `false` means the
/// platform has no source — a wasm host without WASI — and every hashed
/// container runs from a seed of zero, which is still correct, only
/// predictable.
///
/// ```mbt check
/// test {
/// let _ = @spec.hash_seed()
/// let random = @spec.hash_seed_is_random()
/// @test.assert_eq(random, @spec.hash_seed_is_random())
/// }
/// ```
pub fn hash_seed_is_random() -> Bool {
let _ = hash_seed()
seed_state.random
}
///|
/// A seed derived from bytes the caller already holds, which is how a service
/// reproduces the layout of its tables across restarts: pass the same bytes and
/// every container lays out the same way.
///
/// ```mbt check
/// test {
/// @test.assert_eq(
/// @spec.hash_seed_from(b"moonstl"),
/// @spec.hash_seed_from(b"moonstl"),
/// )
/// @test.assert_not_eq(
/// @spec.hash_seed_from(b"moonstl"),
/// @spec.hash_seed_from(b"moonst"),
/// )
/// }
/// ```
pub fn hash_seed_from(bytes : BytesView) -> Int32 {
// FNV-1a over the bytes, then the splitmix64 finalizer so that two similar
// inputs land far apart, as a seed has to.
let mut hash : UInt64 = 0xcbf29ce484222325
for i in 0..> 32).to_uint().reinterpret_as_int()
if seed == 0 {
1
} else {
seed
}
}
///|
fn seed_from_entropy() -> Int32 raise {
let bytes = @moonseed.bytes(32, @system.source)
hash_seed_from(bytes[:])
}
///|
/// The finalizer splitmix64 uses, which spreads any input across all 64 bits.
fn mix64(value : UInt64) -> UInt64 {
let mut z = value
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9
z = (z ^ (z >> 27)) * 0x94d049bb133111eb
z ^ (z >> 31)
}
///|
priv struct SeedState {
mut value : Int32?
mut random : Bool
}
///|
let seed_state : SeedState = { value: None, random: false, }