Skip to content

Fix custom backend for targets without atomics #385

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

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,17 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cargo build -Z build-std=${{ contains(matrix.features, 'std') && 'std' || 'core'}} --target=${{ matrix.target }} --features="${{ join(matrix.features, ',') }}"

build-no-atomics:
name: No Atomics Build
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
targets: riscv32i-unknown-none-elf
- uses: Swatinem/rust-cache@v2
- run: cargo build --features custom --target riscv32i-unknown-none-elf

clippy-fmt:
name: Clippy + rustfmt
runs-on: ubuntu-22.04
Expand Down
56 changes: 56 additions & 0 deletions src/lazy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};

// This structure represents a lazily initialized static usize value. Useful
// when it is preferable to just rerun initialization instead of locking.
// Both unsync_init and sync_init will invoke an init() function until it
// succeeds, then return the cached value for future calls.
//
// Both methods support init() "failing". If the init() method returns UNINIT,
// that value will be returned as normal, but will not be cached.
//
// Users should only depend on the _value_ returned by init() functions.
// Specifically, for the following init() function:
// fn init() -> usize {
// a();
// let v = b();
// c();
// v
// }
// the effects of c() or writes to shared memory will not necessarily be
// observed and additional synchronization methods with be needed.
pub(crate) struct LazyUsize(AtomicUsize);

impl LazyUsize {
pub const fn new() -> Self {
Self(AtomicUsize::new(Self::UNINIT))
}

// The initialization is not completed.
pub const UNINIT: usize = usize::max_value();

// Runs the init() function at least once, returning the value of some run
// of init(). Multiple callers can run their init() functions in parallel.
// init() should always return the same value, if it succeeds.
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
// Relaxed ordering is fine, as we only have a single atomic variable.
let mut val = self.0.load(Relaxed);
if val == Self::UNINIT {
val = init();
self.0.store(val, Relaxed);
}
val
}
}

// Identical to LazyUsize except with bool instead of usize.
pub(crate) struct LazyBool(LazyUsize);

impl LazyBool {
pub const fn new() -> Self {
Self(LazyUsize::new())
}

pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool {
self.0.unsync_init(|| init() as usize) != 0
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ cfg_if! {
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
mod util_libc;
mod use_file;
mod lazy;
#[path = "linux_android.rs"] mod imp;
} else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] {
mod util_libc;
Expand Down Expand Up @@ -272,9 +273,11 @@ cfg_if! {
mod util_libc;
#[path = "emscripten.rs"] mod imp;
} else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] {
mod lazy;
#[path = "rdrand.rs"] mod imp;
} else if #[cfg(all(feature = "rdrand",
any(target_arch = "x86_64", target_arch = "x86")))] {
mod lazy;
#[path = "rdrand.rs"] mod imp;
} else if #[cfg(all(feature = "js",
any(target_arch = "wasm32", target_arch = "wasm64"),
Expand Down
2 changes: 1 addition & 1 deletion src/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

//! Implementation for Linux / Android
use crate::{
util::LazyBool,
lazy::LazyBool,
util_libc::{last_os_error, sys_fill_exact},
{use_file, Error},
};
Expand Down
5 changes: 1 addition & 4 deletions src/rdrand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::{
util::{slice_as_uninit, LazyBool},
Error,
};
use crate::{lazy::LazyBool, util::slice_as_uninit, Error};
use core::mem::{size_of, MaybeUninit};

cfg_if! {
Expand Down
10 changes: 5 additions & 5 deletions src/use_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

//! Implementations that just need to read from a file
use crate::{
util::LazyUsize,
util_libc::{open_readonly, sys_fill_exact},
Error,
};
Expand All @@ -35,6 +34,7 @@ const FILE_PATH: &str = "/dev/random\0";
target_os = "nto",
))]
const FILE_PATH: &str = "/dev/urandom\0";
const FD_UNINIT: usize = usize::max_value();

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
let fd = get_rng_fd()?;
Expand All @@ -47,10 +47,10 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// bytes. The file will be opened exactly once. All subsequent calls will
// return the same file descriptor. This file descriptor is never closed.
fn get_rng_fd() -> Result<libc::c_int, Error> {
static FD: AtomicUsize = AtomicUsize::new(LazyUsize::UNINIT);
static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT);
fn get_fd() -> Option<libc::c_int> {
match FD.load(Relaxed) {
LazyUsize::UNINIT => None,
FD_UNINIT => None,
val => Some(val as libc::c_int),
}
}
Expand All @@ -75,8 +75,8 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
wait_until_rng_ready()?;

let fd = unsafe { open_readonly(FILE_PATH)? };
// The fd always fits in a usize without conflicting with UNINIT.
debug_assert!(fd >= 0 && (fd as usize) < LazyUsize::UNINIT);
// The fd always fits in a usize without conflicting with FD_UNINIT.
debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT);
FD.store(fd as usize, Relaxed);

Ok(fd)
Expand Down
61 changes: 1 addition & 60 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(dead_code)]
use core::{
mem::MaybeUninit,
ptr,
sync::atomic::{AtomicUsize, Ordering::Relaxed},
};

// This structure represents a lazily initialized static usize value. Useful
// when it is preferable to just rerun initialization instead of locking.
// Both unsync_init and sync_init will invoke an init() function until it
// succeeds, then return the cached value for future calls.
//
// Both methods support init() "failing". If the init() method returns UNINIT,
// that value will be returned as normal, but will not be cached.
//
// Users should only depend on the _value_ returned by init() functions.
// Specifically, for the following init() function:
// fn init() -> usize {
// a();
// let v = b();
// c();
// v
// }
// the effects of c() or writes to shared memory will not necessarily be
// observed and additional synchronization methods with be needed.
pub struct LazyUsize(AtomicUsize);

impl LazyUsize {
pub const fn new() -> Self {
Self(AtomicUsize::new(Self::UNINIT))
}

// The initialization is not completed.
pub const UNINIT: usize = usize::max_value();

// Runs the init() function at least once, returning the value of some run
// of init(). Multiple callers can run their init() functions in parallel.
// init() should always return the same value, if it succeeds.
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
// Relaxed ordering is fine, as we only have a single atomic variable.
let mut val = self.0.load(Relaxed);
if val == Self::UNINIT {
val = init();
self.0.store(val, Relaxed);
}
val
}
}

// Identical to LazyUsize except with bool instead of usize.
pub struct LazyBool(LazyUsize);

impl LazyBool {
pub const fn new() -> Self {
Self(LazyUsize::new())
}

pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool {
self.0.unsync_init(|| init() as usize) != 0
}
}
use core::{mem::MaybeUninit, ptr};

/// Polyfill for `maybe_uninit_slice` feature's
/// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have
Expand Down
2 changes: 2 additions & 0 deletions tests/rdrand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use getrandom::Error;
#[macro_use]
extern crate cfg_if;
#[path = "../src/lazy.rs"]
mod lazy;
#[path = "../src/rdrand.rs"]
mod rdrand;
#[path = "../src/util.rs"]
Expand Down