diff --git a/libafl/src/mutators/gramatron.rs b/libafl/src/mutators/gramatron.rs index af0dacffa6..0f24ad53ea 100644 --- a/libafl/src/mutators/gramatron.rs +++ b/libafl/src/mutators/gramatron.rs @@ -4,7 +4,10 @@ use alloc::vec::Vec; use core::cmp::max; use hashbrown::HashMap; -use libafl_bolts::{rands::Rand, Named}; +use libafl_bolts::{ + rands::{choose, Rand}, + Named, +}; use serde::{Deserialize, Serialize}; use crate::{ @@ -117,7 +120,7 @@ where let insert_at = state.rand_mut().below(input.terminals().len() as u64) as usize; - let rand_num = state.rand_mut().next() as usize; + let rand_num = state.rand_mut().next(); let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); @@ -134,7 +137,7 @@ where meta.map.get(&input.terminals()[insert_at].state).map_or( Ok(MutationResult::Skipped), |splice_points| { - let from = splice_points[rand_num % splice_points.len()]; + let from = *choose(splice_points, rand_num); input.terminals_mut().truncate(insert_at); input diff --git a/libafl/src/mutators/grimoire.rs b/libafl/src/mutators/grimoire.rs index eb45693bb4..73f73221de 100644 --- a/libafl/src/mutators/grimoire.rs +++ b/libafl/src/mutators/grimoire.rs @@ -4,7 +4,10 @@ use alloc::vec::Vec; use core::cmp::{max, min}; -use libafl_bolts::{rands::Rand, Named}; +use libafl_bolts::{ + rands::{choose, fast_bound, Rand}, + Named, +}; use crate::{ corpus::Corpus, @@ -17,7 +20,7 @@ use crate::{ const RECURSIVE_REPLACEMENT_DEPTH: [usize; 6] = [2, 4, 8, 16, 32, 64]; const MAX_RECURSIVE_REPLACEMENT_LEN: usize = 64 << 10; -const CHOOSE_SUBINPUT_PROB: u64 = 50; +const CHOOSE_SUBINPUT_PROB: f64 = 0.5; fn extend_with_random_generalized( state: &mut S, @@ -29,10 +32,10 @@ where { let idx = random_corpus_id!(state.corpus(), state.rand_mut()); - if state.rand_mut().below(100) > CHOOSE_SUBINPUT_PROB { - if state.rand_mut().below(100) < 50 { - let rand1 = state.rand_mut().next() as usize; - let rand2 = state.rand_mut().next() as usize; + if state.rand_mut().coinflip(CHOOSE_SUBINPUT_PROB) { + if state.rand_mut().coinflip(0.5) { + let rand1 = state.rand_mut().next(); + let rand2 = state.rand_mut().next(); let other_testcase = state.corpus().get(idx)?.borrow(); if let Some(other) = other_testcase @@ -48,8 +51,8 @@ where { gap_indices.push(i); } - let min_idx = gap_indices[rand1 % gap_indices.len()]; - let max_idx = gap_indices[rand2 % gap_indices.len()]; + let min_idx = *choose(&*gap_indices, rand1); + let max_idx = *choose(&*gap_indices, rand2); let (mut min_idx, max_idx) = (min(min_idx, max_idx), max(min_idx, max_idx)); gap_indices.clear(); @@ -66,11 +69,11 @@ where } } - let rand1 = state.rand_mut().next() as usize; + let rand1 = state.rand_mut().next(); if let Some(meta) = state.metadata_map().get::() { if !meta.tokens().is_empty() { - let tok = &meta.tokens()[rand1 % meta.tokens().len()]; + let tok = choose(meta.tokens(), rand1); if items.last() != Some(&GeneralizedItem::Gap) { items.push(GeneralizedItem::Gap); } @@ -253,8 +256,8 @@ where token_replace = state.rand_mut().below(tokens_len as u64) as usize; } - let stop_at_first = state.rand_mut().below(100) > 50; - let mut rand_idx = state.rand_mut().next() as usize; + let stop_at_first = state.rand_mut().coinflip(0.5); + let rand_idx = state.rand_mut().next(); let meta = state.metadata_map().get::().unwrap(); let token_1 = &meta.tokens()[token_find]; @@ -263,7 +266,7 @@ where let mut mutated = MutationResult::Skipped; let gen = generalised_meta.generalized_mut(); - rand_idx %= gen.len(); + let rand_idx = fast_bound(rand_idx, gen.len() as u64) as usize; 'first: for item in &mut gen[..rand_idx] { if let GeneralizedItem::Bytes(bytes) = item { diff --git a/libafl/src/mutators/mopt_mutator.rs b/libafl/src/mutators/mopt_mutator.rs index 762c971a4c..ad45a6a6ee 100644 --- a/libafl/src/mutators/mopt_mutator.rs +++ b/libafl/src/mutators/mopt_mutator.rs @@ -202,7 +202,7 @@ impl MOpt { let mut total_x_now = 0.0; let mut x_sum = 0.0; for i in 0..self.operator_num { - self.x_now[swarm][i] = (self.rand.below(7000) as f64) * 0.0001 + 0.1; + self.x_now[swarm][i] = 0.7 * self.rand.next_float() + 0.1; total_x_now += self.x_now[swarm][i]; self.v_now[swarm][i] = 0.1; self.l_best[swarm][i] = 0.5; @@ -215,12 +215,8 @@ impl MOpt { for i in 0..self.operator_num { self.v_now[swarm][i] = self.w_now * self.v_now[swarm][i] - + (self.rand.below(1000) as f64) - * 0.001 - * (self.l_best[swarm][i] - self.x_now[swarm][i]) - + (self.rand.below(1000) as f64) - * 0.001 - * (self.g_best[i] - self.x_now[swarm][i]); + + self.rand.next_float() * (self.l_best[swarm][i] - self.x_now[swarm][i]) + + self.rand.next_float() * (self.g_best[i] - self.x_now[swarm][i]); self.x_now[swarm][i] += self.v_now[swarm][i]; self.x_now[swarm][i] = self.x_now[swarm][i].clamp(V_MIN, V_MAX); @@ -282,12 +278,8 @@ impl MOpt { for i in 0..self.operator_num { self.probability_now[swarm][i] = 0.0; self.v_now[swarm][i] = self.w_now * self.v_now[swarm][i] - + (self.rand.below(1000) as f64) - * 0.001 - * (self.l_best[swarm][i] - self.x_now[swarm][i]) - + (self.rand.below(1000) as f64) - * 0.001 - * (self.g_best[i] - self.x_now[swarm][i]); + + self.rand.next_float() * (self.l_best[swarm][i] - self.x_now[swarm][i]) + + self.rand.next_float() * (self.g_best[i] - self.x_now[swarm][i]); self.x_now[swarm][i] += self.v_now[swarm][i]; self.x_now[swarm][i] = self.x_now[swarm][i].clamp(V_MIN, V_MAX); @@ -328,8 +320,8 @@ impl MOpt { let operator_num = self.operator_num; // Fetch a random sele value - let select_prob: f64 = self.probability_now[self.swarm_now][operator_num - 1] - * ((self.rand.below(10000) as f64) * 0.0001); + let select_prob: f64 = + self.probability_now[self.swarm_now][operator_num - 1] * self.rand.next_float(); for i in 0..operator_num { if i == 0 { diff --git a/libafl/src/mutators/scheduled.rs b/libafl/src/mutators/scheduled.rs index 49b3b2be83..ffbb190fb7 100644 --- a/libafl/src/mutators/scheduled.rs +++ b/libafl/src/mutators/scheduled.rs @@ -470,7 +470,7 @@ where #[cfg(test)] mod tests { - use libafl_bolts::rands::{Rand, StdRand, XkcdRand}; + use libafl_bolts::rands::{StdRand, XkcdRand}; use crate::{ corpus::{Corpus, InMemoryCorpus, Testcase}, @@ -486,8 +486,7 @@ mod tests { #[test] fn test_mut_scheduled() { - // With the current impl, seed of 1 will result in a split at pos 2. - let mut rand = XkcdRand::with_seed(5); + let rand = XkcdRand::with_seed(0); let mut corpus: InMemoryCorpus = InMemoryCorpus::new(); corpus .add(Testcase::new(vec![b'a', b'b', b'c'].into())) @@ -510,21 +509,17 @@ mod tests { ) .unwrap(); - rand.set_seed(5); - let mut splice = SpliceMutator::new(); splice.mutate(&mut state, &mut input).unwrap(); log::trace!("{:?}", input.bytes()); // The pre-seeded rand should have spliced at position 2. - // TODO: Maybe have a fixed rand for this purpose? assert_eq!(input.bytes(), &[b'a', b'b', b'f']); } #[test] fn test_havoc() { - // With the current impl, seed of 1 will result in a split at pos 2. let rand = StdRand::with_seed(0x1337); let mut corpus: InMemoryCorpus = InMemoryCorpus::new(); corpus diff --git a/libafl/src/mutators/tuneable.rs b/libafl/src/mutators/tuneable.rs index 9b950b4267..524e007902 100644 --- a/libafl/src/mutators/tuneable.rs +++ b/libafl/src/mutators/tuneable.rs @@ -165,8 +165,7 @@ where // We will sample using the mutation probabilities. // Doing this outside of the original if branch to make the borrow checker happy. #[allow(clippy::cast_precision_loss)] - let coin = state.rand_mut().next() as f32 / u64::MAX as f32; - debug_assert!(coin <= 1.0_f32); + let coin = state.rand_mut().next_float() as f32; let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap(); let power = metadata @@ -204,8 +203,7 @@ where // We will sample using the mutation probabilities. // Doing this outside of the original if branch to make the borrow checker happy. #[allow(clippy::cast_precision_loss)] - let coin = state.rand_mut().next() as f32 / u64::MAX as f32; - debug_assert!(coin <= 1.0_f32); + let coin = state.rand_mut().next_float() as f32; let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap(); debug_assert_eq!( diff --git a/libafl/src/schedulers/accounting.rs b/libafl/src/schedulers/accounting.rs index 53becae593..157394f5b1 100644 --- a/libafl/src/schedulers/accounting.rs +++ b/libafl/src/schedulers/accounting.rs @@ -112,7 +112,7 @@ where CS::State: Debug, { accounting_map: &'a [u32], - skip_non_favored_prob: u64, + skip_non_favored_prob: f64, inner: MinimizerScheduler< CS, LenTimeMulTestcaseScore<::State>, @@ -171,7 +171,7 @@ where .borrow() .has_metadata::(); has - } && state.rand_mut().below(100) < self.skip_non_favored_prob + } && state.rand_mut().coinflip(self.skip_non_favored_prob) { idx = self.inner.base_mut().next(state)?; } @@ -338,7 +338,7 @@ where observer: &O, state: &mut CS::State, base: CS, - skip_non_favored_prob: u64, + skip_non_favored_prob: f64, accounting_map: &'a [u32], ) -> Self { match state.metadata_map().get::() { diff --git a/libafl/src/schedulers/minimizer.rs b/libafl/src/schedulers/minimizer.rs index 2c653b9ee9..c281e4f931 100644 --- a/libafl/src/schedulers/minimizer.rs +++ b/libafl/src/schedulers/minimizer.rs @@ -20,7 +20,7 @@ use crate::{ }; /// Default probability to skip the non-favored values -pub const DEFAULT_SKIP_NON_FAVORED_PROB: u64 = 95; +pub const DEFAULT_SKIP_NON_FAVORED_PROB: f64 = 0.95; /// A testcase metadata saying if a testcase is favored #[derive(Debug, Serialize, Deserialize)] @@ -73,7 +73,7 @@ impl Default for TopRatedsMetadata { #[derive(Debug, Clone)] pub struct MinimizerScheduler { base: CS, - skip_non_favored_prob: u64, + skip_non_favored_prob: f64, remove_metadata: bool, phantom: PhantomData<(F, M, O)>, } @@ -231,7 +231,7 @@ where .borrow() .has_metadata::(); has - } && state.rand_mut().below(100) < self.skip_non_favored_prob + } && state.rand_mut().coinflip(self.skip_non_favored_prob) { idx = self.base.next(state)?; } @@ -406,7 +406,7 @@ where /// and has a non-default probability to skip non-faved [`Testcase`]s using (`skip_non_favored_prob`). /// /// When calling, pass the edges observer which will provided the indexes to minimize over. - pub fn with_skip_prob(_observer: &O, base: CS, skip_non_favored_prob: u64) -> Self { + pub fn with_skip_prob(_observer: &O, base: CS, skip_non_favored_prob: f64) -> Self { require_index_tracking!("MinimizerScheduler", O); Self { base, diff --git a/libafl/src/schedulers/probabilistic_sampling.rs b/libafl/src/schedulers/probabilistic_sampling.rs index 421f9e1ad2..4d528e75c6 100644 --- a/libafl/src/schedulers/probabilistic_sampling.rs +++ b/libafl/src/schedulers/probabilistic_sampling.rs @@ -162,7 +162,7 @@ where "No entries in corpus. This often implies the target is not properly instrumented.", ))) } else { - let rand_prob: f64 = (state.rand_mut().below(100) as f64) / 100.0; + let rand_prob: f64 = state.rand_mut().next_float(); let meta = state.metadata_map().get::().unwrap(); let threshold = meta.total_probability * rand_prob; let mut k: f64 = 0.0; @@ -237,8 +237,8 @@ mod tests { super::ProbabilityMetadata::register(); } - // the first 3 probabilities will be .69, .86, .44 - let rand = StdRand::with_seed(12); + // the first 3 probabilities will be .76, .86, .36 + let rand = StdRand::with_seed(2); let mut scheduler = UniformProbabilitySamplingScheduler::new(); diff --git a/libafl/src/schedulers/weighted.rs b/libafl/src/schedulers/weighted.rs index 4b5ebd6685..c5b58ad818 100644 --- a/libafl/src/schedulers/weighted.rs +++ b/libafl/src/schedulers/weighted.rs @@ -316,8 +316,8 @@ where } else { let s = random_corpus_id!(state.corpus(), state.rand_mut()); - // Choose a random value between 0.000000000 and 1.000000000 - let probability = state.rand_mut().between(0, 1000000000) as f64 / 1000000000_f64; + // Choose a random value between 0.0 and 1.0 + let probability = state.rand_mut().next_float(); let wsmeta = state.metadata_mut::()?; diff --git a/libafl_bolts/src/rands.rs b/libafl_bolts/src/rands.rs index 430a4af0fc..a21d6a9800 100644 --- a/libafl_bolts/src/rands.rs +++ b/libafl_bolts/src/rands.rs @@ -7,14 +7,48 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[cfg(feature = "std")] use crate::current_nanos; -#[cfg(any(feature = "xxh3", feature = "alloc"))] -use crate::hash_std; /// The standard rand implementation for `LibAFL`. /// It is usually the right choice, with very good speed and a reasonable randomness. /// Not cryptographically secure (which is not what you want during fuzzing ;) ) pub type StdRand = RomuDuoJrRand; +/// Choose an item at random from the given iterator, sampling uniformly. +/// +/// Note: the runtime cost is bound by the iterator's [`nth`][`Iterator::nth`] implementation +/// * For `Vec`, slice, array, this is O(1) +/// * For `HashMap`, `HashSet`, this is O(n) +pub fn choose(from: I, rand: u64) -> I::Item +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ + // create iterator + let mut iter = from.into_iter(); + + // make sure there is something to choose from + debug_assert!(iter.len() > 0, "choosing from an empty iterator"); + + // pick a random, valid index + let index = fast_bound(rand, iter.len() as u64) as usize; + + // return the item chosen + iter.nth(index).unwrap() +} + +/// Faster and almost unbiased alternative to `rand % n`. +/// +/// For N-bit bound, probability of getting a biased value is 1/2^(64-N). +/// At least 2^2*(64-N) samples are required to detect this amount of bias. +/// +/// See: [An optimal algorithm for bounded random integers](https://github.com/apple/swift/pull/39143). +#[must_use] +pub fn fast_bound(rand: u64, n: u64) -> u64 { + debug_assert_ne!(n, 0); + let mul = u128::from(rand).wrapping_mul(u128::from(n)); + (mul >> 64) as u64 +} + /// Ways to get random around here. /// Please note that these are not cryptographically secure. /// Or, even if some might be by accident, at least they are not seeded in a cryptographically secure fashion. @@ -25,26 +59,25 @@ pub trait Rand: Debug + Serialize + DeserializeOwned { /// Gets the next 64 bit value fn next(&mut self) -> u64; + /// Gets a value between 0.0 (inclusive) and 1.0 (exclusive) + #[allow(clippy::cast_precision_loss)] + fn next_float(&mut self) -> f64 { + // both 2^53 and 2^-53 can be represented in f64 exactly + const MAX: u64 = 1u64 << 53; + const MAX_DIV: f64 = 1.0 / (MAX as f64); + let u = self.next() & MAX.wrapping_sub(1); + u as f64 * MAX_DIV + } + + /// Returns true with specified probability + fn coinflip(&mut self, success_prob: f64) -> bool { + debug_assert!((0.0..=1.0).contains(&success_prob)); + self.next_float() < success_prob + } + /// Gets a value below the given 64 bit val (exclusive) fn below(&mut self, upper_bound_excl: u64) -> u64 { - if upper_bound_excl <= 1 { - return 0; - } - - /* - Modulo is biased - we don't want our fuzzing to be biased so let's do it - right. See - https://stackoverflow.com/questions/10984974/why-do-people-say-there-is-modulo-bias-when-using-a-random-number-generator - */ - let mut unbiased_rnd: u64; - loop { - unbiased_rnd = self.next(); - if unbiased_rnd < (u64::MAX - (u64::MAX % upper_bound_excl)) { - break; - } - } - - unbiased_rnd % upper_bound_excl + fast_bound(self.next(), upper_bound_excl) } /// Gets a value between the given lower bound (inclusive) and upper bound (inclusive) @@ -53,27 +86,13 @@ pub trait Rand: Debug + Serialize + DeserializeOwned { lower_bound_incl + self.below(upper_bound_incl - lower_bound_incl + 1) } - /// Choose an item at random from the given iterator, sampling uniformly. - /// - /// Note: the runtime cost is bound by the iterator's [`nth`][`Iterator::nth`] implementation - /// * For `Vec`, slice, array, this is O(1) - /// * For `HashMap`, `HashSet`, this is O(n) - fn choose(&mut self, from: I) -> T + /// Convenient variant of [`choose`]. + fn choose(&mut self, from: I) -> I::Item where - I: IntoIterator, - E: ExactSizeIterator + Iterator, + I: IntoIterator, + I::IntoIter: ExactSizeIterator, { - // create iterator - let mut iter = from.into_iter(); - - // make sure there is something to choose from - debug_assert!(iter.len() > 0, "choosing from an empty iterator"); - - // pick a random, valid index - let index = self.below(iter.len() as u64) as usize; - - // return the item chosen - iter.nth(index).unwrap() + choose(from, self.next()) } } @@ -96,13 +115,22 @@ macro_rules! default_rand { }; } +// https://prng.di.unimi.it/splitmix64.c +fn splitmix64(x: &mut u64) -> u64 { + *x = x.wrapping_add(0x9e3779b97f4a7c15); + let mut z = *x; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + z ^ (z >> 31) +} + // Derive Default by calling `new(DEFAULT_SEED)` on each of the following Rand types. -#[cfg(any(feature = "xxh3", feature = "alloc"))] -default_rand!(Xoshiro256StarRand); +default_rand!(Xoshiro256PlusPlusRand); default_rand!(XorShift64Rand); default_rand!(Lehmer64Rand); default_rand!(RomuTrioRand); default_rand!(RomuDuoJrRand); +default_rand!(Sfc64Rand); /// Initialize Rand types from a source of randomness. /// @@ -145,112 +173,108 @@ macro_rules! impl_random { }; } -#[cfg(any(feature = "xxh3", feature = "alloc"))] -impl_random!(Xoshiro256StarRand); +impl_random!(Xoshiro256PlusPlusRand); impl_random!(XorShift64Rand); impl_random!(Lehmer64Rand); impl_random!(RomuTrioRand); impl_random!(RomuDuoJrRand); +impl_random!(Sfc64Rand); -/// XXH3 Based, hopefully speedy, rnd implementation +/// xoshiro256++ PRNG: #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct Xoshiro256StarRand { - rand_seed: [u64; 4], +pub struct Xoshiro256PlusPlusRand { + s: [u64; 4], } -// TODO: re-enable ahash works without alloc -#[cfg(any(feature = "xxh3", feature = "alloc"))] -impl Rand for Xoshiro256StarRand { - #[allow(clippy::unreadable_literal)] - fn set_seed(&mut self, seed: u64) { - self.rand_seed[0] = hash_std(&seed.to_be_bytes()); - self.rand_seed[1] = self.rand_seed[0] ^ 0x1234567890abcdef; - self.rand_seed[2] = self.rand_seed[0] & 0x0123456789abcdef; - self.rand_seed[3] = self.rand_seed[0] | 0x01abcde43f567908; +impl Rand for Xoshiro256PlusPlusRand { + fn set_seed(&mut self, mut seed: u64) { + self.s[0] = splitmix64(&mut seed); + self.s[1] = splitmix64(&mut seed); + self.s[2] = splitmix64(&mut seed); + self.s[3] = splitmix64(&mut seed); } #[inline] fn next(&mut self) -> u64 { - let ret: u64 = self.rand_seed[0] - .wrapping_add(self.rand_seed[3]) + let ret: u64 = self.s[0] + .wrapping_add(self.s[3]) .rotate_left(23) - .wrapping_add(self.rand_seed[0]); - let t: u64 = self.rand_seed[1] << 17; + .wrapping_add(self.s[0]); + let t: u64 = self.s[1] << 17; - self.rand_seed[2] ^= self.rand_seed[0]; - self.rand_seed[3] ^= self.rand_seed[1]; - self.rand_seed[1] ^= self.rand_seed[2]; - self.rand_seed[0] ^= self.rand_seed[3]; + self.s[2] ^= self.s[0]; + self.s[3] ^= self.s[1]; + self.s[1] ^= self.s[2]; + self.s[0] ^= self.s[3]; - self.rand_seed[2] ^= t; + self.s[2] ^= t; - self.rand_seed[3] = self.rand_seed[3].rotate_left(45); + self.s[3] = self.s[3].rotate_left(45); ret } } -#[cfg(any(feature = "xxh3", feature = "alloc"))] -impl Xoshiro256StarRand { - /// Creates a new Xoshiro rand with the given seed +impl Xoshiro256PlusPlusRand { + /// Creates a new xoshiro256++ rand with the given seed #[must_use] pub fn with_seed(seed: u64) -> Self { - let mut rand = Self { rand_seed: [0; 4] }; - rand.set_seed(seed); // TODO: Proper random seed? + let mut rand = Self { s: [0; 4] }; + rand.set_seed(seed); rand } } -/// XXH3 Based, hopefully speedy, rnd implementation +/// Xorshift64 PRNG #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct XorShift64Rand { - rand_seed: u64, + s: u64, } impl Rand for XorShift64Rand { - #[allow(clippy::unreadable_literal)] - fn set_seed(&mut self, seed: u64) { - self.rand_seed = seed ^ 0x1234567890abcdef; + fn set_seed(&mut self, mut seed: u64) { + self.s = splitmix64(&mut seed) | 1; } #[inline] fn next(&mut self) -> u64 { - let mut x = self.rand_seed; + let mut x = self.s; x ^= x << 13; x ^= x >> 7; x ^= x << 17; - self.rand_seed = x; + self.s = x; x } } impl XorShift64Rand { - /// Creates a new Xoshiro rand with the given seed + /// Creates a new xorshift64 rand with the given seed #[must_use] pub fn with_seed(seed: u64) -> Self { - let mut ret: Self = Self { rand_seed: 0 }; - ret.set_seed(seed); // TODO: Proper random seed? + let mut ret: Self = Self { s: 0 }; + ret.set_seed(seed); ret } } -/// XXH3 Based, hopefully speedy, rnd implementation +/// Lehmer64 PRNG #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Lehmer64Rand { - rand_seed: u128, + s: u128, } impl Rand for Lehmer64Rand { - #[allow(clippy::unreadable_literal)] - fn set_seed(&mut self, seed: u64) { - self.rand_seed = u128::from(seed) ^ 0x1234567890abcdef; + fn set_seed(&mut self, mut seed: u64) { + let hi = splitmix64(&mut seed); + let lo = splitmix64(&mut seed) | 1; + self.s = u128::from(hi) << 64 | u128::from(lo); } #[inline] #[allow(clippy::unreadable_literal)] fn next(&mut self) -> u64 { - self.rand_seed *= 0xda942042e4dd58b5; - (self.rand_seed >> 64) as u64 + self.s *= 0xda942042e4dd58b5; + (self.s >> 64) as u64 } } @@ -258,7 +282,7 @@ impl Lehmer64Rand { /// Creates a new Lehmer rand with the given seed #[must_use] pub fn with_seed(seed: u64) -> Self { - let mut ret: Self = Self { rand_seed: 0 }; + let mut ret: Self = Self { s: 0 }; ret.set_seed(seed); ret } @@ -288,10 +312,10 @@ impl RomuTrioRand { } impl Rand for RomuTrioRand { - fn set_seed(&mut self, seed: u64) { - self.x_state = seed ^ 0x12345; - self.y_state = seed ^ 0x6789A; - self.z_state = seed ^ 0xBCDEF; + fn set_seed(&mut self, mut seed: u64) { + self.x_state = splitmix64(&mut seed); + self.y_state = splitmix64(&mut seed); + self.z_state = splitmix64(&mut seed); } #[inline] @@ -328,9 +352,9 @@ impl RomuDuoJrRand { } impl Rand for RomuDuoJrRand { - fn set_seed(&mut self, seed: u64) { - self.x_state = seed ^ 0x12345; - self.y_state = seed ^ 0x6789A; + fn set_seed(&mut self, mut seed: u64) { + self.x_state = splitmix64(&mut seed); + self.y_state = splitmix64(&mut seed); } #[inline] @@ -343,6 +367,53 @@ impl Rand for RomuDuoJrRand { } } +/// [SFC64][1] algorithm by Chris Doty-Humphrey. +/// +/// [1]: https://numpy.org/doc/stable/reference/random/bit_generators/sfc64.html +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct Sfc64Rand { + a: u64, + b: u64, + c: u64, + w: u64, +} + +impl Sfc64Rand { + /// Creates a new [`Sfc64Rand`] with the given seed. + #[must_use] + pub fn with_seed(seed: u64) -> Self { + let mut s = Sfc64Rand { + a: 0, + b: 0, + c: 0, + w: 0, + }; + s.set_seed(seed); + s + } +} + +impl Rand for Sfc64Rand { + fn set_seed(&mut self, seed: u64) { + self.a = seed; + self.b = seed; + self.c = seed; + self.w = 1; + for _ in 0..12 { + self.next(); + } + } + + fn next(&mut self) -> u64 { + let out = self.a.wrapping_add(self.b).wrapping_add(self.w); + self.w = self.w.wrapping_add(1); + self.a = self.b ^ (self.b >> 11); + self.b = self.c.wrapping_add(self.c << 3); + self.c = self.c.rotate_left(24).wrapping_add(out); + out + } +} + /// fake rand, for testing purposes #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] #[allow(clippy::upper_case_acronyms)] @@ -351,8 +422,8 @@ pub struct XkcdRand { } impl Rand for XkcdRand { - fn set_seed(&mut self, val: u64) { - self.val = val; + fn set_seed(&mut self, mut seed: u64) { + self.val = splitmix64(&mut seed); } fn next(&mut self) -> u64 { @@ -363,26 +434,26 @@ impl Rand for XkcdRand { /// A test rng that will return the same value (chose by fair dice roll) for testing. impl XkcdRand { /// Creates a new [`XkcdRand`] with the rand of 4, [chosen by fair dice roll, guaranteed to be random](https://xkcd.com/221/). - /// Will always return this seed. #[must_use] pub fn new() -> Self { - Self::with_seed(4) + Self { val: 4 } } - /// Creates a new [`XkcdRand`] with the given seed. Will always return this seed. + /// Creates a new [`XkcdRand`] with the given seed. #[must_use] pub fn with_seed(seed: u64) -> Self { - Self { val: seed } + let mut rand = XkcdRand { val: 0 }; + rand.set_seed(seed); + rand } } #[cfg(test)] mod tests { - //use xxhash_rust::xxh3::xxh3_64_with_seed; - - #[cfg(any(feature = "xxh3", feature = "alloc"))] - use crate::rands::Xoshiro256StarRand; - use crate::rands::{Rand, RomuDuoJrRand, RomuTrioRand, StdRand, XorShift64Rand}; + use crate::rands::{ + Rand, RomuDuoJrRand, RomuTrioRand, Sfc64Rand, StdRand, XorShift64Rand, + Xoshiro256PlusPlusRand, + }; fn test_single_rand(rand: &mut R) { assert_ne!(rand.next(), rand.next()); @@ -399,8 +470,8 @@ mod tests { test_single_rand(&mut RomuTrioRand::with_seed(0)); test_single_rand(&mut RomuDuoJrRand::with_seed(0)); test_single_rand(&mut XorShift64Rand::with_seed(0)); - #[cfg(any(feature = "xxh3", feature = "alloc"))] - test_single_rand(&mut Xoshiro256StarRand::with_seed(0)); + test_single_rand(&mut Xoshiro256PlusPlusRand::with_seed(0)); + test_single_rand(&mut Sfc64Rand::with_seed(0)); } #[cfg(feature = "std")] @@ -432,6 +503,35 @@ mod tests { log::info!("random value: {}", mutator.rng.next_u32()); } + + #[test] + fn test_sfc64_golden_zig() { + // https://github.com/ziglang/zig/blob/130fb5cb0fb9039e79450c9db58d6590c5bee3b3/lib/std/Random/Sfc64.zig#L73-L99 + let golden: [u64; 16] = [ + 0x3acfa029e3cc6041, + 0xf5b6515bf2ee419c, + 0x1259635894a29b61, + 0xb6ae75395f8ebd6, + 0x225622285ce302e2, + 0x520d28611395cb21, + 0xdb909c818901599d, + 0x8ffd195365216f57, + 0xe8c4ad5e258ac04a, + 0x8f8ef2c89fdb63ca, + 0xf9865b01d98d8e2f, + 0x46555871a65d08ba, + 0x66868677c6298fcd, + 0x2ce15a7e6329f57d, + 0xb2f1833ca91ca79, + 0x4b0890ac9bf453ca, + ]; + + let mut s = Sfc64Rand::with_seed(0); + for v in golden { + let u = s.next(); + assert_eq!(v, u); + } + } } #[cfg(feature = "python")] diff --git a/utils/libafl_benches/benches/rand_speeds.rs b/utils/libafl_benches/benches/rand_speeds.rs index 53f30b855e..378fb09ff8 100644 --- a/utils/libafl_benches/benches/rand_speeds.rs +++ b/utils/libafl_benches/benches/rand_speeds.rs @@ -2,16 +2,19 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use libafl_bolts::rands::{ - Lehmer64Rand, Rand, RomuDuoJrRand, RomuTrioRand, XorShift64Rand, Xoshiro256StarRand, + Lehmer64Rand, Rand, RomuDuoJrRand, RomuTrioRand, Sfc64Rand, XorShift64Rand, + Xoshiro256PlusPlusRand, }; fn criterion_benchmark(c: &mut Criterion) { + let mut sfc64 = Sfc64Rand::with_seed(1); let mut xorshift = XorShift64Rand::with_seed(1); - let mut xoshiro = Xoshiro256StarRand::with_seed(1); + let mut xoshiro = Xoshiro256PlusPlusRand::with_seed(1); let mut romu = RomuDuoJrRand::with_seed(1); let mut lehmer = Lehmer64Rand::with_seed(1); let mut romu_trio = RomuTrioRand::with_seed(1); + c.bench_function("sfc64", |b| b.iter(|| black_box(sfc64.next()))); c.bench_function("xorshift", |b| b.iter(|| black_box(xorshift.next()))); c.bench_function("xoshiro", |b| b.iter(|| black_box(xoshiro.next()))); c.bench_function("romu", |b| b.iter(|| black_box(romu.next())));