urng 1.0.0

Universal Random Number Generator
use wrapn::{wrap, wu64};

use crate::prng::b64::SplitMix64;
use crate::rng::Rng;

/// Xoshiro256++ 64-bit RNG implementation.
///
/// # Example
/// ```
/// use urng::{Rng, Xoshiro256Pp};
///
/// let mut rng = Xoshiro256Pp::new(1);
/// let _ = rng.nextu();
/// ```
#[repr(C, align(64))]
#[derive(Debug, Clone, Copy)]
pub struct Xoshiro256Pp {
    s: [wu64; 4],
}

impl Xoshiro256Pp {
    /// Creates a new `Xoshiro256Pp` instance with the given seed.
    pub const fn new(seed: u64) -> Self {
        let mut seedgen = SplitMix64::new(seed);
        Self {
            s: wrap![
                seedgen.nextu_const(),
                seedgen.nextu_const(),
                seedgen.nextu_const(),
                seedgen.nextu_const(),
            ],
        }
    }
}

impl Rng for Xoshiro256Pp {
    type Word = u64;

    #[inline]
    fn nextu(&mut self) -> Self::Word {
        let s = &mut self.s;
        let res = s[0] + (s[0] + s[3]).rotate_left(23);
        let t = s[1] << 17;

        s[2] ^= s[0];
        s[3] ^= s[1];
        s[1] ^= s[2];
        s[0] ^= s[3];
        s[2] ^= t;
        s[3] = s[3].rotate_left(45);

        *res
    }
}

/// Xoshiro256** 64-bit RNG implementation.
///
/// # Example
/// ```
/// use urng::{Rng, Xoshiro256Ss};
///
/// let mut rng = Xoshiro256Ss::new(1);
/// let _ = rng.nextu();
/// ```
#[repr(C, align(64))]
#[derive(Debug, Clone, Copy)]
pub struct Xoshiro256Ss {
    s: [wu64; 4],
}

impl Xoshiro256Ss {
    /// Creates a new `Xoshiro256Ss` instance with the given seed.
    pub const fn new(seed: u64) -> Self {
        let mut seedgen = SplitMix64::new(seed);
        Self {
            s: wrap![
                seedgen.nextu_const(),
                seedgen.nextu_const(),
                seedgen.nextu_const(),
                seedgen.nextu_const(),
            ],
        }
    }
}

impl Rng for Xoshiro256Ss {
    type Word = u64;

    #[inline]
    fn nextu(&mut self) -> Self::Word {
        let s = &mut self.s;
        let res = (s[1] * 5).rotate_left(7) * 9;
        let t = s[1] << 17;

        s[2] ^= s[0];
        s[3] ^= s[1];
        s[1] ^= s[2];
        s[0] ^= s[3];
        s[2] ^= t;
        s[3] = s[3].rotate_left(45);

        *res
    }
}

#[cfg(feature = "simd")]
pub use simd::*;

#[cfg(feature = "simd")]
pub mod simd {
    #[cfg(target_arch = "x86_64")]
    use std::arch::x86_64::*;

    use crate::prng::b64::SplitMix64;

    /// AVX-512 vectorized xoshiro256++ variant producing 2 `u64` values per call.
    #[cfg(target_arch = "x86_64")]
    #[repr(C, align(64))]
    pub struct Xoshiro256Ssx2 {
        pub(crate) s: __m512i,
    }

    impl Xoshiro256Ssx2 {
        /// Creates a new `Xoshiro256Ssx2` instance.
        #[cfg(target_arch = "x86_64")]
        pub fn new(seed: u64) -> Self {
            let mut seedgen = SplitMix64::new(seed);
            let s = [0u64; 8].map(|_| seedgen.nextu_const());

            unsafe {
                Self {
                    s: _mm512_loadu_si512(s.as_ptr() as *const _),
                }
            }
        }

        /// Generates the next 2 random `u64` values.
        #[cfg(target_arch = "x86_64")]
        #[inline(always)]
        pub fn nextu(&mut self) -> [u64; 2] {
            let s = &mut self.s;
            unsafe {
                // let res = wrap!((s[0] + s[3]).0.rotate_left(23)) + s[0];
                let res = _mm512_add_epi64(
                    _mm512_rol_epi64(
                        _mm512_add_epi64(*s, _mm512_shuffle_epi32(*s, 0b11_10_01_00)),
                        23,
                    ),
                    *s,
                );
                // let t = s[1] << 17;
                let t = _mm512_slli_epi64(_mm512_shuffle_epi32(*s, 0b01_00_11_10), 17);

                // s[2] ^= s[0];
                *s = _mm512_xor_si512(*s, _mm512_shuffle_epi32(*s, 0b10_11_00_01));
                // s[3] ^= s[1];
                *s = _mm512_xor_si512(*s, _mm512_shuffle_epi32(*s, 0b11_10_01_00));
                // s[1] ^= s[2];
                *s = _mm512_xor_si512(*s, _mm512_shuffle_epi32(*s, 0b00_01_10_11));
                // s[0] ^= s[3];
                *s = _mm512_xor_si512(*s, _mm512_shuffle_epi32(*s, 0b01_00_11_10));

                // s[2] ^= t;
                *s = _mm512_xor_si512(*s, t);
                // s[3] = wrap!(s[3].0.rotate_left(45));
                *s = _mm512_rol_epi64(*s, 45);

                // res.0
                let mut out = [0u64; 2];
                _mm512_stream_si512(out.as_mut_ptr() as *mut __m512i, res);
                out
            }
        }
    }
}

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

    crate::safe_test! {
        Xoshiro256Pp,
        Xoshiro256Ss
    }
}