Skip to main content

nuclei/rt/
rng.rs

1//! The per-thread random numbers behind the runtime's choices: where a task
2//! spawned from outside the workers goes, and which branch of a fair
3//! `select!` is polled first.
4//!
5//! Each thread has its own xorshift generator, seeded from its address and
6//! the clock; nothing depends on its quality but balance. A runtime built
7//! with an [`RngSeed`] seeds the generator of each of its workers, and of
8//! each thread while it is in the runtime's `block_on`, from that seed
9//! instead, so each such thread draws the same numbers run after run.
10
11use core::cell::Cell;
12
13thread_local! {
14    static STATE: Cell<u64> = Cell::new(fresh());
15}
16
17/// A state distinct per thread and per call: the address of a local and
18/// the clock.
19fn fresh() -> u64 {
20    let local = 0u8;
21    let addr = &local as *const u8 as u64;
22    let time = std::time::SystemTime::now()
23        .duration_since(std::time::UNIX_EPOCH)
24        .map_or(0, |d| d.as_nanos() as u64);
25    (addr ^ time.rotate_left(32)) | 1
26}
27
28/// The splitmix64 finalizer: spreads every bit of `z` over the word.
29fn mix(mut z: u64) -> u64 {
30    z = z.wrapping_add(0x9E37_79B9_7F4A_7C15);
31    z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
32    z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
33    z ^ (z >> 31)
34}
35
36/// The next number of this thread's generator.
37fn next() -> u64 {
38    STATE
39        .try_with(|s| {
40            let mut x = s.get();
41            x ^= x << 13;
42            x ^= x >> 7;
43            x ^= x << 17;
44            s.set(x);
45            x
46        })
47        // A thread on its way out has no generator left: any number serves.
48        .unwrap_or_else(|_| fresh())
49}
50
51/// A random number below `n`.
52pub(crate) fn random_below(n: usize) -> usize {
53    // Multiply-shift: unbiased enough for placement, no division.
54    ((u128::from(next()) * n as u128) >> 64) as usize
55}
56
57/// A seed for a runtime's random choices: where a task spawned from outside
58/// its workers goes, and which branch of a fair
59/// [`select!`](crate::select) is polled first.
60///
61/// A runtime built with [`Builder::rng_seed`](crate::runtime::Builder::rng_seed)
62/// seeds each of its workers, and each thread in its `block_on`, from the
63/// seed: run after run, such a thread draws the same numbers, so its
64/// choices repeat as long as what it chooses among is the same. The threads
65/// still interleave as the system runs them.
66///
67/// ```
68/// use nuclei::runtime::{RngSeed, Runtime};
69///
70/// let seed = RngSeed::from_bytes(b"a run to repeat");
71/// assert_eq!(seed, RngSeed::from_bytes(b"a run to repeat"));
72/// let rt = Runtime::builder().rng_seed(seed).build().unwrap();
73/// # drop(rt);
74/// ```
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct RngSeed(u64);
77
78impl RngSeed {
79    /// A seed different each time.
80    pub fn new() -> Self {
81        Self(mix(next()))
82    }
83
84    /// The seed of `bytes`: the same bytes give the same seed on every
85    /// system.
86    pub fn from_bytes(bytes: &[u8]) -> Self {
87        // FNV-1a: fixed, so a seed never depends on the toolchain.
88        let hash = bytes.iter().fold(0xCBF2_9CE4_8422_2325u64, |h, &b| {
89            (h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01B3)
90        });
91        Self(mix(hash))
92    }
93}
94
95impl Default for RngSeed {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101/// This thread's generator set from a seed until the guard drops, then
102/// back to where it was.
103pub(crate) struct Reseeded(Option<u64>);
104
105impl Reseeded {
106    /// Seeds this thread's generator from `seed`, for the runtime's thread
107    /// `thread` (0 for a thread in its `block_on`, a worker's index plus one).
108    pub(crate) fn new(seed: RngSeed, thread: u64) -> Self {
109        let state = mix(seed.0 ^ mix(thread)) | 1;
110        Self(STATE.try_with(|s| s.replace(state)).ok())
111    }
112}
113
114impl Drop for Reseeded {
115    fn drop(&mut self) {
116        if let Some(previous) = self.0 {
117            let _ = STATE.try_with(|s| s.set(previous));
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn a_seed_draws_the_same_numbers_each_time_and_is_undone() {
128        let before: Vec<u64> = {
129            let _seeded = Reseeded::new(RngSeed::from_bytes(b"x"), 3);
130            (0..8).map(|_| next()).collect()
131        };
132        let again: Vec<u64> = {
133            let _seeded = Reseeded::new(RngSeed::from_bytes(b"x"), 3);
134            (0..8).map(|_| next()).collect()
135        };
136        assert_eq!(before, again);
137        let other_thread: Vec<u64> = {
138            let _seeded = Reseeded::new(RngSeed::from_bytes(b"x"), 4);
139            (0..8).map(|_| next()).collect()
140        };
141        assert_ne!(before, other_thread);
142        let other_seed: Vec<u64> = {
143            let _seeded = Reseeded::new(RngSeed::from_bytes(b"y"), 3);
144            (0..8).map(|_| next()).collect()
145        };
146        assert_ne!(before, other_seed);
147        // Undone: the thread's own generator goes on, not the seed's.
148        let after: Vec<u64> = (0..8).map(|_| next()).collect();
149        assert_ne!(before, after);
150    }
151
152    #[test]
153    fn numbers_stay_below_the_bound() {
154        for n in [1, 2, 3, 7, 64, 1000] {
155            assert!((0..1000).all(|_| random_below(n) < n));
156        }
157        assert_ne!(RngSeed::new(), RngSeed::new());
158    }
159}