urng 0.12.0

Universal Random Number Generator
Documentation
#[cfg(feature = "simd")]
use std::arch::x86_64::*;

use wrapn::{Wrap, wrap};

use crate::{_internal::impl_seed, rng::Rng32};

/// A SplitMix32 pseudo-random number generator.
///
/// Fast 32-bit finalizer-based PRNG commonly used to seed other generators.
/// Uses a single 32-bit state word advanced by the golden-ratio constant.
///
/// # Examples
///
/// ```
/// use urng::*;
///
/// let mut rng = SplitMix32::new(1);
/// let _ = rng.nextu();
/// ```
#[repr(C)]
pub struct SplitMix32 {
    state: Wrap<u32>,
}

const A: u64 = 0xFF51_AFD7_ED55_8CCD;
const B: u64 = 0xC4CE_B9FE_1A85_EC53;

impl SplitMix32 {
    /// Creates a new `SplitMix32` instance seeded with the given value.
    pub fn new(seed: u32) -> Self {
        Self {
            state: wrap!(seed | 1),
        }
    }
}

impl_seed!(SplitMix32, 32);

impl Rng32 for SplitMix32 {
    #[inline]
    fn nextu(&mut self) -> u32 {
        self.state += wrap!(0x9E3779B9);

        let mut z = self.state.cast::<u64>();
        z = (z ^ (z >> 16)) * A;
        z = (z ^ (z >> 16)) * B;
        (z ^ (z >> 16)).cast::<u32>().value()
    }
}

#[cfg(feature = "simd")]
#[allow(non_upper_case_globals)]
pub const SPLITMIX32x16: usize = 16;
#[cfg(feature = "simd")]
#[allow(non_upper_case_globals)]
pub const SPLITMIX32x16_PAR_CHUNK: usize = 8192;
#[cfg(feature = "simd")]
pub const SPLITMIX32_GAMMA: u32 = 0x9E37_79B9;

/// AVX-512 implementation of SplitMix32 producing 16 values per step.
///
/// # Examples
///
/// ```no_run
/// use urng::rng32::SplitMix32x16;
///
/// unsafe {
///     let mut rng = SplitMix32x16::new(1);
///     let _ = rng.nextu();
/// }
/// ```
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[repr(C, align(64))]
pub struct SplitMix32x16 {
    pub(crate) state: __m512i,
}

#[cfg(all(feature = "simd", target_arch = "x86_64"))]
impl SplitMix32x16 {
    /// Creates a new `SplitMix32x16` instance.
    ///
    /// # Safety
    ///
    /// The caller must ensure the CPU supports the `avx512f` target feature.
    #[target_feature(enable = "avx512f")]
    pub unsafe fn new(seed: u32) -> Self {
        let base = seed | 1;
        let mut init = [0u32; SPLITMIX32x16];
        for (i, v) in init.iter_mut().enumerate() {
            *v = base.wrapping_add(SPLITMIX32_GAMMA.wrapping_mul((i as u32).wrapping_add(1)));
        }
        Self {
            state: unsafe { _mm512_loadu_si512(init.as_ptr() as *const _) },
        }
    }

    /// Computes the SplitMix32 output for 16 lanes at once.
    ///
    /// # Safety
    ///
    /// The caller must ensure the CPU supports the `avx512f` target feature.
    #[target_feature(enable = "avx512f")]
    pub unsafe fn compute(state: __m512i) -> __m512i {
        let c1 = _mm512_set1_epi32(0x85EB_CA6Bu32 as i32);
        let c2 = _mm512_set1_epi32(0xC2B2_AE35u32 as i32);

        let mut z = state;
        z = _mm512_xor_si512(z, _mm512_srli_epi32(z, 16));
        z = _mm512_add_epi32(z, c1);
        z = _mm512_xor_si512(z, _mm512_srli_epi32(z, 13));
        z = _mm512_add_epi32(z, c2);
        _mm512_xor_si512(z, _mm512_srli_epi32(z, 16))
    }

    /// Generates the next 16 random `u32` values.
    ///
    /// # Safety
    ///
    /// The caller must ensure the CPU supports the `avx512f` target feature.
    #[target_feature(enable = "avx512f")]
    pub unsafe fn nextu(&mut self) -> [u32; SPLITMIX32x16] {
        let v = unsafe { Self::compute(self.state) };
        self.state = _mm512_add_epi32(
            self.state,
            _mm512_set1_epi32(SPLITMIX32_GAMMA.wrapping_mul(SPLITMIX32x16 as u32) as i32),
        );
        unsafe { std::mem::transmute(v) }
    }
}

// -- SplitMix32Simd --

/// Opaque handle for the SplitMix32 RNG.
/// Dispatched at runtime to AVX-512 (`SplitMix32x16`) or scalar (`SplitMix32`) implementation.
///
/// # Examples
///
/// ```
/// use urng::rng32::SplitMix32Simd;
///
/// let _ = core::mem::size_of::<SplitMix32Simd>();
/// ```
#[cfg(feature = "simd")]
#[repr(C)]
pub struct SplitMix32Simd([u8; 0]);

#[cfg(test)]
mod tests {
    use super::*;

    crate::safe_test!(SplitMix32);
}