From 169944f1077a4ad83c1d6259a3dd1e27026cf04a Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 8 Nov 2023 08:58:24 +0300 Subject: [PATCH] Fix custom backend for targets without atomics (#385) --- .github/workflows/tests.yml | 11 +++++++ src/lazy.rs | 56 ++++++++++++++++++++++++++++++++++ src/lib.rs | 3 ++ src/linux_android.rs | 2 +- src/rdrand.rs | 5 +-- src/use_file.rs | 10 +++--- src/util.rs | 61 +------------------------------------ tests/rdrand.rs | 2 ++ 8 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 src/lazy.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e6534e9e..e3c9bbe6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 diff --git a/src/lazy.rs b/src/lazy.rs new file mode 100644 index 00000000..b9c2f88c --- /dev/null +++ b/src/lazy.rs @@ -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 + } +} diff --git a/src/lib.rs b/src/lib.rs index 10cc2273..fffa4cb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -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"), diff --git a/src/linux_android.rs b/src/linux_android.rs index e81f1e15..bfeb793e 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -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}, }; diff --git a/src/rdrand.rs b/src/rdrand.rs index 69f6a5d1..59d5249a 100644 --- a/src/rdrand.rs +++ b/src/rdrand.rs @@ -5,10 +5,7 @@ // , 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! { diff --git a/src/use_file.rs b/src/use_file.rs index a6ef0d23..23a5a5ae 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -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, }; @@ -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]) -> Result<(), Error> { let fd = get_rng_fd()?; @@ -47,10 +47,10 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> 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 { - static FD: AtomicUsize = AtomicUsize::new(LazyUsize::UNINIT); + static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT); fn get_fd() -> Option { match FD.load(Relaxed) { - LazyUsize::UNINIT => None, + FD_UNINIT => None, val => Some(val as libc::c_int), } } @@ -75,8 +75,8 @@ fn get_rng_fd() -> Result { 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) diff --git a/src/util.rs b/src/util.rs index 3162afad..bd58c567 100644 --- a/src/util.rs +++ b/src/util.rs @@ -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 diff --git a/tests/rdrand.rs b/tests/rdrand.rs index 25678683..a355c31e 100644 --- a/tests/rdrand.rs +++ b/tests/rdrand.rs @@ -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"]