Skip to content

Commit ec8d0b3

Browse files
committed
Use rand_r algorithm for rand
1 parent bd5c82f commit ec8d0b3

File tree

3 files changed

+54
-77
lines changed

3 files changed

+54
-77
lines changed

Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ readme = "README.md"
99
repository = "https://github.com/rust-embedded-community/tinyrlibc"
1010

1111
[dependencies]
12-
critical-section = { version = "1.2.0", optional = true }
1312
portable-atomic = { version = "1.6.0", optional = true }
1413

1514
[dev-dependencies]
1615
static-alloc = "0.2.4"
17-
critical-section = { version = "1.2.0", features = ["std"] }
1816

1917
[build-dependencies]
2018
cc = "1.0"
@@ -63,7 +61,7 @@ itoa = []
6361
memchr = []
6462
qsort = []
6563
rand_r = []
66-
rand = ["dep:critical-section"]
64+
rand = ["rand_r", "dep:portable-atomic"]
6765
signal = ["dep:portable-atomic"]
6866
signal-cs = ["portable-atomic/critical-section"]
6967
snprintf = []

src/rand.rs

Lines changed: 50 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,74 @@
11
//! Rust implementation of C library functions `rand` and `srand`
22
//!
33
//! Licensed under the Blue Oak Model Licence 1.0.0
4-
use core::ffi::{c_int, c_uint};
4+
use core::{
5+
ffi::{c_int, c_uint},
6+
sync::atomic::Ordering,
7+
};
58

6-
struct GnuRand {
7-
r: [u32; 344],
8-
n: usize,
9-
}
10-
11-
impl GnuRand {
12-
pub fn new(mut seed: u32) -> GnuRand {
13-
let mut r = [0u32; 344];
14-
15-
if seed == 0 {
16-
// Make sure seed is not 0
17-
seed = 1;
18-
}
9+
use portable_atomic::AtomicU32;
1910

20-
r[0] = seed;
21-
for i in 1..31 {
22-
// This does:
23-
// state[i] = (16807 * state[i - 1]) % 2147483647;
24-
// but avoids overflowing 31 bits.
25-
let hi = (r[i - 1] / 127773) as i32;
26-
let lo = (r[i - 1] % 127773) as i32;
27-
let mut word = 16807 * lo - 2836 * hi;
28-
if word < 0 {
29-
word += i32::MAX;
30-
}
31-
r[i] = word as u32;
32-
}
33-
for i in 31..34 {
34-
r[i] = r[i - 31];
35-
}
36-
for i in 34..344 {
37-
r[i] = r[i - 31].wrapping_add(r[i - 3]);
38-
}
39-
40-
GnuRand { r, n: 0 }
41-
}
42-
43-
pub fn next(&mut self) -> i32 {
44-
let x = self.r[(self.n + 313) % 344].wrapping_add(self.r[(self.n + 341) % 344]);
45-
self.r[self.n % 344] = x;
46-
self.n = (self.n + 1) % 344;
47-
(x >> 1) as i32
48-
}
49-
}
50-
51-
static mut RAND: Option<GnuRand> = None;
11+
// static mut RAND: Option<GnuRand> = None;
12+
static RAND_STATE: AtomicU32 = AtomicU32::new(0x0);
5213

5314
/// Rust implementation of C library function `srand`
54-
///
55-
/// Relies on [`critical-section`](https://docs.rs/critical-section/1.2.0/critical_section/) for thread-safety
5615
#[cfg_attr(feature = "rand", no_mangle)]
5716
pub extern "C" fn srand(seed: c_uint) {
58-
let rnd = GnuRand::new(seed);
59-
critical_section::with(|_| unsafe { RAND = Some(rnd) });
17+
RAND_STATE.store(seed, Ordering::Release);
6018
}
6119

6220
/// Rust implementation of C library function `rand`
6321
///
64-
/// Relies on [`critical-section`](https://docs.rs/critical-section/1.2.0/critical_section/) for thread-safety
22+
/// Returns a pseudo-random integer in the range 0 to `RAND_MAX` (inclusive).
23+
/// May produce the same value in a row if called from multiple threads on platforms not supporting CAS operations.
6524
#[cfg_attr(feature = "rand", no_mangle)]
6625
pub extern "C" fn rand() -> c_int {
67-
critical_section::with(|_| {
68-
let rnd = unsafe { RAND.get_or_insert_with(|| GnuRand::new(1)) };
69-
rnd.next()
70-
})
26+
// Atomically update the global LFSR state using compare_and_swap if available
27+
#[allow(dead_code)]
28+
fn with_cas() -> c_int {
29+
let mut current_state = RAND_STATE.load(Ordering::Relaxed);
30+
let mut new_state = current_state;
31+
let mut result = unsafe { crate::rand_r(&mut new_state as *mut _) };
32+
33+
loop {
34+
match RAND_STATE.compare_exchange_weak(
35+
current_state,
36+
new_state,
37+
Ordering::SeqCst,
38+
Ordering::Relaxed,
39+
) {
40+
Ok(_) => break,
41+
Err(c) => current_state = c,
42+
}
43+
new_state = current_state;
44+
result = unsafe { crate::rand_r(&mut new_state as *mut _) };
45+
}
46+
47+
result as _
48+
}
49+
// Fallback to non-atomic operation if compare_and_swap is not available
50+
#[allow(dead_code)]
51+
fn without_cas() -> c_int {
52+
let mut current_state = RAND_STATE.load(Ordering::Acquire);
53+
let result = unsafe { crate::rand_r(&mut current_state as *mut _) };
54+
RAND_STATE.store(current_state, Ordering::Release);
55+
result as _
56+
}
57+
portable_atomic::cfg_has_atomic_cas! { with_cas() }
58+
portable_atomic::cfg_no_atomic_cas! { without_cas() }
7159
}
7260

7361
#[cfg(test)]
7462
mod test {
7563
use super::*;
7664
#[test]
7765
fn test_rand() {
78-
unsafe {
79-
// Values taken from glibc implementation
80-
assert_eq!(rand(), 1804289383);
81-
assert_eq!(rand(), 846930886);
82-
assert_eq!(rand(), 1681692777);
83-
}
84-
}
85-
#[test]
86-
fn test_srand() {
87-
unsafe {
88-
srand(5);
89-
// Values taken from glibc implementation
90-
assert_eq!(rand(), 590011675);
91-
assert_eq!(rand(), 99788765);
92-
assert_eq!(rand(), 2131925610);
66+
assert_eq!(rand(), 1012483);
67+
assert_eq!(rand(), 1716955678);
68+
assert_eq!(rand(), 1792309081);
69+
srand(5);
70+
assert_eq!(rand(), 234104183);
71+
assert_eq!(rand(), 1214203243);
72+
assert_eq!(rand(), 1803669307);
9373
}
9474
}
95-
}

src/rand_r.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ mod test {
4040
unsafe {
4141
let mut seed = 5;
4242
// Values taken from glibc implementation
43-
assert_eq!(rand_r(&mut seed), 234104184);
44-
assert_eq!(rand_r(&mut seed), 1214203244);
45-
assert_eq!(rand_r(&mut seed), 1803669308);
43+
assert_eq!(rand_r(&mut seed), 234104183);
44+
assert_eq!(rand_r(&mut seed), 1214203243);
45+
assert_eq!(rand_r(&mut seed), 1803669307);
4646
}
4747
}
4848
}

0 commit comments

Comments
 (0)