From 1b48d22b1e542dc1f798bf3097e7b7f8635dc913 Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:03:51 +0200 Subject: [PATCH 01/11] Implement rand_r --- Cargo.toml | 2 ++ README.md | 1 + src/lib.rs | 4 ++++ src/rand_r.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 src/rand_r.rs diff --git a/Cargo.toml b/Cargo.toml index 3a4e29b..cb0b307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ all = [ "itoa", "memchr", "qsort", + "rand_r", "snprintf", "strcat", "strchr", @@ -59,6 +60,7 @@ isupper = [] itoa = [] memchr = [] qsort = [] +rand_r = [] signal = ["dep:portable-atomic"] signal-cs = ["portable-atomic/critical-section"] snprintf = [] diff --git a/README.md b/README.md index ca09ba4..b203866 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP * itoa * utoa +* rand_r ## To Do diff --git a/src/lib.rs b/src/lib.rs index 8198eef..44b36dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,10 @@ mod abs; #[cfg(feature = "abs")] pub use self::abs::abs; +mod rand_r; +#[cfg(feature = "rand_r")] +pub use self::rand_r::rand_r; + mod strcmp; #[cfg(feature = "strcmp")] pub use self::strcmp::strcmp; diff --git a/src/rand_r.rs b/src/rand_r.rs new file mode 100644 index 0000000..d16a687 --- /dev/null +++ b/src/rand_r.rs @@ -0,0 +1,48 @@ +//! Rust implementation of C library function `rand_r` +//! +//! Licensed under the Blue Oak Model Licence 1.0.0 +use core::ffi::{c_int, c_uint}; + +/// Rust implementation of C library function `rand_r` +/// +/// Passing NULL (core::ptr::null()) gives undefined behaviour. +#[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_rand_r")] +#[cfg_attr(feature = "rand_r", no_mangle)] +pub unsafe extern "C" fn rand_r(seedp: *mut c_uint) -> c_int { + // This algorithm is mentioned in the ISO C standard, here extended for 32 bits. + let mut next = *seedp; + let mut result: c_int; + + next = next.wrapping_mul(1103515245); + next = next.wrapping_add(12345); + result = ((next / 65536) % 2048) as c_int; + + next = next.wrapping_mul(1103515245); + next = next.wrapping_add(12345); + result <<= 10; + result ^= ((next / 65536) % 1024) as c_int; + + next = next.wrapping_mul(1103515245); + next = next.wrapping_add(12345); + result <<= 10; + result ^= ((next / 65536) % 1024) as c_int; + + *seedp = next; + + result +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_rand_r() { + unsafe { + let mut seed = 5; + // Values taken from glibc implementation + assert_eq!(rand_r(&mut seed), 234104184); + assert_eq!(rand_r(&mut seed), 1214203244); + assert_eq!(rand_r(&mut seed), 1803669308); + } + } +} From 4f3b386065982e6c2c40a24fe79914ca31635599 Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:47:25 +0200 Subject: [PATCH 02/11] Implement rand and srand --- Cargo.toml | 3 ++ README.md | 1 + src/lib.rs | 4 +++ src/rand.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/rand.rs diff --git a/Cargo.toml b/Cargo.toml index cb0b307..f154a6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,12 @@ readme = "README.md" repository = "https://github.com/rust-embedded-community/tinyrlibc" [dependencies] +critical-section = { version = "1.2.0", optional = true } portable-atomic = { version = "1.6.0", optional = true } [dev-dependencies] static-alloc = "0.2.4" +critical-section = { version = "1.2.0", features = ["std"] } [build-dependencies] cc = "1.0" @@ -61,6 +63,7 @@ itoa = [] memchr = [] qsort = [] rand_r = [] +rand = ["dep:critical-section"] signal = ["dep:portable-atomic"] signal-cs = ["portable-atomic/critical-section"] snprintf = [] diff --git a/README.md b/README.md index b203866..4e855b9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP * snprintf * vsnprintf * qsort +* rand * alloc (optional) * malloc * calloc diff --git a/src/lib.rs b/src/lib.rs index 44b36dc..0081a2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,10 @@ pub use self::abs::abs; mod rand_r; #[cfg(feature = "rand_r")] pub use self::rand_r::rand_r; +#[cfg(feature = "rand")] +mod rand; +#[cfg(feature = "rand")] +pub use self::rand::{rand, srand}; mod strcmp; #[cfg(feature = "strcmp")] diff --git a/src/rand.rs b/src/rand.rs new file mode 100644 index 0000000..f5e5b02 --- /dev/null +++ b/src/rand.rs @@ -0,0 +1,95 @@ +//! Rust implementation of C library functions `rand` and `srand` +//! +//! Licensed under the Blue Oak Model Licence 1.0.0 +use core::ffi::{c_int, c_uint}; + +struct GnuRand { + r: [u32; 344], + n: usize, +} + +impl GnuRand { + pub fn new(mut seed: u32) -> GnuRand { + let mut r = [0u32; 344]; + + if seed == 0 { + // Make sure seed is not 0 + seed = 1; + } + + r[0] = seed; + for i in 1..31 { + // This does: + // state[i] = (16807 * state[i - 1]) % 2147483647; + // but avoids overflowing 31 bits. + let hi = (r[i - 1] / 127773) as i32; + let lo = (r[i - 1] % 127773) as i32; + let mut word = 16807 * lo - 2836 * hi; + if word < 0 { + word += i32::MAX; + } + r[i] = word as u32; + } + for i in 31..34 { + r[i] = r[i - 31]; + } + for i in 34..344 { + r[i] = r[i - 31].wrapping_add(r[i - 3]); + } + + GnuRand { r, n: 0 } + } + + pub fn next(&mut self) -> i32 { + let x = self.r[(self.n + 313) % 344].wrapping_add(self.r[(self.n + 341) % 344]); + self.r[self.n % 344] = x; + self.n = (self.n + 1) % 344; + (x >> 1) as i32 + } +} + +static mut RAND: Option = None; + +/// Rust implementation of C library function `srand` +/// +/// Relies on [`critical-section`](https://docs.rs/critical-section/1.2.0/critical_section/) for thread-safety +#[cfg_attr(feature = "rand", no_mangle)] +pub extern "C" fn srand(seed: c_uint) { + let rnd = GnuRand::new(seed); + critical_section::with(|_| unsafe { RAND = Some(rnd) }); +} + +/// Rust implementation of C library function `rand` +/// +/// Relies on [`critical-section`](https://docs.rs/critical-section/1.2.0/critical_section/) for thread-safety +#[cfg_attr(feature = "rand", no_mangle)] +pub extern "C" fn rand() -> c_int { + critical_section::with(|_| { + let rnd = unsafe { RAND.get_or_insert_with(|| GnuRand::new(1)) }; + rnd.next() + }) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_rand() { + unsafe { + // Values taken from glibc implementation + assert_eq!(rand(), 1804289383); + assert_eq!(rand(), 846930886); + assert_eq!(rand(), 1681692777); + } + } + #[test] + fn test_srand() { + unsafe { + srand(5); + // Values taken from glibc implementation + assert_eq!(rand(), 590011675); + assert_eq!(rand(), 99788765); + assert_eq!(rand(), 2131925610); + } + } +} From ff710a761db90c4835fcd047b0f0713b64be41d6 Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:08:23 +0200 Subject: [PATCH 03/11] Use rand_r algorithm for rand --- Cargo.toml | 4 +- src/rand.rs | 121 +++++++++++++++++++++----------------------------- src/rand_r.rs | 6 +-- 3 files changed, 54 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f154a6f..f30db08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,10 @@ readme = "README.md" repository = "https://github.com/rust-embedded-community/tinyrlibc" [dependencies] -critical-section = { version = "1.2.0", optional = true } portable-atomic = { version = "1.6.0", optional = true } [dev-dependencies] static-alloc = "0.2.4" -critical-section = { version = "1.2.0", features = ["std"] } [build-dependencies] cc = "1.0" @@ -63,7 +61,7 @@ itoa = [] memchr = [] qsort = [] rand_r = [] -rand = ["dep:critical-section"] +rand = ["rand_r", "dep:portable-atomic"] signal = ["dep:portable-atomic"] signal-cs = ["portable-atomic/critical-section"] snprintf = [] diff --git a/src/rand.rs b/src/rand.rs index f5e5b02..b082e37 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -1,73 +1,61 @@ //! Rust implementation of C library functions `rand` and `srand` //! //! Licensed under the Blue Oak Model Licence 1.0.0 -use core::ffi::{c_int, c_uint}; +use core::{ + ffi::{c_int, c_uint}, + sync::atomic::Ordering, +}; -struct GnuRand { - r: [u32; 344], - n: usize, -} - -impl GnuRand { - pub fn new(mut seed: u32) -> GnuRand { - let mut r = [0u32; 344]; - - if seed == 0 { - // Make sure seed is not 0 - seed = 1; - } +use portable_atomic::AtomicU32; - r[0] = seed; - for i in 1..31 { - // This does: - // state[i] = (16807 * state[i - 1]) % 2147483647; - // but avoids overflowing 31 bits. - let hi = (r[i - 1] / 127773) as i32; - let lo = (r[i - 1] % 127773) as i32; - let mut word = 16807 * lo - 2836 * hi; - if word < 0 { - word += i32::MAX; - } - r[i] = word as u32; - } - for i in 31..34 { - r[i] = r[i - 31]; - } - for i in 34..344 { - r[i] = r[i - 31].wrapping_add(r[i - 3]); - } - - GnuRand { r, n: 0 } - } - - pub fn next(&mut self) -> i32 { - let x = self.r[(self.n + 313) % 344].wrapping_add(self.r[(self.n + 341) % 344]); - self.r[self.n % 344] = x; - self.n = (self.n + 1) % 344; - (x >> 1) as i32 - } -} - -static mut RAND: Option = None; +// static mut RAND: Option = None; +static RAND_STATE: AtomicU32 = AtomicU32::new(0x0); /// Rust implementation of C library function `srand` -/// -/// Relies on [`critical-section`](https://docs.rs/critical-section/1.2.0/critical_section/) for thread-safety #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn srand(seed: c_uint) { - let rnd = GnuRand::new(seed); - critical_section::with(|_| unsafe { RAND = Some(rnd) }); + RAND_STATE.store(seed, Ordering::Release); } /// Rust implementation of C library function `rand` /// -/// Relies on [`critical-section`](https://docs.rs/critical-section/1.2.0/critical_section/) for thread-safety +/// Returns a pseudo-random integer in the range 0 to `RAND_MAX` (inclusive). +/// May produce the same value in a row if called from multiple threads on platforms not supporting CAS operations. #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn rand() -> c_int { - critical_section::with(|_| { - let rnd = unsafe { RAND.get_or_insert_with(|| GnuRand::new(1)) }; - rnd.next() - }) + // Atomically update the global LFSR state using compare_and_swap if available + #[allow(dead_code)] + fn with_cas() -> c_int { + let mut current_state = RAND_STATE.load(Ordering::Relaxed); + let mut new_state = current_state; + let mut result = unsafe { crate::rand_r(&mut new_state as *mut _) }; + + loop { + match RAND_STATE.compare_exchange_weak( + current_state, + new_state, + Ordering::SeqCst, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(c) => current_state = c, + } + new_state = current_state; + result = unsafe { crate::rand_r(&mut new_state as *mut _) }; + } + + result as _ + } + // Fallback to non-atomic operation if compare_and_swap is not available + #[allow(dead_code)] + fn without_cas() -> c_int { + let mut current_state = RAND_STATE.load(Ordering::Acquire); + let result = unsafe { crate::rand_r(&mut current_state as *mut _) }; + RAND_STATE.store(current_state, Ordering::Release); + result as _ + } + portable_atomic::cfg_has_atomic_cas! { with_cas() } + portable_atomic::cfg_no_atomic_cas! { without_cas() } } #[cfg(test)] @@ -75,21 +63,12 @@ mod test { use super::*; #[test] fn test_rand() { - unsafe { - // Values taken from glibc implementation - assert_eq!(rand(), 1804289383); - assert_eq!(rand(), 846930886); - assert_eq!(rand(), 1681692777); - } - } - #[test] - fn test_srand() { - unsafe { - srand(5); - // Values taken from glibc implementation - assert_eq!(rand(), 590011675); - assert_eq!(rand(), 99788765); - assert_eq!(rand(), 2131925610); - } + assert_eq!(rand(), 1012483); + assert_eq!(rand(), 1716955678); + assert_eq!(rand(), 1792309081); + srand(5); + assert_eq!(rand(), 234104183); + assert_eq!(rand(), 1214203243); + assert_eq!(rand(), 1803669307); } } diff --git a/src/rand_r.rs b/src/rand_r.rs index d16a687..5c7d250 100644 --- a/src/rand_r.rs +++ b/src/rand_r.rs @@ -40,9 +40,9 @@ mod test { unsafe { let mut seed = 5; // Values taken from glibc implementation - assert_eq!(rand_r(&mut seed), 234104184); - assert_eq!(rand_r(&mut seed), 1214203244); - assert_eq!(rand_r(&mut seed), 1803669308); + assert_eq!(rand_r(&mut seed), 234104183); + assert_eq!(rand_r(&mut seed), 1214203243); + assert_eq!(rand_r(&mut seed), 1803669307); } } } From a2840f988942ca81c7365e706b140d64f87a2846 Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:08:56 +0200 Subject: [PATCH 04/11] Export RAND_MAX symbol --- src/lib.rs | 2 +- src/rand_r.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0081a2e..6b7924e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub use self::abs::abs; mod rand_r; #[cfg(feature = "rand_r")] -pub use self::rand_r::rand_r; +pub use self::rand_r::{rand_r, RAND_MAX}; #[cfg(feature = "rand")] mod rand; #[cfg(feature = "rand")] diff --git a/src/rand_r.rs b/src/rand_r.rs index 5c7d250..706fcf6 100644 --- a/src/rand_r.rs +++ b/src/rand_r.rs @@ -3,6 +3,10 @@ //! Licensed under the Blue Oak Model Licence 1.0.0 use core::ffi::{c_int, c_uint}; +#[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_RAND_MAX")] +#[cfg_attr(feature = "rand_r", no_mangle)] +pub static RAND_MAX: c_int = 0x7FFF_FFFC as _; + /// Rust implementation of C library function `rand_r` /// /// Passing NULL (core::ptr::null()) gives undefined behaviour. @@ -29,7 +33,7 @@ pub unsafe extern "C" fn rand_r(seedp: *mut c_uint) -> c_int { *seedp = next; - result + result - 1 } #[cfg(test)] From 4d6e9494ab1952e04961a25c6dee19de905a3e70 Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:51:42 +0100 Subject: [PATCH 05/11] Require CAS for rand --- Cargo.toml | 1 + src/rand.rs | 56 ++++++++++++++++++++++------------------------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f30db08..bbf52c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ memchr = [] qsort = [] rand_r = [] rand = ["rand_r", "dep:portable-atomic"] +rand-cs = ["rand", "portable-atomic/critical-section"] signal = ["dep:portable-atomic"] signal-cs = ["portable-atomic/critical-section"] snprintf = [] diff --git a/src/rand.rs b/src/rand.rs index b082e37..f4c8845 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -17,45 +17,35 @@ pub extern "C" fn srand(seed: c_uint) { RAND_STATE.store(seed, Ordering::Release); } -/// Rust implementation of C library function `rand` +/// Rust implementation of C library function `rand`. /// -/// Returns a pseudo-random integer in the range 0 to `RAND_MAX` (inclusive). -/// May produce the same value in a row if called from multiple threads on platforms not supporting CAS operations. +/// Returns a pseudo-random integer in the range 0 to [`RAND_MAX`](crate::RAND_MAX) (inclusive). +/// This requires CAS operations. If your platform does not support them natively, +/// you either have to enable the `rand-cs` feature of `tinyrlibc`, +/// or the [`critical-section`](https://docs.rs/portable-atomic/1.9.0/portable_atomic/#optional-features-critical-section) feature, +/// or the [`unsafe-assume-single-core`](https://docs.rs/portable-atomic/1.9.0/portable_atomic/#optional-features-unsafe-assume-single-core) feature +/// in [`portable-atomic`](https://crates.io/crates/portable-atomic). #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn rand() -> c_int { - // Atomically update the global LFSR state using compare_and_swap if available - #[allow(dead_code)] - fn with_cas() -> c_int { - let mut current_state = RAND_STATE.load(Ordering::Relaxed); - let mut new_state = current_state; - let mut result = unsafe { crate::rand_r(&mut new_state as *mut _) }; + let mut current_state = RAND_STATE.load(Ordering::Relaxed); + let mut new_state = current_state; + let mut result = unsafe { crate::rand_r(&mut new_state as *mut _) }; - loop { - match RAND_STATE.compare_exchange_weak( - current_state, - new_state, - Ordering::SeqCst, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(c) => current_state = c, - } - new_state = current_state; - result = unsafe { crate::rand_r(&mut new_state as *mut _) }; + loop { + match RAND_STATE.compare_exchange_weak( + current_state, + new_state, + Ordering::SeqCst, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(c) => current_state = c, } - - result as _ - } - // Fallback to non-atomic operation if compare_and_swap is not available - #[allow(dead_code)] - fn without_cas() -> c_int { - let mut current_state = RAND_STATE.load(Ordering::Acquire); - let result = unsafe { crate::rand_r(&mut current_state as *mut _) }; - RAND_STATE.store(current_state, Ordering::Release); - result as _ + new_state = current_state; + result = unsafe { crate::rand_r(&mut new_state as *mut _) }; } - portable_atomic::cfg_has_atomic_cas! { with_cas() } - portable_atomic::cfg_no_atomic_cas! { without_cas() } + + result as _ } #[cfg(test)] From f243872d1dc0c99183c463d41ba72500273bcc61 Mon Sep 17 00:00:00 2001 From: Sympatron GmbH <35803463+Sympatron@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:08:54 +0100 Subject: [PATCH 06/11] Small optimizations to rand --- src/rand.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/rand.rs b/src/rand.rs index f4c8845..9075b59 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -8,13 +8,12 @@ use core::{ use portable_atomic::AtomicU32; -// static mut RAND: Option = None; static RAND_STATE: AtomicU32 = AtomicU32::new(0x0); /// Rust implementation of C library function `srand` #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn srand(seed: c_uint) { - RAND_STATE.store(seed, Ordering::Release); + RAND_STATE.store(seed, Ordering::Relaxed); } /// Rust implementation of C library function `rand`. @@ -28,24 +27,20 @@ pub extern "C" fn srand(seed: c_uint) { #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn rand() -> c_int { let mut current_state = RAND_STATE.load(Ordering::Relaxed); - let mut new_state = current_state; - let mut result = unsafe { crate::rand_r(&mut new_state as *mut _) }; loop { + let mut new_state = current_state; + let result = unsafe { crate::rand_r(&mut new_state as *mut _) }; match RAND_STATE.compare_exchange_weak( current_state, new_state, - Ordering::SeqCst, + Ordering::Relaxed, Ordering::Relaxed, ) { - Ok(_) => break, + Ok(_) => return result as _, Err(c) => current_state = c, } - new_state = current_state; - result = unsafe { crate::rand_r(&mut new_state as *mut _) }; } - - result as _ } #[cfg(test)] From e01ca025fb44da1d05150164dd51a0e7bf9d6fed Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 1 Nov 2024 16:45:28 +0000 Subject: [PATCH 07/11] Update random number generator --- Cargo.toml | 3 +-- examples/rand.rs | 24 ++++++++++++++++++++++++ src/rand.rs | 40 +++++++++++---------------------------- src/rand_r.rs | 49 +++++++++++++++++++++++++++--------------------- 4 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 examples/rand.rs diff --git a/Cargo.toml b/Cargo.toml index bbf52c3..ecf6538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,8 +61,7 @@ itoa = [] memchr = [] qsort = [] rand_r = [] -rand = ["rand_r", "dep:portable-atomic"] -rand-cs = ["rand", "portable-atomic/critical-section"] +rand = ["rand_r"] signal = ["dep:portable-atomic"] signal-cs = ["portable-atomic/critical-section"] snprintf = [] diff --git a/examples/rand.rs b/examples/rand.rs new file mode 100644 index 0000000..f96ce9f --- /dev/null +++ b/examples/rand.rs @@ -0,0 +1,24 @@ +fn main() { + let mut distribution = std::collections::BTreeMap::new(); + + let mut seedp = 1; + for _ in 0..4 { + println!("."); + for _ in 0..1_000_000 { + let random = unsafe { tinyrlibc::rand_r(&mut seedp) }; + for place in 0..core::ffi::c_int::BITS { + let random_bit = (random & (1 << place)) != 0; + if random_bit { + distribution + .entry(place) + .and_modify(|v| *v += 1) + .or_insert(1); + } + } + } + } + + for (k, v) in distribution.iter() { + println!("{:02} => {}", k, v); + } +} diff --git a/src/rand.rs b/src/rand.rs index 9075b59..98f096b 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -8,7 +8,7 @@ use core::{ use portable_atomic::AtomicU32; -static RAND_STATE: AtomicU32 = AtomicU32::new(0x0); +static RAND_STATE: AtomicU32 = AtomicU32::new(1); /// Rust implementation of C library function `srand` #[cfg_attr(feature = "rand", no_mangle)] @@ -17,30 +17,12 @@ pub extern "C" fn srand(seed: c_uint) { } /// Rust implementation of C library function `rand`. -/// -/// Returns a pseudo-random integer in the range 0 to [`RAND_MAX`](crate::RAND_MAX) (inclusive). -/// This requires CAS operations. If your platform does not support them natively, -/// you either have to enable the `rand-cs` feature of `tinyrlibc`, -/// or the [`critical-section`](https://docs.rs/portable-atomic/1.9.0/portable_atomic/#optional-features-critical-section) feature, -/// or the [`unsafe-assume-single-core`](https://docs.rs/portable-atomic/1.9.0/portable_atomic/#optional-features-unsafe-assume-single-core) feature -/// in [`portable-atomic`](https://crates.io/crates/portable-atomic). #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn rand() -> c_int { - let mut current_state = RAND_STATE.load(Ordering::Relaxed); - - loop { - let mut new_state = current_state; - let result = unsafe { crate::rand_r(&mut new_state as *mut _) }; - match RAND_STATE.compare_exchange_weak( - current_state, - new_state, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => return result as _, - Err(c) => current_state = c, - } - } + let mut state = RAND_STATE.load(Ordering::Relaxed); + let result = unsafe { crate::rand_r(&mut state) }; + RAND_STATE.store(state); + result } #[cfg(test)] @@ -48,12 +30,12 @@ mod test { use super::*; #[test] fn test_rand() { - assert_eq!(rand(), 1012483); - assert_eq!(rand(), 1716955678); - assert_eq!(rand(), 1792309081); + assert_eq!(rand(), 1012484); + assert_eq!(rand(), 1716955679); + assert_eq!(rand(), 1792309082); srand(5); - assert_eq!(rand(), 234104183); - assert_eq!(rand(), 1214203243); - assert_eq!(rand(), 1803669307); + assert_eq!(rand(), 234104184); + assert_eq!(rand(), 1214203244); + assert_eq!(rand(), 1803669308); } } diff --git a/src/rand_r.rs b/src/rand_r.rs index 706fcf6..d735ac4 100644 --- a/src/rand_r.rs +++ b/src/rand_r.rs @@ -5,7 +5,7 @@ use core::ffi::{c_int, c_uint}; #[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_RAND_MAX")] #[cfg_attr(feature = "rand_r", no_mangle)] -pub static RAND_MAX: c_int = 0x7FFF_FFFC as _; +pub static RAND_MAX: c_int = c_int::MAX; /// Rust implementation of C library function `rand_r` /// @@ -13,27 +13,35 @@ pub static RAND_MAX: c_int = 0x7FFF_FFFC as _; #[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_rand_r")] #[cfg_attr(feature = "rand_r", no_mangle)] pub unsafe extern "C" fn rand_r(seedp: *mut c_uint) -> c_int { - // This algorithm is mentioned in the ISO C standard, here extended for 32 bits. - let mut next = *seedp; - let mut result: c_int; - - next = next.wrapping_mul(1103515245); - next = next.wrapping_add(12345); - result = ((next / 65536) % 2048) as c_int; + let mut result; - next = next.wrapping_mul(1103515245); - next = next.wrapping_add(12345); - result <<= 10; - result ^= ((next / 65536) % 1024) as c_int; + fn pump(input: c_uint) -> c_uint { + // This algorithm is mentioned in the ISO C standard + input.wrapping_mul(1103515245).wrapping_add(12345) + } - next = next.wrapping_mul(1103515245); - next = next.wrapping_add(12345); - result <<= 10; - result ^= ((next / 65536) % 1024) as c_int; + fn select_top(state: c_uint, bits: usize) -> c_uint { + // ignore the lower 16 bits, as they are low quality + (state >> 16) & ((1 << bits) - 1) + } + let mut next = *seedp; + if c_int::MAX == 32767 { + // pull 15 bits in one go + next = pump(next); + result = select_top(next, 15); + } else { + // pull 31 bits in three goes + next = pump(next); + result = select_top(next, 11) << 20; + next = pump(next); + result |= select_top(next, 10) << 10; + next = pump(next); + result |= select_top(next, 10); + } *seedp = next; - result - 1 + result as c_int } #[cfg(test)] @@ -43,10 +51,9 @@ mod test { fn test_rand_r() { unsafe { let mut seed = 5; - // Values taken from glibc implementation - assert_eq!(rand_r(&mut seed), 234104183); - assert_eq!(rand_r(&mut seed), 1214203243); - assert_eq!(rand_r(&mut seed), 1803669307); + assert_eq!(rand_r(&mut seed), 234104184); + assert_eq!(rand_r(&mut seed), 1214203244); + assert_eq!(rand_r(&mut seed), 1803669308); } } } From c549fe2c29faca93b07d081ae4cfd14876f1e839 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 1 Nov 2024 16:52:02 +0000 Subject: [PATCH 08/11] Rand example also prints min/max --- examples/rand.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/examples/rand.rs b/examples/rand.rs index f96ce9f..bfccb74 100644 --- a/examples/rand.rs +++ b/examples/rand.rs @@ -1,11 +1,23 @@ +//! Show the output of the random number generator + +use core::ffi::c_int; + fn main() { let mut distribution = std::collections::BTreeMap::new(); + let mut min = c_int::MAX; + let mut max = c_int::MIN; let mut seedp = 1; - for _ in 0..4 { - println!("."); + for i in 0..40 { + println!("{}/40", i); for _ in 0..1_000_000 { let random = unsafe { tinyrlibc::rand_r(&mut seedp) }; + if random > max { + max = random; + } + if random < min { + min = random; + } for place in 0..core::ffi::c_int::BITS { let random_bit = (random & (1 << place)) != 0; if random_bit { @@ -18,7 +30,9 @@ fn main() { } } + println!("Min value: {min:#10x}"); + println!("Max value: {max:#10x}"); for (k, v) in distribution.iter() { - println!("{:02} => {}", k, v); + println!("Bit {:02} was set {} times", k, v); } } From 52fa186e5c6a8d5d89215642749d8888b164bc60 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Wed, 13 Nov 2024 16:01:13 +0000 Subject: [PATCH 09/11] Allow RAND_MAX to be set to 32767. Might be useful if your newlib assumes that, even with 32-bit integers. Also adds "rand" to list of features in "all" and fixes missing portable-atomic. --- Cargo.toml | 4 +++- src/rand.rs | 37 ++++++++++++++++++++++++++----------- src/rand_r.rs | 31 ++++++++++++++++++++----------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ecf6538..62f6d78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ all = [ "memchr", "qsort", "rand_r", + "rand", "snprintf", "strcat", "strchr", @@ -61,7 +62,8 @@ itoa = [] memchr = [] qsort = [] rand_r = [] -rand = ["rand_r"] +rand = ["rand_r", "dep:portable-atomic"] +rand_max_i16 = [] signal = ["dep:portable-atomic"] signal-cs = ["portable-atomic/critical-section"] snprintf = [] diff --git a/src/rand.rs b/src/rand.rs index 98f096b..e018af4 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -2,7 +2,7 @@ //! //! Licensed under the Blue Oak Model Licence 1.0.0 use core::{ - ffi::{c_int, c_uint}, + ffi::{c_int, c_uint, c_ulong}, sync::atomic::Ordering, }; @@ -13,15 +13,18 @@ static RAND_STATE: AtomicU32 = AtomicU32::new(1); /// Rust implementation of C library function `srand` #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn srand(seed: c_uint) { - RAND_STATE.store(seed, Ordering::Relaxed); + // we do this cast to support platforms where c_uint is u16. + // but it complains on platforms where c_uint is u32. + #[allow(clippy::unnecessary_cast)] + RAND_STATE.store(seed as u32, Ordering::Relaxed); } /// Rust implementation of C library function `rand`. #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn rand() -> c_int { - let mut state = RAND_STATE.load(Ordering::Relaxed); + let mut state = RAND_STATE.load(Ordering::Relaxed) as c_ulong; let result = unsafe { crate::rand_r(&mut state) }; - RAND_STATE.store(state); + RAND_STATE.store(state as u32, Ordering::Relaxed); result } @@ -30,12 +33,24 @@ mod test { use super::*; #[test] fn test_rand() { - assert_eq!(rand(), 1012484); - assert_eq!(rand(), 1716955679); - assert_eq!(rand(), 1792309082); - srand(5); - assert_eq!(rand(), 234104184); - assert_eq!(rand(), 1214203244); - assert_eq!(rand(), 1803669308); + if c_int::MAX == 32767 || cfg!(feature = "rand_max_i16") { + srand(1); + assert_eq!(rand(), 16838); + assert_eq!(rand(), 5758); + assert_eq!(rand(), 10113); + srand(5); + assert_eq!(rand(), 18655); + assert_eq!(rand(), 8457); + assert_eq!(rand(), 10616); + } else { + srand(1); + assert_eq!(rand(), 476707713); + assert_eq!(rand(), 1186278907); + assert_eq!(rand(), 505671508); + srand(5); + assert_eq!(rand(), 234104184); + assert_eq!(rand(), 1214203244); + assert_eq!(rand(), 1803669308); + } } } diff --git a/src/rand_r.rs b/src/rand_r.rs index d735ac4..a31143b 100644 --- a/src/rand_r.rs +++ b/src/rand_r.rs @@ -1,7 +1,7 @@ //! Rust implementation of C library function `rand_r` //! //! Licensed under the Blue Oak Model Licence 1.0.0 -use core::ffi::{c_int, c_uint}; +use core::ffi::{c_int, c_uint, c_ulong}; #[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_RAND_MAX")] #[cfg_attr(feature = "rand_r", no_mangle)] @@ -12,21 +12,21 @@ pub static RAND_MAX: c_int = c_int::MAX; /// Passing NULL (core::ptr::null()) gives undefined behaviour. #[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_rand_r")] #[cfg_attr(feature = "rand_r", no_mangle)] -pub unsafe extern "C" fn rand_r(seedp: *mut c_uint) -> c_int { - let mut result; +pub unsafe extern "C" fn rand_r(seedp: *mut c_ulong) -> c_int { + let mut result: c_ulong; - fn pump(input: c_uint) -> c_uint { + fn pump(input: c_ulong) -> c_ulong { // This algorithm is mentioned in the ISO C standard input.wrapping_mul(1103515245).wrapping_add(12345) } - fn select_top(state: c_uint, bits: usize) -> c_uint { + fn select_top(state: c_ulong, bits: usize) -> c_ulong { // ignore the lower 16 bits, as they are low quality (state >> 16) & ((1 << bits) - 1) } let mut next = *seedp; - if c_int::MAX == 32767 { + if c_int::MAX == 32767 || cfg!(feature = "rand_max_i16") { // pull 15 bits in one go next = pump(next); result = select_top(next, 15); @@ -49,11 +49,20 @@ mod test { use super::*; #[test] fn test_rand_r() { - unsafe { - let mut seed = 5; - assert_eq!(rand_r(&mut seed), 234104184); - assert_eq!(rand_r(&mut seed), 1214203244); - assert_eq!(rand_r(&mut seed), 1803669308); + if c_int::MAX == 32767 || cfg!(feature = "rand_max_i16") { + unsafe { + let mut seed = 5; + assert_eq!(rand_r(&mut seed), 18655); + assert_eq!(rand_r(&mut seed), 8457); + assert_eq!(rand_r(&mut seed), 10616); + } + } else { + unsafe { + let mut seed = 5; + assert_eq!(rand_r(&mut seed), 234104184); + assert_eq!(rand_r(&mut seed), 1214203244); + assert_eq!(rand_r(&mut seed), 1803669308); + } } } } From 91e061247a91d28bf860723216b6de555e63bd50 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Wed, 13 Nov 2024 16:01:25 +0000 Subject: [PATCH 10/11] Remove rand example. It's not actually that useful. --- examples/rand.rs | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 examples/rand.rs diff --git a/examples/rand.rs b/examples/rand.rs deleted file mode 100644 index bfccb74..0000000 --- a/examples/rand.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Show the output of the random number generator - -use core::ffi::c_int; - -fn main() { - let mut distribution = std::collections::BTreeMap::new(); - let mut min = c_int::MAX; - let mut max = c_int::MIN; - - let mut seedp = 1; - for i in 0..40 { - println!("{}/40", i); - for _ in 0..1_000_000 { - let random = unsafe { tinyrlibc::rand_r(&mut seedp) }; - if random > max { - max = random; - } - if random < min { - min = random; - } - for place in 0..core::ffi::c_int::BITS { - let random_bit = (random & (1 << place)) != 0; - if random_bit { - distribution - .entry(place) - .and_modify(|v| *v += 1) - .or_insert(1); - } - } - } - } - - println!("Min value: {min:#10x}"); - println!("Max value: {max:#10x}"); - for (k, v) in distribution.iter() { - println!("Bit {:02} was set {} times", k, v); - } -} From e069a288afceae79e6eef2a552877c7c118e9fc7 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Thu, 14 Nov 2024 14:45:11 +0000 Subject: [PATCH 11/11] Use an 'unsigned int' as the rand_r seed. This then matches the stdlib.h declaration. Looking at the numbers generated, having that be a 16-bit value doesn't appear to affect the quality of the output significantly. It's certainly good enough for typical use cases. --- src/rand.rs | 4 ++-- src/rand_r.rs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/rand.rs b/src/rand.rs index e018af4..0e89d60 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -2,7 +2,7 @@ //! //! Licensed under the Blue Oak Model Licence 1.0.0 use core::{ - ffi::{c_int, c_uint, c_ulong}, + ffi::{c_int, c_uint}, sync::atomic::Ordering, }; @@ -22,7 +22,7 @@ pub extern "C" fn srand(seed: c_uint) { /// Rust implementation of C library function `rand`. #[cfg_attr(feature = "rand", no_mangle)] pub extern "C" fn rand() -> c_int { - let mut state = RAND_STATE.load(Ordering::Relaxed) as c_ulong; + let mut state = RAND_STATE.load(Ordering::Relaxed) as c_uint; let result = unsafe { crate::rand_r(&mut state) }; RAND_STATE.store(state as u32, Ordering::Relaxed); result diff --git a/src/rand_r.rs b/src/rand_r.rs index a31143b..bed95d9 100644 --- a/src/rand_r.rs +++ b/src/rand_r.rs @@ -1,7 +1,7 @@ //! Rust implementation of C library function `rand_r` //! //! Licensed under the Blue Oak Model Licence 1.0.0 -use core::ffi::{c_int, c_uint, c_ulong}; +use core::ffi::{c_int, c_uint}; #[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_RAND_MAX")] #[cfg_attr(feature = "rand_r", no_mangle)] @@ -12,20 +12,20 @@ pub static RAND_MAX: c_int = c_int::MAX; /// Passing NULL (core::ptr::null()) gives undefined behaviour. #[cfg_attr(not(feature = "rand_r"), export_name = "tinyrlibc_rand_r")] #[cfg_attr(feature = "rand_r", no_mangle)] -pub unsafe extern "C" fn rand_r(seedp: *mut c_ulong) -> c_int { - let mut result: c_ulong; +pub unsafe extern "C" fn rand_r(seedp: *mut c_uint) -> c_int { + let mut result: c_int; - fn pump(input: c_ulong) -> c_ulong { + fn pump(input: u32) -> u32 { // This algorithm is mentioned in the ISO C standard input.wrapping_mul(1103515245).wrapping_add(12345) } - fn select_top(state: c_ulong, bits: usize) -> c_ulong { + fn select_top(state: u32, bits: usize) -> c_int { // ignore the lower 16 bits, as they are low quality - (state >> 16) & ((1 << bits) - 1) + ((state >> 16) & ((1 << bits) - 1)) as c_int } - let mut next = *seedp; + let mut next = *seedp as u32; if c_int::MAX == 32767 || cfg!(feature = "rand_max_i16") { // pull 15 bits in one go next = pump(next); @@ -39,7 +39,7 @@ pub unsafe extern "C" fn rand_r(seedp: *mut c_ulong) -> c_int { next = pump(next); result |= select_top(next, 10); } - *seedp = next; + *seedp = next as c_uint; result as c_int }