Skip to content

Commit

Permalink
Merge pull request #325 from pitdicker/isaac_blockrng
Browse files Browse the repository at this point in the history
Implement `BlockRngCore` ISAAC and ISAAC-64
  • Loading branch information
pitdicker authored Apr 4, 2018
2 parents 31087b3 + ec6f5dd commit 73ea10f
Show file tree
Hide file tree
Showing 18 changed files with 635 additions and 448 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ matrix:
install:
script:
- cargo test --all --tests --no-default-features
- cargo test --features serde-1,log
- cargo test --features serde1,log
- rust: stable
os: osx
install:
script:
- cargo test --all --tests --no-default-features
- cargo test --features serde-1,log
- cargo test --features serde1,log
- rust: beta
install:
script:
- cargo test --all --tests --no-default-features
- cargo test --tests --no-default-features --features=serde-1
- cargo test --tests --no-default-features --features=serde1
- rust: nightly
install:
before_script:
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
script:
- cargo test --all --tests --no-default-features --features=alloc
- cargo test --all --features=alloc
- cargo test --features serde-1,log,nightly
- cargo test --features serde1,log,nightly
- cargo test --benches
- cargo doc --no-deps --all --all-features
- cargo --list | egrep "^\s*deadlinks$" -q || cargo install cargo-deadlinks
Expand Down
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You may also find the [Update Guide](UPDATING.md) useful.
- Create a seperate `rand_core` crate. (#288)
- Deprecate `rand_derive`. (#256)
- Add `log` feature. Logging is now available in `JitterRng`, `OsRng`, `EntropyRng` and `ReseedingRng`. (#246)
- Add `serde-1` feature for some PRNGs. (#189)
- Add `serde1` feature for some PRNGs. (#189)
- `stdweb` feature for `OsRng` support on WASM via stdweb. (#272, #336)

### `Rng` trait
Expand Down Expand Up @@ -55,7 +55,7 @@ You may also find the [Update Guide](UPDATING.md) useful.
- Change `thread_rng` reseeding threshold to 32 MiB. (#277)
- PRNGs no longer implement `Copy`. (#209)
- `Debug` implementations no longer show internals. (#209)
- Implement serialisation for `XorShiftRng`, `IsaacRng` and `Isaac64Rng` under the `serde-1` feature. (#189)
- Implement serialization for `XorShiftRng`, `IsaacRng` and `Isaac64Rng` under the `serde1` feature. (#189)
- Implement `BlockRngCore` for `ChaChaCore` and `Hc128Core`. (#281)
- All PRNGs are now portable across big- and little-endian architectures. (#209)
- `Isaac64Rng::next_u32` no longer throws away half the results. (#209)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ nightly = ["i128_support"] # enables all features requiring nightly rust
std = ["rand_core/std", "alloc", "libc", "winapi", "cloudabi", "fuchsia-zircon"]
alloc = ["rand_core/alloc"] # enables Vec and Box support (without std)
i128_support = [] # enables i128 and u128 support
serde-1 = ["serde", "serde_derive"] # enables serialisation for PRNGs
serde1 = ["serde", "serde_derive", "rand_core/serde1"] # enables serialization for PRNGs

[workspace]
members = ["rand_core"]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ optional features are available:
- `i128_support` enables support for generating `u128` and `i128` values
- `log` enables some logging via the `log` crate
- `nightly` enables all unstable features (`i128_support`)
- `serde-1` enables serialisation for some types, via Serde version 1
- `serde1` enables serialization for some types, via Serde version 1
- `stdweb` enables support for `OsRng` on WASM via stdweb.
- `std` enabled by default; by setting "default-features = false" `no_std`
mode is activated; this removes features depending on `std` functionality:
Expand Down Expand Up @@ -132,7 +132,7 @@ cargo test --tests --no-default-features
cargo test --tests --no-default-features --features alloc
# Test log and serde support
cargo test --features serde-1,log
cargo test --features serde1,log
# Test 128-bit support (requires nightly)
cargo test --all --features nightly
Expand Down
2 changes: 1 addition & 1 deletion UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ deprecation of `Rand`.
Several new Cargo feature flags have been added:

- `alloc`, used without `std`, allows use of `Box` and `Vec`
- `serde-1` adds serialisation support to some PRNGs
- `serde1` adds serialization support to some PRNGs
- `log` adds logging in a few places (primarily to `OsRng` and `JitterRng`)

### `Rng` and friends (core traits)
Expand Down
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ build: false
test_script:
- cargo test --benches
- cargo test --all
- cargo test --features serde-1,log,nightly
- cargo test --features serde1,log,nightly
- cargo test --all --tests --no-default-features --features=alloc
- cargo test --tests --no-default-features --features=serde-1
- cargo test --tests --no-default-features --features=serde1
5 changes: 5 additions & 0 deletions rand_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ appveyor = { repository = "alexcrichton/rand" }
# default = ["std"]
std = ["alloc"] # use std library; should be default but for above bug
alloc = [] # enables Vec and Box support without std
serde1 = ["serde", "serde_derive"] # enables serde for BlockRng wrapper

[dependencies]
serde = { version = "1", optional = true }
serde_derive = { version = "1", optional = true }
228 changes: 216 additions & 12 deletions rand_core/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use core::cmp::min;
use core::mem::size_of;
use {RngCore, BlockRngCore, CryptoRng, SeedableRng, Error};

#[cfg(feature="serde1")] use serde::{Serialize, Deserialize};

/// Implement `next_u64` via `next_u32`, little-endian order.
pub fn next_u64_via_u32<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
// Use LE; we explicitly generate one value before the next.
Expand Down Expand Up @@ -184,10 +186,14 @@ pub fn next_u64_via_fill<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
/// [`RngCore`]: ../RngCore.t.html
/// [`SeedableRng`]: ../SeedableRng.t.html
#[derive(Clone)]
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
pub struct BlockRng<R: BlockRngCore + ?Sized> {
pub results: R::Results,
pub index: usize,
pub core: R,
#[cfg_attr(feature="serde1", serde(bound(
serialize = "R::Results: Serialize",
deserialize = "R::Results: Deserialize<'de>")))]
results: R::Results,
index: usize,
core: R,
}

// Custom Debug implementation that does not expose the contents of `results`.
Expand All @@ -201,6 +207,35 @@ impl<R: BlockRngCore + fmt::Debug> fmt::Debug for BlockRng<R> {
}
}

impl<R: BlockRngCore> BlockRng<R> {
/// Create a new `BlockRng` from an existing RNG implementing
/// `BlockRngCore`. Results will be generated on first use.
pub fn new(core: R) -> BlockRng<R>{
let results_empty = R::Results::default();
BlockRng {
core,
index: results_empty.as_ref().len(),
results: results_empty,
}
}

/// Return a reference the wrapped `BlockRngCore`.
pub fn inner(&self) -> &R {
&self.core
}

/// Return a mutable reference the wrapped `BlockRngCore`.
pub fn inner_mut(&mut self) -> &mut R {
&mut self.core
}

// Reset the number of available results.
// This will force a new set of results to be generated on next use.
pub fn reset(&mut self) {
self.index = self.results.as_ref().len();
}
}

impl<R: BlockRngCore<Item=u32>> RngCore for BlockRng<R>
where <R as BlockRngCore>::Results: AsRef<[u32]>
{
Expand Down Expand Up @@ -317,21 +352,190 @@ impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng<R> {
type Seed = R::Seed;

fn from_seed(seed: Self::Seed) -> Self {
Self::new(R::from_seed(seed))
}

fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
Ok(Self::new(R::from_rng(rng)?))
}
}



/// Wrapper around PRNGs that implement [`BlockRngCore`] to keep a results
/// buffer and offer the methods from [`RngCore`].
///
/// This is similar to [`BlockRng`], but specialized for algorithms that operate
/// on `u64` values.
///
/// [`BlockRngCore`]: ../BlockRngCore.t.html
/// [`RngCore`]: ../RngCore.t.html
/// [`BlockRng`]: struct.BlockRng.html
#[derive(Clone)]
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
pub struct BlockRng64<R: BlockRngCore + ?Sized> {
#[cfg_attr(feature="serde1", serde(bound(
serialize = "R::Results: Serialize",
deserialize = "R::Results: Deserialize<'de>")))]
results: R::Results,
index: usize,
half_used: bool, // true if only half of the previous result is used
core: R,
}

// Custom Debug implementation that does not expose the contents of `results`.
impl<R: BlockRngCore + fmt::Debug> fmt::Debug for BlockRng64<R> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("BlockRng64")
.field("core", &self.core)
.field("result_len", &self.results.as_ref().len())
.field("index", &self.index)
.field("half_used", &self.half_used)
.finish()
}
}

impl<R: BlockRngCore> BlockRng64<R> {
/// Create a new `BlockRng` from an existing RNG implementing
/// `BlockRngCore`. Results will be generated on first use.
pub fn new(core: R) -> BlockRng64<R>{
let results_empty = R::Results::default();
Self {
core: R::from_seed(seed),
index: results_empty.as_ref().len(), // generate on first use
BlockRng64 {
core,
index: results_empty.as_ref().len(),
half_used: false,
results: results_empty,
}
}

/// Return a mutable reference the wrapped `BlockRngCore`.
pub fn inner(&mut self) -> &mut R {
&mut self.core
}

// Reset the number of available results.
// This will force a new set of results to be generated on next use.
pub fn reset(&mut self) {
self.index = self.results.as_ref().len();
}
}

impl<R: BlockRngCore<Item=u64>> RngCore for BlockRng64<R>
where <R as BlockRngCore>::Results: AsRef<[u64]>
{
#[inline(always)]
fn next_u32(&mut self) -> u32 {
let mut index = self.index * 2 - self.half_used as usize;
if index >= self.results.as_ref().len() * 2 {
self.core.generate(&mut self.results);
self.index = 0;
// `self.half_used` is by definition `false`
self.half_used = false;
index = 0;
}

self.half_used = !self.half_used;
self.index += self.half_used as usize;

// Index as if this is a u32 slice.
unsafe {
let results =
&*(self.results.as_ref() as *const [u64] as *const [u32]);
if cfg!(target_endian = "little") {
*results.get_unchecked(index)
} else {
*results.get_unchecked(index ^ 1)
}
}
}

#[inline(always)]
fn next_u64(&mut self) -> u64 {
if self.index >= self.results.as_ref().len() {
self.core.generate(&mut self.results);
self.index = 0;
}

let value = self.results.as_ref()[self.index];
self.index += 1;
self.half_used = false;
value
}

// As an optimization we try to write directly into the output buffer.
// This is only enabled for little-endian platforms where unaligned writes
// are known to be safe and fast.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut filled = 0;
self.half_used = false;

// Continue filling from the current set of results
if self.index < self.results.as_ref().len() {
let (consumed_u64, filled_u8) =
fill_via_u64_chunks(&self.results.as_ref()[self.index..],
dest);

self.index += consumed_u64;
filled += filled_u8;
}

let len_remainder =
(dest.len() - filled) % (self.results.as_ref().len() * 8);
let end_direct = dest.len() - len_remainder;

while filled < end_direct {
let dest_u64: &mut R::Results = unsafe {
::core::mem::transmute(dest[filled..].as_mut_ptr())
};
self.core.generate(dest_u64);
filled += self.results.as_ref().len() * 8;
}
self.index = self.results.as_ref().len();

if len_remainder > 0 {
self.core.generate(&mut self.results);
let (consumed_u64, _) =
fill_via_u64_chunks(&mut self.results.as_ref(),
&mut dest[filled..]);

self.index = consumed_u64;
}
}

#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut read_len = 0;
self.half_used = false;
while read_len < dest.len() {
if self.index as usize >= self.results.as_ref().len() {
self.core.generate(&mut self.results);
self.index = 0;
}

let (consumed_u64, filled_u8) =
fill_via_u64_chunks(&self.results.as_ref()[self.index as usize..],
&mut dest[read_len..]);

self.index += consumed_u64;
read_len += filled_u8;
}
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
Ok(self.fill_bytes(dest))
}
}

impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng64<R> {
type Seed = R::Seed;

fn from_seed(seed: Self::Seed) -> Self {
Self::new(R::from_seed(seed))
}

fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
let results_empty = R::Results::default();
Ok(Self {
core: R::from_rng(rng)?,
index: results_empty.as_ref().len(), // generate on first use
results: results_empty,
})
Ok(Self::new(R::from_rng(rng)?))
}
}

Expand Down
2 changes: 2 additions & 0 deletions rand_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@

#[cfg(feature="std")] extern crate core;
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;
#[cfg(feature="serde1")] extern crate serde;
#[cfg(feature="serde1")] #[macro_use] extern crate serde_derive;


use core::default::Default;
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@
#[cfg(feature="std")] extern crate std as core;
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;

#[cfg(test)] #[cfg(feature="serde-1")] extern crate bincode;
#[cfg(feature="serde-1")] extern crate serde;
#[cfg(feature="serde-1")] #[macro_use] extern crate serde_derive;
#[cfg(test)] #[cfg(feature="serde1")] extern crate bincode;
#[cfg(feature="serde1")] extern crate serde;
#[cfg(feature="serde1")] #[macro_use] extern crate serde_derive;

#[cfg(all(target_arch = "wasm32", feature = "stdweb"))]
#[macro_use]
Expand Down
Loading

0 comments on commit 73ea10f

Please sign in to comment.