Skip to content

Commit 169944f

Browse files
authored
Fix custom backend for targets without atomics (#385)
1 parent baf2198 commit 169944f

File tree

8 files changed

+80
-70
lines changed

8 files changed

+80
-70
lines changed

.github/workflows/tests.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,17 @@ jobs:
327327
- uses: Swatinem/rust-cache@v2
328328
- run: cargo build -Z build-std=${{ contains(matrix.features, 'std') && 'std' || 'core'}} --target=${{ matrix.target }} --features="${{ join(matrix.features, ',') }}"
329329

330+
build-no-atomics:
331+
name: No Atomics Build
332+
runs-on: ubuntu-22.04
333+
steps:
334+
- uses: actions/checkout@v3
335+
- uses: dtolnay/rust-toolchain@stable
336+
with:
337+
targets: riscv32i-unknown-none-elf
338+
- uses: Swatinem/rust-cache@v2
339+
- run: cargo build --features custom --target riscv32i-unknown-none-elf
340+
330341
clippy-fmt:
331342
name: Clippy + rustfmt
332343
runs-on: ubuntu-22.04

src/lazy.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
2+
3+
// This structure represents a lazily initialized static usize value. Useful
4+
// when it is preferable to just rerun initialization instead of locking.
5+
// Both unsync_init and sync_init will invoke an init() function until it
6+
// succeeds, then return the cached value for future calls.
7+
//
8+
// Both methods support init() "failing". If the init() method returns UNINIT,
9+
// that value will be returned as normal, but will not be cached.
10+
//
11+
// Users should only depend on the _value_ returned by init() functions.
12+
// Specifically, for the following init() function:
13+
// fn init() -> usize {
14+
// a();
15+
// let v = b();
16+
// c();
17+
// v
18+
// }
19+
// the effects of c() or writes to shared memory will not necessarily be
20+
// observed and additional synchronization methods with be needed.
21+
pub(crate) struct LazyUsize(AtomicUsize);
22+
23+
impl LazyUsize {
24+
pub const fn new() -> Self {
25+
Self(AtomicUsize::new(Self::UNINIT))
26+
}
27+
28+
// The initialization is not completed.
29+
pub const UNINIT: usize = usize::max_value();
30+
31+
// Runs the init() function at least once, returning the value of some run
32+
// of init(). Multiple callers can run their init() functions in parallel.
33+
// init() should always return the same value, if it succeeds.
34+
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
35+
// Relaxed ordering is fine, as we only have a single atomic variable.
36+
let mut val = self.0.load(Relaxed);
37+
if val == Self::UNINIT {
38+
val = init();
39+
self.0.store(val, Relaxed);
40+
}
41+
val
42+
}
43+
}
44+
45+
// Identical to LazyUsize except with bool instead of usize.
46+
pub(crate) struct LazyBool(LazyUsize);
47+
48+
impl LazyBool {
49+
pub const fn new() -> Self {
50+
Self(LazyUsize::new())
51+
}
52+
53+
pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool {
54+
self.0.unsync_init(|| init() as usize) != 0
55+
}
56+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ cfg_if! {
224224
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
225225
mod util_libc;
226226
mod use_file;
227+
mod lazy;
227228
#[path = "linux_android.rs"] mod imp;
228229
} else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] {
229230
mod util_libc;
@@ -272,9 +273,11 @@ cfg_if! {
272273
mod util_libc;
273274
#[path = "emscripten.rs"] mod imp;
274275
} else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] {
276+
mod lazy;
275277
#[path = "rdrand.rs"] mod imp;
276278
} else if #[cfg(all(feature = "rdrand",
277279
any(target_arch = "x86_64", target_arch = "x86")))] {
280+
mod lazy;
278281
#[path = "rdrand.rs"] mod imp;
279282
} else if #[cfg(all(feature = "js",
280283
any(target_arch = "wasm32", target_arch = "wasm64"),

src/linux_android.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
//! Implementation for Linux / Android
1010
use crate::{
11-
util::LazyBool,
11+
lazy::LazyBool,
1212
util_libc::{last_os_error, sys_fill_exact},
1313
{use_file, Error},
1414
};

src/rdrand.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
8-
use crate::{
9-
util::{slice_as_uninit, LazyBool},
10-
Error,
11-
};
8+
use crate::{lazy::LazyBool, util::slice_as_uninit, Error};
129
use core::mem::{size_of, MaybeUninit};
1310

1411
cfg_if! {

src/use_file.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
//! Implementations that just need to read from a file
1010
use crate::{
11-
util::LazyUsize,
1211
util_libc::{open_readonly, sys_fill_exact},
1312
Error,
1413
};
@@ -35,6 +34,7 @@ const FILE_PATH: &str = "/dev/random\0";
3534
target_os = "nto",
3635
))]
3736
const FILE_PATH: &str = "/dev/urandom\0";
37+
const FD_UNINIT: usize = usize::max_value();
3838

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

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

8282
Ok(fd)

src/util.rs

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,66 +6,7 @@
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
88
#![allow(dead_code)]
9-
use core::{
10-
mem::MaybeUninit,
11-
ptr,
12-
sync::atomic::{AtomicUsize, Ordering::Relaxed},
13-
};
14-
15-
// This structure represents a lazily initialized static usize value. Useful
16-
// when it is preferable to just rerun initialization instead of locking.
17-
// Both unsync_init and sync_init will invoke an init() function until it
18-
// succeeds, then return the cached value for future calls.
19-
//
20-
// Both methods support init() "failing". If the init() method returns UNINIT,
21-
// that value will be returned as normal, but will not be cached.
22-
//
23-
// Users should only depend on the _value_ returned by init() functions.
24-
// Specifically, for the following init() function:
25-
// fn init() -> usize {
26-
// a();
27-
// let v = b();
28-
// c();
29-
// v
30-
// }
31-
// the effects of c() or writes to shared memory will not necessarily be
32-
// observed and additional synchronization methods with be needed.
33-
pub struct LazyUsize(AtomicUsize);
34-
35-
impl LazyUsize {
36-
pub const fn new() -> Self {
37-
Self(AtomicUsize::new(Self::UNINIT))
38-
}
39-
40-
// The initialization is not completed.
41-
pub const UNINIT: usize = usize::max_value();
42-
43-
// Runs the init() function at least once, returning the value of some run
44-
// of init(). Multiple callers can run their init() functions in parallel.
45-
// init() should always return the same value, if it succeeds.
46-
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
47-
// Relaxed ordering is fine, as we only have a single atomic variable.
48-
let mut val = self.0.load(Relaxed);
49-
if val == Self::UNINIT {
50-
val = init();
51-
self.0.store(val, Relaxed);
52-
}
53-
val
54-
}
55-
}
56-
57-
// Identical to LazyUsize except with bool instead of usize.
58-
pub struct LazyBool(LazyUsize);
59-
60-
impl LazyBool {
61-
pub const fn new() -> Self {
62-
Self(LazyUsize::new())
63-
}
64-
65-
pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool {
66-
self.0.unsync_init(|| init() as usize) != 0
67-
}
68-
}
9+
use core::{mem::MaybeUninit, ptr};
6910

7011
/// Polyfill for `maybe_uninit_slice` feature's
7112
/// `MaybeUninit::slice_assume_init_mut`. Every element of `slice` must have

tests/rdrand.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use getrandom::Error;
77
#[macro_use]
88
extern crate cfg_if;
9+
#[path = "../src/lazy.rs"]
10+
mod lazy;
911
#[path = "../src/rdrand.rs"]
1012
mod rdrand;
1113
#[path = "../src/util.rs"]

0 commit comments

Comments
 (0)