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

Add rand, srand, and rand_r implementations #31

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ all = [
"itoa",
"memchr",
"qsort",
"rand_r",
"snprintf",
"strcat",
"strchr",
Expand Down Expand Up @@ -59,6 +60,9 @@ isupper = []
itoa = []
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 = []
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -49,6 +50,7 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP

* itoa
* utoa
* rand_r

## To Do

Expand Down
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ mod abs;
#[cfg(feature = "abs")]
pub use self::abs::abs;

mod rand_r;
#[cfg(feature = "rand_r")]
pub use self::rand_r::{rand_r, RAND_MAX};
#[cfg(feature = "rand")]
mod rand;
#[cfg(feature = "rand")]
pub use self::rand::{rand, srand};

mod strcmp;
#[cfg(feature = "strcmp")]
pub use self::strcmp::strcmp;
Expand Down
64 changes: 64 additions & 0 deletions src/rand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! 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},
sync::atomic::Ordering,
};

use portable_atomic::AtomicU32;

// static mut RAND: Option<GnuRand> = None;
thejpster marked this conversation as resolved.
Show resolved Hide resolved
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);
thejpster marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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);
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,
Copy link
Member

Choose a reason for hiding this comment

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

Is this required? No other accesses are gated on this CAS operation so I think Relaxed is OK.

Copy link
Contributor Author

@Sympatron Sympatron Oct 30, 2024

Choose a reason for hiding this comment

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

Honestly, I am not sure and wanted to play it safe. I basically copied the code from the docs. If you are sure, I can change it though.

Copy link
Member

Choose a reason for hiding this comment

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

Reading https://marabos.nl/atomics/memory-ordering.html I'm pretty sure that Relaxed for both is OK here. We're only modifying that single value, and nothing else related to it. So there are no other writes that must happen strictly before or strictly after we modify this one value.

Ordering::Relaxed,
) {
Ok(_) => break,
Err(c) => current_state = c,
}
new_state = current_state;
thejpster marked this conversation as resolved.
Show resolved Hide resolved
result = unsafe { crate::rand_r(&mut new_state as *mut _) };
}

result as _
}

#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_rand() {
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);
}
}
52 changes: 52 additions & 0 deletions src/rand_r.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! 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};

#[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 _;
Copy link
Member

Choose a reason for hiding this comment

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

This should be odd.

As you're pulling 31 random bits, I'm pretty sure this should be 0x7fff_ffff.

Copy link
Contributor Author

@Sympatron Sympatron Oct 31, 2024

Choose a reason for hiding this comment

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

I tested all 2^32 possible seeds and the value never went over 0x7FFF_FFFC. This seemed strange to me and I didn't really know what to do with RAND_MAX in that case. The original algorithm goes to up 0x7FFF_FFFD, but does not include 0. That is why I subtracted 1, because I thought it would be way more unexpected to never get 0 then to never get 0x7FFF_FFFD.

Since it may be odd to have RAND_MAX not be 2^n - 1, we could either reduce it to 30 bits at 0x3FFF_FFFF or "lie" and set it to 0x7FFF_FFFF which could be bad, because knowing RAND_MAX is necessary if you want to use rand to get a non-biased distribution.

Since tinyrlibc is mainly used by C code, RAND_MAX will probably never be used directly, because in C RAND_MAX is a ´#defineanyway, so this value will not be seen by the code usingrand`.

Copy link
Member

Choose a reason for hiding this comment

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

How many times did you call rand()? The 10 or 11 bits that get pulled should be relatively uniform. It might be worth logging them individually. Then if they are uniform, you should get values from 0 to 2^31 - 1 by combining 31 random bits together.


/// 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 - 1
}

#[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), 234104183);
assert_eq!(rand_r(&mut seed), 1214203243);
assert_eq!(rand_r(&mut seed), 1803669307);
}
}
}
Loading