Add no_std build support

Based on @jethrogb's core branch and my master branch
Plus some extra notes in README
This commit is contained in:
Diggory Hardy 2017-12-14 16:36:28 +00:00
parent 5116356d1c
commit 0afeb1d056
12 changed files with 158 additions and 74 deletions

View file

@ -14,11 +14,16 @@ keywords = ["random", "rng"]
categories = ["algorithms"]
[features]
i128_support = []
nightly = ["i128_support"]
default = ["std"]
nightly = ["i128_support"] # enables all features requiring nightly rust
std = ["libc"] # default feature; without this rand uses libcore
alloc = [] # enables Vec and Box support without std
i128_support = [] # enables i128 and u128 support
[dependencies]
libc = "0.2"
libc = { version = "0.2", optional = true }
[dev-dependencies]
log = "0.3.0"

View file

@ -57,6 +57,48 @@ let mut rng = rand::ChaChaRng::new_unseeded();
println!("i32: {}, u32: {}", rng.gen::<i32>(), rng.gen::<u32>())
```
## Features
By default, `rand` is built with all stable features available. The following
optional features are available:
- `i128_support` enables support for generating `u128` and `i128` values
- `nightly` enables all unstable features (`i128_support`)
- `std` enabled by default; by setting "default-features = false" `no_std`
mode is activated; this removes features depending on `std` functionality:
- `OsRng` is entirely unavailable
- `JitterRng` code is still present, but a nanosecond timer must be
provided via `JitterRng::new_with_timer`
- Since no external entropy is available, it is not possible to create
generators with fresh seeds (user must provide entropy)
- `thread_rng`, `weak_rng` and `random` are all disabled
- exponential, normal and gamma type distributions are unavailable
since `exp` and `log` functions are not provided in `core`
- any code requiring `Vec` or `Box`
- `alloc` can be used instead of `std` to provide `Vec` and `Box`
## Testing
Unfortunately, `cargo test` does not test everything. The following tests are
recommended:
```
# Basic tests for rand and sub-crates
cargo test --all
# Test no_std support (build only since nearly all tests require std)
cargo build --all --no-default-features
# Test 128-bit support (requires nightly)
cargo test --all --features nightly
# Benchmarks (requires nightly)
cargo bench
# or just to test the benchmark code:
cargo test --benches
```
# `derive(Rand)`
You can derive the `Rand` trait for your custom type via the `#[derive(Rand)]`

View file

@ -17,20 +17,29 @@
//! internally. The `IndependentSample` trait is for generating values
//! that do not need to record state.
use std::marker;
use core::marker;
use {Rng, Rand};
pub use self::range::Range;
#[cfg(feature="std")]
pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
#[cfg(feature="std")]
pub use self::normal::{Normal, LogNormal};
#[cfg(feature="std")]
pub use self::exponential::Exp;
pub mod range;
#[cfg(feature="std")]
pub mod gamma;
#[cfg(feature="std")]
pub mod normal;
#[cfg(feature="std")]
pub mod exponential;
#[cfg(feature="std")]
mod ziggurat_tables;
/// Types that can be used to create a random instance of `Support`.
pub trait Sample<Support> {
/// Generate a random value of `Support`, using `rng` as the
@ -203,8 +212,6 @@ impl<'a, T: Clone> IndependentSample<T> for WeightedChoice<'a, T> {
}
}
mod ziggurat_tables;
/// Sample a random number using the Ziggurat method (specifically the
/// ZIGNOR variant from Doornik 2005). Most of the arguments are
/// directly from the paper:
@ -220,6 +227,7 @@ mod ziggurat_tables;
// the perf improvement (25-50%) is definitely worth the extra code
// size from force-inlining.
#[cfg(feature="std")]
#[inline(always)]
fn ziggurat<R: Rng, P, Z>(
rng: &mut R,

View file

@ -12,7 +12,7 @@
// this is surprisingly complicated to be both generic & correct
use std::num::Wrapping as w;
use core::num::Wrapping as w;
use Rng;
use distributions::{Sample, IndependentSample};
@ -99,7 +99,7 @@ macro_rules! integer_impl {
#[inline]
fn construct_range(low: $ty, high: $ty) -> Range<$ty> {
let range = (w(high as $unsigned) - w(low as $unsigned)).0;
let unsigned_max: $unsigned = ::std::$unsigned::MAX;
let unsigned_max: $unsigned = ::core::$unsigned::MAX;
// this is the largest number that fits into $unsigned
// that `range` divides evenly, so, if we've sampled
@ -191,7 +191,7 @@ mod tests {
$(
let v: &[($ty, $ty)] = &[(0, 10),
(10, 127),
(::std::$ty::MIN, ::std::$ty::MAX)];
(::core::$ty::MIN, ::core::$ty::MAX)];
for &(low, high) in v.iter() {
let mut sampler: Range<$ty> = Range::new(low, high);
for _ in 0..1000 {

View file

@ -18,7 +18,8 @@
use Rng;
use std::{fmt, mem, ptr};
use core::{fmt, mem, ptr};
#[cfg(feature="std")]
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
const MEMORY_BLOCKS: usize = 64;
@ -107,6 +108,7 @@ impl fmt::Display for TimerError {
}
}
#[cfg(feature="std")]
impl ::std::error::Error for TimerError {
fn description(&self) -> &str {
self.description()
@ -114,6 +116,7 @@ impl ::std::error::Error for TimerError {
}
// Initialise to zero; must be positive
#[cfg(feature="std")]
static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT;
impl JitterRng {
@ -123,8 +126,9 @@ impl JitterRng {
/// During initialization CPU execution timing jitter is measured a few
/// hundred times. If this does not pass basic quality tests, an error is
/// returned. The test result is cached to make subsequent calls faster.
#[cfg(feature="std")]
pub fn new() -> Result<JitterRng, TimerError> {
let mut ec = JitterRng::new_with_timer(get_nstime);
let mut ec = JitterRng::new_with_timer(platform::get_nstime);
let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u32;
if rounds == 0 {
// No result yet: run test.
@ -648,57 +652,61 @@ impl JitterRng {
/// # try_main().unwrap();
/// # }
/// ```
#[cfg(feature="std")]
pub fn timer_stats(&mut self, var_rounds: bool) -> i64 {
let time = get_nstime();
let time = platform::get_nstime();
self.memaccess(var_rounds);
self.lfsr_time(time, var_rounds);
let time2 = get_nstime();
let time2 = platform::get_nstime();
time2.wrapping_sub(time) as i64
}
}
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))]
fn get_nstime() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(feature="std")]
mod platform {
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))]
pub fn get_nstime() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
// The correct way to calculate the current time is
// `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64`
// But this is faster, and the difference in terms of entropy is negligible
// (log2(10^9) == 29.9).
dur.as_secs() << 30 | dur.subsec_nanos() as u64
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn get_nstime() -> u64 {
extern crate libc;
// On Mac OS and iOS std::time::SystemTime only has 1000ns resolution.
// We use `mach_absolute_time` instead. This provides a CPU dependent unit,
// to get real nanoseconds the result should by multiplied by numer/denom
// from `mach_timebase_info`.
// But we are not interested in the exact nanoseconds, just entropy. So we
// use the raw result.
unsafe { libc::mach_absolute_time() }
}
#[cfg(target_os = "windows")]
fn get_nstime() -> u64 {
#[allow(non_camel_case_types)]
type LARGE_INTEGER = i64;
#[allow(non_camel_case_types)]
type BOOL = i32;
extern "system" {
fn QueryPerformanceCounter(lpPerformanceCount: *mut LARGE_INTEGER) -> BOOL;
let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
// The correct way to calculate the current time is
// `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64`
// But this is faster, and the difference in terms of entropy is negligible
// (log2(10^9) == 29.9).
dur.as_secs() << 30 | dur.subsec_nanos() as u64
}
let mut t = 0;
unsafe { QueryPerformanceCounter(&mut t); }
t as u64
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub fn get_nstime() -> u64 {
extern crate libc;
// On Mac OS and iOS std::time::SystemTime only has 1000ns resolution.
// We use `mach_absolute_time` instead. This provides a CPU dependent unit,
// to get real nanoseconds the result should by multiplied by numer/denom
// from `mach_timebase_info`.
// But we are not interested in the exact nanoseconds, just entropy. So we
// use the raw result.
unsafe { libc::mach_absolute_time() }
}
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
fn get_nstime() -> u64 {
unreachable!()
#[cfg(target_os = "windows")]
pub fn get_nstime() -> u64 {
#[allow(non_camel_case_types)]
type LARGE_INTEGER = i64;
#[allow(non_camel_case_types)]
type BOOL = i32;
extern "system" {
fn QueryPerformanceCounter(lpPerformanceCount: *mut LARGE_INTEGER) -> BOOL;
}
let mut t = 0;
unsafe { QueryPerformanceCounter(&mut t); }
t as u64
}
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
pub fn get_nstime() -> u64 {
unreachable!()
}
}
// A function that is opaque to the optimizer to assist in avoiding dead-code

View file

@ -243,19 +243,22 @@
#![deny(missing_debug_implementations)]
#![cfg_attr(not(feature="std"), no_std)]
#![cfg_attr(all(feature="alloc", not(feature="std")), feature(alloc))]
#![cfg_attr(feature = "i128_support", feature(i128_type, i128))]
#[cfg(feature="std")] extern crate std as core;
#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc;
#[cfg(test)] #[macro_use] extern crate log;
use std::cell::RefCell;
use std::marker;
use std::mem;
use std::io;
use std::rc::Rc;
use core::marker;
use core::mem;
#[cfg(feature="std")] use std::cell::RefCell;
#[cfg(feature="std")] use std::io;
#[cfg(feature="std")] use std::rc::Rc;
pub use jitter::JitterRng;
pub use os::OsRng;
#[cfg(feature="std")] pub use os::OsRng;
pub use isaac::{IsaacRng, Isaac64Rng};
pub use chacha::ChaChaRng;
@ -274,9 +277,9 @@ pub mod distributions;
pub mod reseeding;
mod rand_impls;
pub mod jitter;
pub mod os;
pub mod read;
pub mod seq;
#[cfg(feature="std")] pub mod os;
#[cfg(feature="std")] pub mod read;
#[cfg(any(feature="std", feature = "alloc"))] pub mod seq;
mod prng;
// These tiny modules are here to avoid API breakage, probably only temporarily
@ -625,6 +628,7 @@ impl<'a, R: ?Sized> Rng for &'a mut R where R: Rng {
}
}
#[cfg(feature="std")]
impl<R: ?Sized> Rng for Box<R> where R: Rng {
fn next_u32(&mut self) -> u32 {
(**self).next_u32()
@ -776,6 +780,7 @@ impl StdRng {
///
/// Reading the randomness from the OS may fail, and any error is
/// propagated via the `io::Result` return value.
#[cfg(feature="std")]
pub fn new() -> io::Result<StdRng> {
match OsRng::new() {
Ok(mut r) => Ok(StdRng { rng: r.gen() }),
@ -823,14 +828,17 @@ impl<'a> SeedableRng<&'a [usize]> for StdRng {
/// create the `Rng` yourself.
///
/// This will seed the generator with randomness from thread_rng.
#[cfg(feature="std")]
pub fn weak_rng() -> XorShiftRng {
thread_rng().gen()
}
/// Controls how the thread-local RNG is reseeded.
#[cfg(feature="std")]
#[derive(Debug)]
struct ThreadRngReseeder;
#[cfg(feature="std")]
impl reseeding::Reseeder<StdRng> for ThreadRngReseeder {
fn reseed(&mut self, rng: &mut StdRng) {
match StdRng::new() {
@ -839,10 +847,13 @@ impl reseeding::Reseeder<StdRng> for ThreadRngReseeder {
}
}
}
#[cfg(feature="std")]
const THREAD_RNG_RESEED_THRESHOLD: u64 = 32_768;
#[cfg(feature="std")]
type ThreadRngInner = reseeding::ReseedingRng<StdRng, ThreadRngReseeder>;
/// The thread-local RNG.
#[cfg(feature="std")]
#[derive(Clone, Debug)]
pub struct ThreadRng {
rng: Rc<RefCell<ThreadRngInner>>,
@ -860,6 +871,7 @@ pub struct ThreadRng {
/// if the operating system random number generator is rigged to give
/// the same sequence always. If absolute consistency is required,
/// explicitly select an RNG, e.g. `IsaacRng` or `Isaac64Rng`.
#[cfg(feature="std")]
pub fn thread_rng() -> ThreadRng {
// used to make space in TLS for a random number generator
thread_local!(static THREAD_RNG_KEY: Rc<RefCell<ThreadRngInner>> = {
@ -876,6 +888,7 @@ pub fn thread_rng() -> ThreadRng {
ThreadRng { rng: THREAD_RNG_KEY.with(|t| t.clone()) }
}
#[cfg(feature="std")]
impl Rng for ThreadRng {
fn next_u32(&mut self) -> u32 {
self.rng.borrow_mut().next_u32()
@ -933,13 +946,12 @@ impl Rng for ThreadRng {
/// *x = rng.gen();
/// }
/// ```
#[cfg(feature="std")]
#[inline]
pub fn random<T: Rand>() -> T {
thread_rng().gen()
}
#[inline(always)]
#[deprecated(since="0.4.0", note="renamed to seq::sample_iter")]
/// DEPRECATED: use `seq::sample_iter` instead.
///
/// Randomly sample up to `amount` elements from a finite iterator.
@ -954,6 +966,9 @@ pub fn random<T: Rand>() -> T {
/// let sample = sample(&mut rng, 1..100, 5);
/// println!("{:?}", sample);
/// ```
#[cfg(feature="std")]
#[inline(always)]
#[deprecated(since="0.4.0", note="renamed to seq::sample_iter")]
pub fn sample<T, I, R>(rng: &mut R, iterable: I, amount: usize) -> Vec<T>
where I: IntoIterator<Item=T>,
R: Rng,

View file

@ -10,7 +10,7 @@
//! The ChaCha random number generator.
use std::num::Wrapping as w;
use core::num::Wrapping as w;
use {Rng, SeedableRng, Rand};
#[allow(bad_style)]

View file

@ -12,10 +12,10 @@
#![allow(non_camel_case_types)]
use std::slice;
use std::iter::repeat;
use std::num::Wrapping as w;
use std::fmt;
use core::slice;
use core::iter::repeat;
use core::num::Wrapping as w;
use core::fmt;
use {Rng, SeedableRng, Rand};

View file

@ -10,7 +10,7 @@
//! Xorshift generators
use std::num::Wrapping as w;
use core::num::Wrapping as w;
use {Rng, SeedableRng, Rand};
/// An Xorshift[1] random number

View file

@ -10,8 +10,7 @@
//! The implementations of `Rand` for the built-in types.
use std::char;
use std::mem;
use core::{char, mem};
use {Rand,Rng};

View file

@ -11,7 +11,7 @@
//! A wrapper around another RNG that reseeds it after it
//! generates a certain number of random bytes.
use std::default::Default;
use core::default::Default;
use {Rng, SeedableRng};

View file

@ -11,7 +11,13 @@
//! Functions for randomly accessing and sampling sequences.
use super::Rng;
use std::collections::hash_map::HashMap;
// This crate is only enabled when either std or alloc is available.
// BTreeMap is not as fast in tests, but better than nothing.
#[cfg(feature="std")] use std::collections::HashMap;
#[cfg(not(feature="std"))] use alloc::btree_map::BTreeMap;
#[cfg(not(feature="std"))] use alloc::Vec;
/// Randomly sample `amount` elements from a finite iterator.
///
@ -189,7 +195,8 @@ fn sample_indices_cache<R>(
where R: Rng,
{
debug_assert!(amount <= length);
let mut cache = HashMap::with_capacity(amount);
#[cfg(feature="std")] let mut cache = HashMap::with_capacity(amount);
#[cfg(not(feature="std"))] let mut cache = BTreeMap::new();
let mut out = Vec::with_capacity(amount);
for i in 0..amount {
let j: usize = rng.gen_range(i, length);