Skip to content

Commit 73ea10f

Browse files
authored
Merge pull request #325 from pitdicker/isaac_blockrng
Implement `BlockRngCore` ISAAC and ISAAC-64
2 parents 31087b3 + ec6f5dd commit 73ea10f

18 files changed

+635
-448
lines changed

.travis.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ matrix:
1212
install:
1313
script:
1414
- cargo test --all --tests --no-default-features
15-
- cargo test --features serde-1,log
15+
- cargo test --features serde1,log
1616
- rust: stable
1717
os: osx
1818
install:
1919
script:
2020
- cargo test --all --tests --no-default-features
21-
- cargo test --features serde-1,log
21+
- cargo test --features serde1,log
2222
- rust: beta
2323
install:
2424
script:
2525
- cargo test --all --tests --no-default-features
26-
- cargo test --tests --no-default-features --features=serde-1
26+
- cargo test --tests --no-default-features --features=serde1
2727
- rust: nightly
2828
install:
2929
before_script:
3030
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
3131
script:
3232
- cargo test --all --tests --no-default-features --features=alloc
3333
- cargo test --all --features=alloc
34-
- cargo test --features serde-1,log,nightly
34+
- cargo test --features serde1,log,nightly
3535
- cargo test --benches
3636
- cargo doc --no-deps --all --all-features
3737
- cargo --list | egrep "^\s*deadlinks$" -q || cargo install cargo-deadlinks

CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ You may also find the [Update Guide](UPDATING.md) useful.
1616
- Create a seperate `rand_core` crate. (#288)
1717
- Deprecate `rand_derive`. (#256)
1818
- Add `log` feature. Logging is now available in `JitterRng`, `OsRng`, `EntropyRng` and `ReseedingRng`. (#246)
19-
- Add `serde-1` feature for some PRNGs. (#189)
19+
- Add `serde1` feature for some PRNGs. (#189)
2020
- `stdweb` feature for `OsRng` support on WASM via stdweb. (#272, #336)
2121

2222
### `Rng` trait
@@ -55,7 +55,7 @@ You may also find the [Update Guide](UPDATING.md) useful.
5555
- Change `thread_rng` reseeding threshold to 32 MiB. (#277)
5656
- PRNGs no longer implement `Copy`. (#209)
5757
- `Debug` implementations no longer show internals. (#209)
58-
- Implement serialisation for `XorShiftRng`, `IsaacRng` and `Isaac64Rng` under the `serde-1` feature. (#189)
58+
- Implement serialization for `XorShiftRng`, `IsaacRng` and `Isaac64Rng` under the `serde1` feature. (#189)
5959
- Implement `BlockRngCore` for `ChaChaCore` and `Hc128Core`. (#281)
6060
- All PRNGs are now portable across big- and little-endian architectures. (#209)
6161
- `Isaac64Rng::next_u32` no longer throws away half the results. (#209)

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ nightly = ["i128_support"] # enables all features requiring nightly rust
2323
std = ["rand_core/std", "alloc", "libc", "winapi", "cloudabi", "fuchsia-zircon"]
2424
alloc = ["rand_core/alloc"] # enables Vec and Box support (without std)
2525
i128_support = [] # enables i128 and u128 support
26-
serde-1 = ["serde", "serde_derive"] # enables serialisation for PRNGs
26+
serde1 = ["serde", "serde_derive", "rand_core/serde1"] # enables serialization for PRNGs
2727

2828
[workspace]
2929
members = ["rand_core"]

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ optional features are available:
103103
- `i128_support` enables support for generating `u128` and `i128` values
104104
- `log` enables some logging via the `log` crate
105105
- `nightly` enables all unstable features (`i128_support`)
106-
- `serde-1` enables serialisation for some types, via Serde version 1
106+
- `serde1` enables serialization for some types, via Serde version 1
107107
- `stdweb` enables support for `OsRng` on WASM via stdweb.
108108
- `std` enabled by default; by setting "default-features = false" `no_std`
109109
mode is activated; this removes features depending on `std` functionality:
@@ -132,7 +132,7 @@ cargo test --tests --no-default-features
132132
cargo test --tests --no-default-features --features alloc
133133
134134
# Test log and serde support
135-
cargo test --features serde-1,log
135+
cargo test --features serde1,log
136136
137137
# Test 128-bit support (requires nightly)
138138
cargo test --all --features nightly

UPDATING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ deprecation of `Rand`.
2727
Several new Cargo feature flags have been added:
2828

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

3333
### `Rng` and friends (core traits)

appveyor.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ build: false
3434
test_script:
3535
- cargo test --benches
3636
- cargo test --all
37-
- cargo test --features serde-1,log,nightly
37+
- cargo test --features serde1,log,nightly
3838
- cargo test --all --tests --no-default-features --features=alloc
39-
- cargo test --tests --no-default-features --features=serde-1
39+
- cargo test --tests --no-default-features --features=serde1

rand_core/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,8 @@ appveyor = { repository = "alexcrichton/rand" }
2222
# default = ["std"]
2323
std = ["alloc"] # use std library; should be default but for above bug
2424
alloc = [] # enables Vec and Box support without std
25+
serde1 = ["serde", "serde_derive"] # enables serde for BlockRng wrapper
26+
27+
[dependencies]
28+
serde = { version = "1", optional = true }
29+
serde_derive = { version = "1", optional = true }

rand_core/src/impls.rs

+216-12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ use core::cmp::min;
2727
use core::mem::size_of;
2828
use {RngCore, BlockRngCore, CryptoRng, SeedableRng, Error};
2929

30+
#[cfg(feature="serde1")] use serde::{Serialize, Deserialize};
31+
3032
/// Implement `next_u64` via `next_u32`, little-endian order.
3133
pub fn next_u64_via_u32<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
3234
// Use LE; we explicitly generate one value before the next.
@@ -184,10 +186,14 @@ pub fn next_u64_via_fill<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
184186
/// [`RngCore`]: ../RngCore.t.html
185187
/// [`SeedableRng`]: ../SeedableRng.t.html
186188
#[derive(Clone)]
189+
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
187190
pub struct BlockRng<R: BlockRngCore + ?Sized> {
188-
pub results: R::Results,
189-
pub index: usize,
190-
pub core: R,
191+
#[cfg_attr(feature="serde1", serde(bound(
192+
serialize = "R::Results: Serialize",
193+
deserialize = "R::Results: Deserialize<'de>")))]
194+
results: R::Results,
195+
index: usize,
196+
core: R,
191197
}
192198

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

210+
impl<R: BlockRngCore> BlockRng<R> {
211+
/// Create a new `BlockRng` from an existing RNG implementing
212+
/// `BlockRngCore`. Results will be generated on first use.
213+
pub fn new(core: R) -> BlockRng<R>{
214+
let results_empty = R::Results::default();
215+
BlockRng {
216+
core,
217+
index: results_empty.as_ref().len(),
218+
results: results_empty,
219+
}
220+
}
221+
222+
/// Return a reference the wrapped `BlockRngCore`.
223+
pub fn inner(&self) -> &R {
224+
&self.core
225+
}
226+
227+
/// Return a mutable reference the wrapped `BlockRngCore`.
228+
pub fn inner_mut(&mut self) -> &mut R {
229+
&mut self.core
230+
}
231+
232+
// Reset the number of available results.
233+
// This will force a new set of results to be generated on next use.
234+
pub fn reset(&mut self) {
235+
self.index = self.results.as_ref().len();
236+
}
237+
}
238+
204239
impl<R: BlockRngCore<Item=u32>> RngCore for BlockRng<R>
205240
where <R as BlockRngCore>::Results: AsRef<[u32]>
206241
{
@@ -317,21 +352,190 @@ impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng<R> {
317352
type Seed = R::Seed;
318353

319354
fn from_seed(seed: Self::Seed) -> Self {
355+
Self::new(R::from_seed(seed))
356+
}
357+
358+
fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
359+
Ok(Self::new(R::from_rng(rng)?))
360+
}
361+
}
362+
363+
364+
365+
/// Wrapper around PRNGs that implement [`BlockRngCore`] to keep a results
366+
/// buffer and offer the methods from [`RngCore`].
367+
///
368+
/// This is similar to [`BlockRng`], but specialized for algorithms that operate
369+
/// on `u64` values.
370+
///
371+
/// [`BlockRngCore`]: ../BlockRngCore.t.html
372+
/// [`RngCore`]: ../RngCore.t.html
373+
/// [`BlockRng`]: struct.BlockRng.html
374+
#[derive(Clone)]
375+
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
376+
pub struct BlockRng64<R: BlockRngCore + ?Sized> {
377+
#[cfg_attr(feature="serde1", serde(bound(
378+
serialize = "R::Results: Serialize",
379+
deserialize = "R::Results: Deserialize<'de>")))]
380+
results: R::Results,
381+
index: usize,
382+
half_used: bool, // true if only half of the previous result is used
383+
core: R,
384+
}
385+
386+
// Custom Debug implementation that does not expose the contents of `results`.
387+
impl<R: BlockRngCore + fmt::Debug> fmt::Debug for BlockRng64<R> {
388+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
389+
fmt.debug_struct("BlockRng64")
390+
.field("core", &self.core)
391+
.field("result_len", &self.results.as_ref().len())
392+
.field("index", &self.index)
393+
.field("half_used", &self.half_used)
394+
.finish()
395+
}
396+
}
397+
398+
impl<R: BlockRngCore> BlockRng64<R> {
399+
/// Create a new `BlockRng` from an existing RNG implementing
400+
/// `BlockRngCore`. Results will be generated on first use.
401+
pub fn new(core: R) -> BlockRng64<R>{
320402
let results_empty = R::Results::default();
321-
Self {
322-
core: R::from_seed(seed),
323-
index: results_empty.as_ref().len(), // generate on first use
403+
BlockRng64 {
404+
core,
405+
index: results_empty.as_ref().len(),
406+
half_used: false,
324407
results: results_empty,
325408
}
326409
}
327410

411+
/// Return a mutable reference the wrapped `BlockRngCore`.
412+
pub fn inner(&mut self) -> &mut R {
413+
&mut self.core
414+
}
415+
416+
// Reset the number of available results.
417+
// This will force a new set of results to be generated on next use.
418+
pub fn reset(&mut self) {
419+
self.index = self.results.as_ref().len();
420+
}
421+
}
422+
423+
impl<R: BlockRngCore<Item=u64>> RngCore for BlockRng64<R>
424+
where <R as BlockRngCore>::Results: AsRef<[u64]>
425+
{
426+
#[inline(always)]
427+
fn next_u32(&mut self) -> u32 {
428+
let mut index = self.index * 2 - self.half_used as usize;
429+
if index >= self.results.as_ref().len() * 2 {
430+
self.core.generate(&mut self.results);
431+
self.index = 0;
432+
// `self.half_used` is by definition `false`
433+
self.half_used = false;
434+
index = 0;
435+
}
436+
437+
self.half_used = !self.half_used;
438+
self.index += self.half_used as usize;
439+
440+
// Index as if this is a u32 slice.
441+
unsafe {
442+
let results =
443+
&*(self.results.as_ref() as *const [u64] as *const [u32]);
444+
if cfg!(target_endian = "little") {
445+
*results.get_unchecked(index)
446+
} else {
447+
*results.get_unchecked(index ^ 1)
448+
}
449+
}
450+
}
451+
452+
#[inline(always)]
453+
fn next_u64(&mut self) -> u64 {
454+
if self.index >= self.results.as_ref().len() {
455+
self.core.generate(&mut self.results);
456+
self.index = 0;
457+
}
458+
459+
let value = self.results.as_ref()[self.index];
460+
self.index += 1;
461+
self.half_used = false;
462+
value
463+
}
464+
465+
// As an optimization we try to write directly into the output buffer.
466+
// This is only enabled for little-endian platforms where unaligned writes
467+
// are known to be safe and fast.
468+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
469+
fn fill_bytes(&mut self, dest: &mut [u8]) {
470+
let mut filled = 0;
471+
self.half_used = false;
472+
473+
// Continue filling from the current set of results
474+
if self.index < self.results.as_ref().len() {
475+
let (consumed_u64, filled_u8) =
476+
fill_via_u64_chunks(&self.results.as_ref()[self.index..],
477+
dest);
478+
479+
self.index += consumed_u64;
480+
filled += filled_u8;
481+
}
482+
483+
let len_remainder =
484+
(dest.len() - filled) % (self.results.as_ref().len() * 8);
485+
let end_direct = dest.len() - len_remainder;
486+
487+
while filled < end_direct {
488+
let dest_u64: &mut R::Results = unsafe {
489+
::core::mem::transmute(dest[filled..].as_mut_ptr())
490+
};
491+
self.core.generate(dest_u64);
492+
filled += self.results.as_ref().len() * 8;
493+
}
494+
self.index = self.results.as_ref().len();
495+
496+
if len_remainder > 0 {
497+
self.core.generate(&mut self.results);
498+
let (consumed_u64, _) =
499+
fill_via_u64_chunks(&mut self.results.as_ref(),
500+
&mut dest[filled..]);
501+
502+
self.index = consumed_u64;
503+
}
504+
}
505+
506+
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
507+
fn fill_bytes(&mut self, dest: &mut [u8]) {
508+
let mut read_len = 0;
509+
self.half_used = false;
510+
while read_len < dest.len() {
511+
if self.index as usize >= self.results.as_ref().len() {
512+
self.core.generate(&mut self.results);
513+
self.index = 0;
514+
}
515+
516+
let (consumed_u64, filled_u8) =
517+
fill_via_u64_chunks(&self.results.as_ref()[self.index as usize..],
518+
&mut dest[read_len..]);
519+
520+
self.index += consumed_u64;
521+
read_len += filled_u8;
522+
}
523+
}
524+
525+
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
526+
Ok(self.fill_bytes(dest))
527+
}
528+
}
529+
530+
impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng64<R> {
531+
type Seed = R::Seed;
532+
533+
fn from_seed(seed: Self::Seed) -> Self {
534+
Self::new(R::from_seed(seed))
535+
}
536+
328537
fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
329-
let results_empty = R::Results::default();
330-
Ok(Self {
331-
core: R::from_rng(rng)?,
332-
index: results_empty.as_ref().len(), // generate on first use
333-
results: results_empty,
334-
})
538+
Ok(Self::new(R::from_rng(rng)?))
335539
}
336540
}
337541

rand_core/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444

4545
#[cfg(feature="std")] extern crate core;
4646
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;
47+
#[cfg(feature="serde1")] extern crate serde;
48+
#[cfg(feature="serde1")] #[macro_use] extern crate serde_derive;
4749

4850

4951
use core::default::Default;

src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,9 @@
195195
#[cfg(feature="std")] extern crate std as core;
196196
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;
197197

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

202202
#[cfg(all(target_arch = "wasm32", feature = "stdweb"))]
203203
#[macro_use]

0 commit comments

Comments
 (0)