| //! Helpers for runtime target feature detection that are shared across architectures. |
| |
| // `AtomicU32` is preferred for a consistent size across targets. |
| #[cfg(all(target_has_atomic = "ptr", not(target_has_atomic = "32")))] |
| compile_error!("currently all targets that support `AtomicPtr` also support `AtomicU32`"); |
| |
| use core::sync::atomic::{AtomicU32, Ordering}; |
| |
| /// Given a list of identifiers, assign each one a unique sequential single-bit mask. |
| #[allow(unused_macros)] |
| macro_rules! unique_masks { |
| ($ty:ty, $($name:ident,)+) => { |
| #[cfg(test)] |
| pub const ALL: &[$ty] = &[$($name),+]; |
| #[cfg(test)] |
| pub const NAMES: &[&str] = &[$(stringify!($name)),+]; |
| |
| unique_masks!(@one; $ty; 0; $($name,)+); |
| }; |
| // Matcher for a single value |
| (@one; $_ty:ty; $_idx:expr;) => {}; |
| (@one; $ty:ty; $shift:expr; $name:ident, $($tail:tt)*) => { |
| pub const $name: $ty = 1 << $shift; |
| // Ensure the top bit is not used since it stores initialized state. |
| const _: () = assert!($name != (1 << (<$ty>::BITS - 1))); |
| // Increment the shift and invoke the next |
| unique_masks!(@one; $ty; $shift + 1; $($tail)*); |
| }; |
| } |
| |
| /// Call `init` once to choose an implementation, then use it for the rest of the program. |
| /// |
| /// - `sig` is the function type. |
| /// - `init` is an expression called at startup that chooses an implementation and returns a |
| /// function pointer. |
| /// - `call` is an expression to call a function returned by `init`, encapsulating any safety |
| /// preconditions. |
| /// |
| /// The type `Func` is available in `init` and `call`. |
| /// |
| /// This is effectively our version of an ifunc without linker support. Note that `init` may be |
| /// called more than once until one completes. |
| #[allow(unused_macros)] // only used on some architectures |
| macro_rules! select_once { |
| ( |
| sig: fn($($arg:ident: $ArgTy:ty),*) -> $RetTy:ty, |
| init: $init:expr, |
| call: $call:expr, |
| ) => {{ |
| use core::mem; |
| use core::sync::atomic::{AtomicPtr, Ordering}; |
| |
| type Func = unsafe fn($($arg: $ArgTy),*) -> $RetTy; |
| |
| /// Stores a pointer that is immediately jumped to. By default it is an init function |
| /// that sets FUNC to something else. |
| static FUNC: AtomicPtr<()> = AtomicPtr::new((initializer as Func) as *mut ()); |
| |
| /// Run once to set the function that will be used for all subsequent calls. |
| fn initializer($($arg: $ArgTy),*) -> $RetTy { |
| // Select an implementation, ensuring a 'static lifetime. |
| let fn_ptr: Func = $init(); |
| FUNC.store(fn_ptr as *mut (), Ordering::Relaxed); |
| |
| // Forward the call to the selected function. |
| $call(fn_ptr) |
| } |
| |
| let raw: *mut () = FUNC.load(Ordering::Relaxed); |
| |
| // SAFETY: will only ever be `initializer` or another function pointer that has the |
| // 'static lifetime. |
| let fn_ptr: Func = unsafe { mem::transmute::<*mut (), Func>(raw) }; |
| |
| $call(fn_ptr) |
| }} |
| } |
| |
| #[allow(unused_imports)] |
| pub(crate) use {select_once, unique_masks}; |
| |
| use crate::support::cold_path; |
| |
| /// Helper for working with bit flags, based on `bitflags`. |
| #[derive(Clone, Copy, Debug, PartialEq)] |
| pub struct Flags(u32); |
| |
| #[allow(dead_code)] // only used on some architectures |
| impl Flags { |
| /// No bits set. |
| pub const fn empty() -> Self { |
| Self(0) |
| } |
| |
| /// Create with bits already set. |
| pub const fn from_bits(val: u32) -> Self { |
| Self(val) |
| } |
| |
| /// Get the integer representation. |
| pub fn bits(&self) -> u32 { |
| self.0 |
| } |
| |
| /// Set any bits in `mask`. |
| pub fn insert(&mut self, mask: u32) { |
| self.0 |= mask; |
| } |
| |
| /// Check whether the mask is set. |
| pub fn contains(&self, mask: u32) -> bool { |
| self.0 & mask == mask |
| } |
| |
| /// Check whether the nth bit is set. |
| pub fn test_nth(&self, bit: u32) -> bool { |
| debug_assert!(bit < u32::BITS, "bit index out-of-bounds"); |
| self.0 & (1 << bit) != 0 |
| } |
| } |
| |
| /// Load flags from an atomic value. If the flags have not yet been initialized, call `init` |
| /// to do so. |
| /// |
| /// Note that `init` may run more than once. |
| #[allow(dead_code)] // only used on some architectures |
| pub fn get_or_init_flags_cache(cache: &AtomicU32, init: impl FnOnce() -> Flags) -> Flags { |
| // The top bit is used to indicate that the values have already been set once. |
| const INITIALIZED: u32 = 1 << 31; |
| |
| // Relaxed ops are sufficient since the result should always be the same. |
| let mut flags = Flags::from_bits(cache.load(Ordering::Relaxed)); |
| |
| if !flags.contains(INITIALIZED) { |
| // Without this, `init` is inlined and the bit check gets wrapped in `init`'s lengthy |
| // prologue/epilogue. Cold pathing gives a preferable load->test->?jmp->ret. |
| cold_path(); |
| |
| flags = init(); |
| debug_assert!( |
| !flags.contains(INITIALIZED), |
| "initialized bit shouldn't be set" |
| ); |
| flags.insert(INITIALIZED); |
| cache.store(flags.bits(), Ordering::Relaxed); |
| } |
| |
| flags |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn unique_masks() { |
| unique_masks! { |
| u32, |
| V0, |
| V1, |
| V2, |
| } |
| assert_eq!(V0, 1u32 << 0); |
| assert_eq!(V1, 1u32 << 1); |
| assert_eq!(V2, 1u32 << 2); |
| assert_eq!(ALL, [V0, V1, V2]); |
| assert_eq!(NAMES, ["V0", "V1", "V2"]); |
| } |
| |
| #[test] |
| fn flag_cache_is_used() { |
| // Sanity check that flags are only ever set once |
| static CACHE: AtomicU32 = AtomicU32::new(0); |
| |
| let mut f1 = Flags::from_bits(0x1); |
| let f2 = Flags::from_bits(0x2); |
| |
| let r1 = get_or_init_flags_cache(&CACHE, || f1); |
| let r2 = get_or_init_flags_cache(&CACHE, || f2); |
| |
| f1.insert(1 << 31); // init bit |
| |
| assert_eq!(r1, f1); |
| assert_eq!(r2, f1); |
| } |
| |
| #[test] |
| fn select_cache_is_used() { |
| // Sanity check that cache is used |
| static CALLED: AtomicU32 = AtomicU32::new(0); |
| |
| fn inner() { |
| fn nop() {} |
| |
| select_once! { |
| sig: fn() -> (), |
| init: || { |
| CALLED.fetch_add(1, Ordering::Relaxed); |
| nop |
| }, |
| call: |fn_ptr: Func| unsafe { fn_ptr() }, |
| } |
| } |
| |
| // `init` should only have been called once. |
| inner(); |
| assert_eq!(CALLED.load(Ordering::Relaxed), 1); |
| inner(); |
| assert_eq!(CALLED.load(Ordering::Relaxed), 1); |
| } |
| } |