Skip to content

Commit

Permalink
Merge pull request rust-random#208 from dhardy/core
Browse files Browse the repository at this point in the history
Add no_std build support
  • Loading branch information
dhardy authored Dec 17, 2017
2 parents 5116356 + 0afeb1d commit 0316790
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 73 deletions.
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)]`
Expand Down
14 changes: 11 additions & 3 deletions src/distributions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions src/distributions/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
90 changes: 49 additions & 41 deletions src/jitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -107,13 +108,15 @@ impl fmt::Display for TimerError {
}
}

#[cfg(feature="std")]
impl ::std::error::Error for TimerError {
fn description(&self) -> &str {
self.description()
}
}

// Initialise to zero; must be positive
#[cfg(feature="std")]
static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT;

impl JitterRng {
Expand All @@ -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.
Expand Down Expand Up @@ -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
}

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"))]
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(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")]
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;
}

#[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 mut t = 0;
unsafe { QueryPerformanceCounter(&mut t); }
t as u64
}

let mut t = 0;
unsafe { QueryPerformanceCounter(&mut t); }
t as u64
}

#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
fn get_nstime() -> u64 {
unreachable!()
#[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
Expand Down
Loading

0 comments on commit 0316790

Please sign in to comment.