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:
parent
5ff709f241
commit
e1b8c9b5d8
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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!(
|
||||
|
@ -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>() {
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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>()?;
|
||||
|
||||
|
@ -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")]
|
||||
|
@ -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())));
|
||||
|
Loading…
x
Reference in New Issue
Block a user