Repository: rust-random/getrandom (rust)

Issue:
Add support for targets without atomic operations
This crate cannot currently be built for targets that do not support atomic operations (e.g. riscv32im) due to `LazyBool/Usize` in https://github.com/rust-random/getrandom/blob/baf2198d2a52e16e3c8a820ce9c47f2fe39cbe2d/src/util.rs#L33, even though those are not used when providing a custom implementation.

Example error when compiling for riscv32im
```
error[E0432]: unresolved import `core::sync::atomic::AtomicUsize`
  --> src/util.rs:12:20
   |
12 |     sync::atomic::{AtomicUsize, Ordering::Relaxed},
   |                    ^^^^^^^^^^^ no `AtomicUsize` in `sync::atomic`
```

It would be nice to have this working there as well, a quick workaround could be to conditionally compile those additional structs only when atomics are available.

Pull request description:
Fix custom backend for targets without atomics
Closes #383 

Patch:
diff --git a/src/lazy.rs b/src/lazy.rs
new file mode 100644
index 0000000..b9c2f88
--- /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 10cc227..fffa4cb 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 e81f1e1..bfeb793 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 69f6a5d..59d5249 100644
--- a/src/rdrand.rs
+++ b/src/rdrand.rs
@@ -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! {
diff --git a/src/use_file.rs b/src/use_file.rs
index a6ef0d2..23a5a5a 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<u8>]) -> Result<(), Error> {
     let fd = get_rng_fd()?;
@@ -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),
         }
     }
@@ -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)
diff --git a/src/util.rs b/src/util.rs
index 3162afa..bd58c56 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


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e6534e9..e3c9bbe 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/tests/rdrand.rs b/tests/rdrand.rs
index 2567868..a355c31 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"]