diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf3a794..dc74d2a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove impl of `RngCore` for `BlockRng`, making the latter more generic ([#34]) - Add trait `le::Word` ([#34]) - Add fn `BlockRng::reconstruct` and fn `BlockRng::remaining_results` ([#36]) +- Move `le::{Word, next_u64_via_u32}` to new `utils` module; remove `le` ([#38]) +- Replace `le::fill_bytes_via_next` with `utils::fill_bytes_via_next_word` ([#38]) +- Replace `le::next_u32_via_fill` and `le::next_u64_via_fill` with `utils::next_word_via_fill` ([#38]) +- Replace `le::read_u32_into` and `le::read_u64_into` with `utils::read_words` ([#38]) ### Other - Changed repository from [rust-random/rand] to [rust-random/core]. @@ -38,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#34]: https://github.com/rust-random/rand-core/pull/34 [#35]: https://github.com/rust-random/rand-core/pull/35 [#36]: https://github.com/rust-random/rand-core/pull/36 +[#38]: https://github.com/rust-random/rand-core/pull/38 [rust-random/rand]: https://github.com/rust-random/rand [rust-random/core]: https://github.com/rust-random/core diff --git a/src/block.rs b/src/block.rs index 8b502603..db04f5a6 100644 --- a/src/block.rs +++ b/src/block.rs @@ -39,11 +39,7 @@ //! fn from_seed(seed: Self::Seed) -> Self { //! let core = MyRngCore { //! // ... -//! # state: { -//! # let mut buf = [0u32; 8]; -//! # rand_core::le::read_u32_into(&seed, &mut buf); -//! # buf -//! # } +//! # state: rand_core::utils::read_words(&seed), //! }; //! MyRng(BlockRng::new(core)) //! } @@ -85,7 +81,7 @@ //! [`SeedableRng`]: crate::SeedableRng //! [`rand::rngs::ReseedingRng`]: https://docs.rs/rand/latest/rand/rngs/struct.ReseedingRng.html -use crate::le::{Word, fill_via_chunks}; +use crate::utils::Word; use core::fmt; /// A random (block) generator @@ -141,15 +137,14 @@ pub struct BlockRng { } // Custom Debug implementation that does not expose the contents of `results`. -impl fmt::Debug for BlockRng +impl fmt::Debug for BlockRng where - G: Generator + fmt::Debug, + G: Generator + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("BlockRng") .field("core", &self.core) - .field("index", &self.index()) - .finish() + .finish_non_exhaustive() } } @@ -296,11 +291,29 @@ impl> BlockRng { self.core.generate(&mut self.results); index = 0; } - let (consumed_u32, filled_u8) = - fill_via_chunks(&self.results[index..], &mut dest[read_len..]); - self.set_index(index + consumed_u32); - read_len += filled_u8; + let size = core::mem::size_of::(); + let mut chunks = dest[read_len..].chunks_exact_mut(size); + let mut src = self.results[index..].iter(); + + let zipped = chunks.by_ref().zip(src.by_ref()); + let num_chunks = zipped.len(); + zipped.for_each(|(chunk, src)| chunk.copy_from_slice(src.to_le_bytes().as_ref())); + index += num_chunks; + read_len += num_chunks * size; + + if let Some(src) = src.next() { + // We have consumed all full chunks of dest, but not src. + let dest = chunks.into_remainder(); + let n = dest.len(); + if n > 0 { + dest.copy_from_slice(&src.to_le_bytes().as_ref()[..n]); + index += 1; + read_len += n; + } + } + + self.set_index(index); } } } diff --git a/src/le.rs b/src/le.rs deleted file mode 100644 index 9c1730d1..00000000 --- a/src/le.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! # Little-Endian utilities -//! -//! For cross-platform reproducibility, Little-Endian order (least-significant -//! part first) has been chosen as the standard for inter-type conversion. -//! For example, ``next_u64_via_u32`] takes `u32` -//! values `x, y`, then outputs `(y << 32) | x`. -//! -//! Byte-swapping (like the std `to_le` functions) is only needed to convert -//! to/from byte sequences, and since its purpose is reproducibility, -//! non-reproducible sources (e.g. `OsRng`) need not bother with it. -//! -//! ### Implementing [`RngCore`] -//! -//! Usually an implementation of [`RngCore`] will implement one of the three -//! methods over its internal source. The following helpers are provided for -//! the remaining implementations. -//! -//! **`fn next_u32`:** -//! - `self.next_u64() as u32` -//! - `(self.next_u64() >> 32) as u32` -//! - [next_u32_via_fill][](self) -//! -//! **`fn next_u64`:** -//! - [next_u64_via_u32][](self) -//! - [next_u64_via_fill][](self) -//! -//! **`fn fill_bytes`:** -//! - [fill_bytes_via_next][](self, dest) -//! -//! ### Implementing [`SeedableRng`] -//! -//! In many cases, [`SeedableRng::Seed`] must be converted to `[u32]` or -//! `[u64]`. The following helpers are provided: -//! -//! - [`read_u32_into`] -//! - [`read_u64_into`] - -use crate::RngCore; -#[allow(unused)] -use crate::SeedableRng; - -/// Implement `next_u64` via `next_u32`, little-endian order. -pub fn next_u64_via_u32(rng: &mut R) -> u64 { - // Use LE; we explicitly generate one value before the next. - let x = u64::from(rng.next_u32()); - let y = u64::from(rng.next_u32()); - (y << 32) | x -} - -/// Implement `fill_bytes` via `next_u64` and `next_u32`, little-endian order. -/// -/// The fastest way to fill a slice is usually to work as long as possible with -/// integers. That is why this method mostly uses `next_u64`, and only when -/// there are 4 or less bytes remaining at the end of the slice it uses -/// `next_u32` once. -pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { - let mut left = dest; - while left.len() >= 8 { - let (l, r) = { left }.split_at_mut(8); - left = r; - let chunk: [u8; 8] = rng.next_u64().to_le_bytes(); - l.copy_from_slice(&chunk); - } - let n = left.len(); - if n > 4 { - let chunk: [u8; 8] = rng.next_u64().to_le_bytes(); - left.copy_from_slice(&chunk[..n]); - } else if n > 0 { - let chunk: [u8; 4] = rng.next_u32().to_le_bytes(); - left.copy_from_slice(&chunk[..n]); - } -} - -mod word { - pub trait Sealed: Copy { - type Bytes: Sized + AsRef<[u8]>; - - fn to_le_bytes(self) -> Self::Bytes; - - fn from_usize(val: usize) -> Self; - fn into_usize(self) -> usize; - } - - impl Sealed for u32 { - type Bytes = [u8; 4]; - - fn to_le_bytes(self) -> Self::Bytes { - Self::to_le_bytes(self) - } - - fn from_usize(val: usize) -> Self { - val.try_into().unwrap() - } - fn into_usize(self) -> usize { - self.try_into().unwrap() - } - } - - impl Sealed for u64 { - type Bytes = [u8; 8]; - - fn to_le_bytes(self) -> Self::Bytes { - Self::to_le_bytes(self) - } - - fn from_usize(val: usize) -> Self { - val.try_into().unwrap() - } - fn into_usize(self) -> usize { - self.try_into().unwrap() - } - } -} - -/// A marker trait for supported word types -/// -/// This is implemented for: `u32`, `u64`. -pub trait Word: word::Sealed {} -impl Word for W {} - -/// Fill dest from src -/// -/// Returns `(n, byte_len)`. `src[..n]` is consumed, -/// `dest[..byte_len]` is filled. `src[n..]` and `dest[byte_len..]` are left -/// unaltered. -pub(crate) fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { - let size = core::mem::size_of::(); - - // Always use little endian for portability of results. - - let mut dest = dest.chunks_exact_mut(size); - let mut src = src.iter(); - - let zipped = dest.by_ref().zip(src.by_ref()); - let num_chunks = zipped.len(); - zipped.for_each(|(dest, src)| dest.copy_from_slice(src.to_le_bytes().as_ref())); - - let byte_len = num_chunks * size; - if let Some(src) = src.next() { - // We have consumed all full chunks of dest, but not src. - let dest = dest.into_remainder(); - let n = dest.len(); - if n > 0 { - dest.copy_from_slice(&src.to_le_bytes().as_ref()[..n]); - return (num_chunks + 1, byte_len + n); - } - } - (num_chunks, byte_len) -} - -/// Implement `next_u32` via `fill_bytes`, little-endian order. -pub fn next_u32_via_fill(rng: &mut R) -> u32 { - let mut buf = [0; 4]; - rng.fill_bytes(&mut buf); - u32::from_le_bytes(buf) -} - -/// Implement `next_u64` via `fill_bytes`, little-endian order. -pub fn next_u64_via_fill(rng: &mut R) -> u64 { - let mut buf = [0; 8]; - rng.fill_bytes(&mut buf); - u64::from_le_bytes(buf) -} - -/// Fills `dst: &mut [u32]` from `src` -/// -/// Reads use Little-Endian byte order, allowing portable reproduction of `dst` -/// from a byte slice. -/// -/// # Panics -/// -/// If `src` has insufficient length (if `src.len() < 4*dst.len()`). -#[inline] -#[track_caller] -pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { - assert!(src.len() >= 4 * dst.len()); - for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(4)) { - *out = u32::from_le_bytes(chunk.try_into().unwrap()); - } -} - -/// Fills `dst: &mut [u64]` from `src` -/// -/// # Panics -/// -/// If `src` has insufficient length (if `src.len() < 8*dst.len()`). -#[inline] -#[track_caller] -pub fn read_u64_into(src: &[u8], dst: &mut [u64]) { - assert!(src.len() >= 8 * dst.len()); - for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(8)) { - *out = u64::from_le_bytes(chunk.try_into().unwrap()); - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_fill_via_u32_chunks() { - let src_orig = [1u32, 2, 3]; - - let src = src_orig; - let mut dst = [0u8; 11]; - assert_eq!(fill_via_chunks(&src, &mut dst), (3, 11)); - assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 13]; - assert_eq!(fill_via_chunks(&src, &mut dst), (3, 12)); - assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 5]; - assert_eq!(fill_via_chunks(&src, &mut dst), (2, 5)); - assert_eq!(dst, [1, 0, 0, 0, 2]); - } - - #[test] - fn test_fill_via_u64_chunks() { - let src_orig = [1u64, 2]; - - let src = src_orig; - let mut dst = [0u8; 11]; - assert_eq!(fill_via_chunks(&src, &mut dst), (2, 11)); - assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 17]; - assert_eq!(fill_via_chunks(&src, &mut dst), (2, 16)); - assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 5]; - assert_eq!(fill_via_chunks(&src, &mut dst), (1, 5)); - assert_eq!(dst, [1, 0, 0, 0, 0]); - } - - #[test] - fn test_read() { - let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; - - let mut buf = [0u32; 4]; - read_u32_into(&bytes, &mut buf); - assert_eq!(buf[0], 0x04030201); - assert_eq!(buf[3], 0x100F0E0D); - - let mut buf = [0u32; 3]; - read_u32_into(&bytes[1..13], &mut buf); // unaligned - assert_eq!(buf[0], 0x05040302); - assert_eq!(buf[2], 0x0D0C0B0A); - - let mut buf = [0u64; 2]; - read_u64_into(&bytes, &mut buf); - assert_eq!(buf[0], 0x0807060504030201); - assert_eq!(buf[1], 0x100F0E0D0C0B0A09); - - let mut buf = [0u64; 1]; - read_u64_into(&bytes[7..15], &mut buf); // unaligned - assert_eq!(buf[0], 0x0F0E0D0C0B0A0908); - } -} diff --git a/src/lib.rs b/src/lib.rs index c152835a..a7323bc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,8 @@ use core::{fmt, ops::DerefMut}; pub mod block; -pub mod le; +pub mod utils; +mod word; /// Implementation-level interface for RNGs /// @@ -50,7 +51,7 @@ pub mod le; /// /// Typically an RNG will implement only one of the methods available /// in this trait directly, then use the helper functions from the -/// [`le` module](crate::le) to implement the other methods. +/// [`utils`] module to implement the other methods. /// /// Note that implementors of [`RngCore`] also automatically implement /// the [`TryRngCore`] trait with the `Error` associated type being @@ -506,8 +507,7 @@ mod test { type Seed = [u8; 8]; fn from_seed(seed: Self::Seed) -> Self { - let mut x = [0u64; 1]; - le::read_u64_into(&seed, &mut x); + let x: [u64; 1] = utils::read_words(&seed); SeedableNum(x[0]) } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..4fcd294c --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,168 @@ +//! Utilties to aid trait implementations +//! +//! ## Portability +//! +//! For cross-platform reproducibility, Little-Endian order (least-significant +//! part first) has been chosen as the standard for inter-type conversion. +//! For example, [`next_u64_via_u32`] generates two `u32` values `x, y`, +//! then outputs `(y << 32) | x`. +//! +//! Byte-swapping (like the std `to_le` functions) is only needed to convert +//! to/from byte sequences, and since its purpose is reproducibility, +//! non-reproducible sources (e.g. `OsRng`) need not bother with it. +//! +//! ## Implementing [`RngCore`] +//! +//! Usually an implementation of [`RngCore`] will implement one of the three +//! methods over its internal source. The following helpers are provided for +//! the remaining implementations. +//! +//! **`fn next_u32`:** +//! - `self.next_u64() as u32` +//! - `(self.next_u64() >> 32) as u32` +//! - [next_word_via_fill][](self) +//! +//! **`fn next_u64`:** +//! - [next_u64_via_u32][](self) +//! - [next_word_via_fill][](self) +//! +//! **`fn fill_bytes`:** +//! - [fill_bytes_via_next_word][](self, dest) +//! +//! ## Implementing [`SeedableRng`] +//! +//! In many cases, [`SeedableRng::Seed`] must be converted to `[u32; _]` or +//! `[u64; _]`. [`read_words`] may be used for this. +//! +//! ## Example +//! +//! We demonstrate a simple multiplicative congruential generator (MCG), taken +//! from M.E. O'Neill's blog post +//! [Does It Beat the Minimal Standard?](https://www.pcg-random.org/posts/does-it-beat-the-minimal-standard.html). +//! ``` +//! use rand_core::{RngCore, SeedableRng, utils}; +//! +//! pub struct Mcg128(u128); +//! +//! impl SeedableRng for Mcg128 { +//! type Seed = [u8; 16]; +//! +//! #[inline] +//! fn from_seed(seed: Self::Seed) -> Self { +//! // Always use little-endian byte order to ensure portable results +//! Self(u128::from_le_bytes(seed)) +//! } +//! } +//! +//! impl RngCore for Mcg128 { +//! #[inline] +//! fn next_u32(&mut self) -> u32 { +//! (self.next_u64() >> 32) as u32 +//! } +//! +//! #[inline] +//! fn next_u64(&mut self) -> u64 { +//! self.0 = self.0.wrapping_mul(0x0fc94e3bf4e9ab32866458cd56f5e605); +//! (self.0 >> 64) as u64 +//! } +//! +//! #[inline] +//! fn fill_bytes(&mut self, dst: &mut [u8]) { +//! utils::fill_bytes_via_next_word(dst, || self.next_u64()); +//! } +//! } +//! # +//! # let mut rng = Mcg128::seed_from_u64(42); +//! # assert_eq!(rng.next_u32(), 3443086493); +//! # assert_eq!(rng.next_u64(), 3462997187007721903); +//! # let mut buf = [0u8; 5]; +//! # rng.fill_bytes(&mut buf); +//! # assert_eq!(buf, [154, 23, 43, 68, 75]); +//! ``` + +use crate::RngCore; +#[allow(unused)] +use crate::SeedableRng; +pub use crate::word::Word; + +/// Implement `next_u64` via `next_u32`, little-endian order. +#[inline] +pub fn next_u64_via_u32(rng: &mut R) -> u64 { + // Use LE; we explicitly generate one value before the next. + let x = u64::from(rng.next_u32()); + let y = u64::from(rng.next_u32()); + (y << 32) | x +} + +/// Fill `dst` with bytes using `next_word` +/// +/// This may be used to implement [`RngCore::fill_bytes`] over `next_u32` or +/// `next_u64`. Words are used in order of generation. The last word may be +/// partially discarded. +#[inline] +pub fn fill_bytes_via_next_word(dst: &mut [u8], mut next_word: impl FnMut() -> W) { + let mut chunks = dst.chunks_exact_mut(size_of::()); + for chunk in &mut chunks { + let val = next_word(); + chunk.copy_from_slice(val.to_le_bytes().as_ref()); + } + let rem = chunks.into_remainder(); + if !rem.is_empty() { + let val = next_word().to_le_bytes(); + rem.copy_from_slice(&val.as_ref()[..rem.len()]); + } +} + +/// Yield a word using [`RngCore::fill_bytes`] +/// +/// This may be used to implement `next_u32` or `next_u64`. +pub fn next_word_via_fill(rng: &mut R) -> W { + let mut buf: W::Bytes = Default::default(); + rng.fill_bytes(buf.as_mut()); + W::from_le_bytes(buf) +} + +/// Reads an array of words from a byte slice +/// +/// Words are read from `src` in order, using LE conversion from bytes. +/// +/// # Panics +/// +/// Panics if `size_of_val(src) != size_of::<[W; N]>()`. +#[inline(always)] +pub fn read_words(src: &[u8]) -> [W; N] { + assert_eq!(size_of_val(src), size_of::<[W; N]>()); + let mut dst = [W::from_usize(0); N]; + let chunks = src.chunks_exact(size_of::()); + for (out, chunk) in dst.iter_mut().zip(chunks) { + let mut buf: W::Bytes = Default::default(); + buf.as_mut().copy_from_slice(chunk); + *out = W::from_le_bytes(buf); + } + dst +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_read() { + let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + + let buf: [u32; 4] = read_words(&bytes); + assert_eq!(buf[0], 0x04030201); + assert_eq!(buf[3], 0x100F0E0D); + + let buf: [u32; 3] = read_words(&bytes[1..13]); // unaligned + assert_eq!(buf[0], 0x05040302); + assert_eq!(buf[2], 0x0D0C0B0A); + + let buf: [u64; 2] = read_words(&bytes); + assert_eq!(buf[0], 0x0807060504030201); + assert_eq!(buf[1], 0x100F0E0D0C0B0A09); + + let buf: [u64; 1] = read_words(&bytes[7..15]); // unaligned + assert_eq!(buf[0], 0x0F0E0D0C0B0A0908); + } +} diff --git a/src/word.rs b/src/word.rs new file mode 100644 index 00000000..de038c33 --- /dev/null +++ b/src/word.rs @@ -0,0 +1,66 @@ +//! The [`Word`] trait + +/// A marker trait for supported "word" types. +/// +/// This is implemented for: `u32`, `u64`. +pub trait Word: sealed::Sealed {} + +impl Word for u32 {} +impl Word for u64 {} + +mod sealed { + /// Sealed trait implemented for `u32` and `u64`. + pub trait Sealed: Default + Copy + TryFrom + Eq + core::hash::Hash { + type Bytes: Default + Sized + AsRef<[u8]> + AsMut<[u8]>; + + fn from_le_bytes(bytes: Self::Bytes) -> Self; + fn to_le_bytes(self) -> Self::Bytes; + + fn from_usize(val: usize) -> Self; + fn into_usize(self) -> usize; + } + + impl Sealed for u32 { + type Bytes = [u8; 4]; + + #[inline(always)] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + Self::from_le_bytes(bytes) + } + #[inline(always)] + fn to_le_bytes(self) -> Self::Bytes { + Self::to_le_bytes(self) + } + + #[inline(always)] + fn from_usize(val: usize) -> Self { + val.try_into().unwrap() + } + #[inline(always)] + fn into_usize(self) -> usize { + self.try_into().unwrap() + } + } + + impl Sealed for u64 { + type Bytes = [u8; 8]; + + #[inline(always)] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + Self::from_le_bytes(bytes) + } + #[inline(always)] + fn to_le_bytes(self) -> Self::Bytes { + Self::to_le_bytes(self) + } + + #[inline(always)] + fn from_usize(val: usize) -> Self { + val.try_into().unwrap() + } + #[inline(always)] + fn into_usize(self) -> usize { + self.try_into().unwrap() + } + } +}