Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement BlockRngCore ISAAC and ISAAC-64 #325

Merged
merged 9 commits into from
Apr 4, 2018

Conversation

pitdicker
Copy link
Contributor

@pitdicker pitdicker commented Mar 22, 2018

Updated description:

This is the part I left out of #281.

This PR implements BlockRngCore for ISAAC and ISAAC-64. This meant changes in both rand and rand_core.
rand_core:

  • Added a BlockRng64 wrapper, because BlockRng only works for u32. The implementation is mostly just of copy of the one in Isaac64Rng. Only the fill_bytes part uses the same direct filling trick as BlockRng. This does not have any advantage at all for Isaac64Rng, because it generates 2kb at a time, and the copy cost is negligible compared to the time that takes. But as this is supposed to be a generic wrapper, it seems to make sense.
  • Added the serde-1 feature to derive Serialize and Deserialize for the BlockRng wrappers.

rand:

  • Added a generic IsaacArray wrapper to support AsRef and other traits on the BlockRng::Results type of Isaac{64}Rng.
  • Renamed isaac_serde to isaac_array, because both deal with the problem of bad support for large arrays.
  • The code from IsaacRng and Isaac64Rng is as much as possible just a refactor of existing functionality. I believe the only small intended change is that the results type does not use Wrapping<T>.

First I thought this PR had problems that would take me forever to figure out, but thanks to help from @dtolnay they vanished overnight 😄. Now I think it would be very nice to get this in the 0.5 release, for three reasons:

  • Serialization support for BlockRng. We recommend RNGs to implement Serde, then we should also make it possible to do so.
  • This is a breaking change to one of our new features: serialization of Isaac{64}Rng.
  • This finishes the BlockRngCore PR by implementing it for ISAAC (making it possible to also use it with ReseedingRng etc.).

Original post:

This is the part I left out of #281.

There are a few things which make implementing BlockRngCore not straight-forward.

  • Added a BlockRng64 wrapper, because BlockRng only works for u32.
  • Asref() is not implemented for arrays with more than 32 elements. This adds an IsaacArray as work-around.
  • I am not sure how to re-implement serialisation. Do we want to make BlockRng in rand-core serialisable? I did some effort, but implementing serialize, and especially deserialize, by hand becomes ugly quickly. cc @LinearZoetrope

So this is not yet mergeable, but I am hoping for some input 😄.

///
/// This is similar to [`BlockRng`], but specialized for algorithms that operate
/// on `u64` values (rare). Hopefully specialization will one day allow
/// `BlockRng` to support both, so that this wrapper can be removed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "rare" part may change... I also don't know that it's worth talking about this future direction here. Also since all the implementations are different, is there any point making BlockRng support both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove the speculation...

The idea was once that things like ReseedingRng can then be used for both 32- and 64-bit RNGs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which is a problem in itself. I wonder whether many people will complain about the new ReseedingRng being incompatible with many PRNGs... maybe not: I'm struggling to find any references which are not simply copies of the source

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a grand total of two:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The servo one is little more than a reshuffle of Rand code, so more like one, and that's reseeding ChaCha, so we're fine.

@vks
Copy link
Collaborator

vks commented Mar 23, 2018

Is ISAAC going to be deprecated over HC128?

@pitdicker
Copy link
Contributor Author

In my opinion it already is, with StdRng and ThreadRng having switched.

@vks
Copy link
Collaborator

vks commented Mar 23, 2018

Well, at least it's not yet official according to the documentation.

@pitdicker
Copy link
Contributor Author

Good point, I'll make a reference to HC-128 in the documentation

@UserAB1236872
Copy link
Contributor

UserAB1236872 commented Mar 24, 2018

Have you tried just deriving Serialize and Deserialize on Block Core? It should automagically only derive where R and R::Results are Ser/De. Then any types using this that we want to enable serde on just require their own derive.

@pitdicker
Copy link
Contributor Author

@LinearZoetrope I have pushed my (unsuccessful) attempt to derive Serialize and Deserialize on BlockRng. It did not work 'automagically' enough... It fails with (repeated 3 times):

error[E0277]: the trait bound `<R as BlockRngCore>::Results: serde::Serialize` is not satisfied
   --> rand_core/src/impls.rs:187:38
    |
187 | #[cfg_attr(feature="serde-1", derive(Serialize,Deserialize))]
    |                                      ^^^^^^^^^ the trait `serde::Serialize` is not implemented for `<R as BlockRngCore>::Results`
    |
    = help: consider adding a `where <R as BlockRngCore>::Results: serde::Serialize` bound
    = note: required by `serde::ser::SerializeStruct::serialize_field`

error[E0277]: the trait bound `<R as BlockRngCore>::Results: serde::Deserialize<'_>` is not satisfied
   --> rand_core/src/impls.rs:187:48
    |
187 | #[cfg_attr(feature="serde-1", derive(Serialize,Deserialize))]
    |                                                ^^^^^^^^^^^ the trait `serde::Deserialize<'_>` is not implemented for `<R as BlockRngCore>::Results`
    |
    = help: consider adding a `where <R as BlockRngCore>::Results: serde::Deserialize<'_>` bound
    = note: required by `serde::de::SeqAccess::next_element`

Are you interested in taking a look?

@pitdicker
Copy link
Contributor Author

🎉 Finally have Serde re-implemented and a green bill from the CI. Updated the description in the first post.

(and relative links are really inconsistent)
#[derive(Copy, Clone)]
#[allow(missing_debug_implementations)]
#[cfg_attr(feature="serde-1", derive(Serialize, Deserialize))]
pub struct IsaacArray<T> where T: Default + Copy {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default and Copy should never be used in trait bounds on a data structure -- only on impls and methods. The only code that requires these bounds are the Deserialize and Default impls, so the bounds should be on those impls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for reviewing, I hope I got them right now. It compiles...

@@ -44,6 +44,8 @@

#[cfg(feature="std")] extern crate core;
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;
#[cfg(feature="serde-1")] extern crate serde;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a weak preference for serde1 as the name of the Serde feature instead of serde-1, to be consistent with crates that enable optional Serde support through an optional dependency on the serde1 shim.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see serde-1 seems to be used by a couple of crates to add the optional dependency. serde1 does not seem to have any reverse dependencies?

I don't like serde-1, and serde1 also does not seem ideal compared to just serde.
For rand_core, we could name the feature just serde with:
serde = { version = "1", optional = true, features = ["derive"] }.

For rand I see the serde feature as temporary, until we split of the PRNGs into a separate crate. There seems to be some movement in rust-lang/cargo#1286.

@dhardy What do you think? Does it make sense to use serde as the feature name in rand_core, and at some point in the future both crates can use that name?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well if serde never has another breaking release we don't need to append any number, but since it had quite a few before 1.0 appending a number seemed prudent. If @dtolnay prefers serde1 that is fine, except that it's a breaking change (we added the serde-1 feature for the Rand 0.4 release).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except that it's a breaking change (we added the serde-1 feature for the Rand 0.4 release).

I don't think we did? https://github.com/rust-lang-nursery/rand/blob/0.4/Cargo.toml

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then we aren't constrained and can use serde1 as suggested.

Copy link
Member

@dhardy dhardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about making the public fields private? I think the only accessors actually needed should be to core and a "reset" function (for ChaCha::set_counter).

serialize = "R::Results: Serialize",
deserialize = "R::Results: Deserialize<'de>")))]
pub results: R::Results,
pub index: usize,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment that 0 is never stored? next_u32 relies on this.

#[cfg_attr(feature="serde-1", serde(bound(
serialize = "R::Results: Serialize",
deserialize = "R::Results: Deserialize<'de>")))]
pub results: R::Results,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public fields should be documented — but I think perhaps we should not make these public (just add necessary accessor fns and a "reset" function).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But than I have to do things properly... But it really cleans things up, so good idea.

deserialize = "R::Results: Deserialize<'de>")))]
pub results: R::Results,
pub index: usize,
pub half_used: bool, // true if only half of the previous result is used
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think fill_bytes should reset half_used = false ? In fact I think the x86 version could also use the left-over bytes easily (because LE means the bytes are contiguous); the other version could also but would need an extra copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I think it only happened to pass the tests because it then it generates a new set of results in-between, which resets half_used.

I don't like to change the logic at the moment t.b.h.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think tests could catch this anyway; there's no memory unsafety, just the possibility of skipping one word of output and using another twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I though we tested for it in test_isaac64_true_values_mixed, but that tests only mixing next_u64 and next_u32.

@@ -44,6 +44,8 @@

#[cfg(feature="std")] extern crate core;
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;
#[cfg(feature="serde-1")] extern crate serde;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then we aren't constrained and can use serde1 as suggested.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really we should have mutable and immutable accessors, i.e. fn inner(&self) -> &R and fn inner_mut.

I'm surprised you didn't call this core though (you can shadow the name).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did initially, but I then thought this would make more sense for users, who don't see the field name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just thought it aligned well with BlockRngCore but doesn't matter.

@@ -424,6 +463,7 @@ where <R as BlockRngCore>::Results: AsRef<[u64]>
#[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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need this in the other implementation of fill_bytes too!

index: RAND_SIZE, // generate on first use
half_used: false,
})
Isaac64Rng(BlockRng64::new(Isaac64Core::new_from_u64(seed)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the separate Isaac64Core::new_from_u64 function, but if you do want it you don't need key on the line above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sloppy work... I am sorry. Modified the commit.

@dhardy
Copy link
Member

dhardy commented Apr 4, 2018

Looks good 👍

@pitdicker pitdicker merged commit baac1ca into rust-random:master Apr 4, 2018
@pitdicker pitdicker deleted the isaac_blockrng branch April 4, 2018 16:55
pitdicker added a commit that referenced this pull request Apr 4, 2018
Implement `BlockRngCore` ISAAC and ISAAC-64
@pitdicker pitdicker mentioned this pull request Apr 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants