libafl_bolts: improvements to the rands module, add next_float (#2086)

* rands: use splitmix64 for seeding

Seeding with splitmix64 is a good way to avoid starting with
low-entropy PRNG states, and is explicitly recommended
by the authors of both xoshiro256++ and Romu.

While at it, give the xoshiro256++ PRNG its proper name.

* rands: use fast_bound() to generate number in range

* rands: add top-level choose()

* rands: add Rand::next_float()

* rands: add Rand::coinflip() helper

* libafl: unbreak tests that relied on direct seeding

* rands: add SFC64 PRNG

SFC64 is a well-established and well-understood PRNG designed by
Chris Doty-Humphrey, the author of PractRand. It has been tested
quite a lot over the years, and to date has no known weaknesses.

Compared to xoshiro256++, it is slightly faster and is likely to
be a more future-proof design (xoshiro/xoroshiro family of generators
come with quite long history of [flaws][1] found over the years).

Compared to Romu, it is slightly slower, but guarantees absense
of bias, minimum period of at least 2^64 for any seed, and
non-overlapping streams for different seeds.

[1]: https://tom-kaitchuck.medium.com/designing-a-new-prng-1c4ffd27124d
This commit is contained in:
Gregory Petrosyan 2024-04-23 16:37:28 +03:00 committed by GitHub
parent 5ff709f241
commit e1b8c9b5d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 258 additions and 164 deletions

View File

@ -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

View File

@ -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<S>(
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::<Tokens>() {
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::<Tokens>().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 {

View File

@ -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 {

View File

@ -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<BytesInput> = 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<BytesInput> = InMemoryCorpus::new();
corpus

View File

@ -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!(

View File

@ -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<<CS as UsesState>::State>,
@ -171,7 +171,7 @@ where
.borrow()
.has_metadata::<IsFavoredMetadata>();
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::<TopAccountingMetadata>() {

View File

@ -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<CS, F, M, O> {
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::<IsFavoredMetadata>();
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,

View File

@ -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::<ProbabilityMetadata>().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();

View File

@ -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::<WeightedScheduleMetadata>()?;

View File

@ -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<I>(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<I, E, T>(&mut self, from: I) -> T
/// Convenient variant of [`choose`].
fn choose<I>(&mut self, from: I) -> I::Item
where
I: IntoIterator<Item = T, IntoIter = E>,
E: ExactSizeIterator + Iterator<Item = T>,
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: <https://prng.di.unimi.it/>
#[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<R: 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")]

View File

@ -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())));