#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(named_asm_labels)]
#![cfg_attr(not(feature = "std"), no_std)]
pub use bytemuck;
#[cfg(any(
feature = "std",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
))]
#[inline(always)]
pub fn global<T: bytemuck::Zeroable + Sync + 'static>() -> &'static T {
global_impl::<false, T>()
}
#[cfg(any(
feature = "std",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
))]
#[inline(always)]
pub fn local_global<T: bytemuck::Zeroable + Sync + 'static>() -> &'static T {
global_impl::<true, T>()
}
#[cfg(any(
feature = "std",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
))]
#[inline(always)]
fn global_impl<const LOCAL: bool, T: bytemuck::Zeroable + Sync + 'static>() -> &'static T {
assert!(core::mem::align_of::<T>() <= 4 * 1024);
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
))]
{
unsafe {
core::arch::asm!(
".ifnotdef global_{local}_{id}",
".if {local}",
".local global_{local}_{id}",
".endif",
".comm global_{local}_{id}, {size}, {align}",
".endif",
local = const if LOCAL {1} else {0},
id = sym global::<T>,
size = const core::mem::size_of::<T>(),
align = const core::mem::align_of::<T>(),
options(nomem)
);
let addr: usize;
#[cfg(target_arch = "x86_64")]
{
core::arch::asm!(
"lea {addr}, [rip+global_{local}_{id}]",
addr = out(reg) addr,
local = const if LOCAL {1} else {0},
id = sym global::<T>,
options(pure, nomem)
);
}
#[cfg(target_arch = "aarch64")]
{
core::arch::asm!(
"adrp {addr}, global_{local}_{id}",
"add {addr}, {addr}, :lo12:global_{local}_{id}",
addr = out(reg) addr,
local = const if LOCAL {1} else {0},
id = sym global::<T>,
options(pure, nomem)
);
}
#[cfg(target_arch = "arm")]
{
core::arch::asm!(
"ldr {addr}, =global_{local}_{id}",
addr = out(reg) addr,
local = const if LOCAL {1} else {0},
id = sym global::<T>,
options(pure, nomem)
);
}
#[cfg(target_arch = "x86")]
{
core::arch::asm!(
"call 2f",
"2: pop {addr}",
"lea global_{local}_{id}-2b({addr}), {addr}",
addr = out(reg) addr,
local = const if LOCAL {1} else {0},
id = sym global::<T>,
options(pure, nomem, att_syntax)
);
}
&*(addr as *const _)
}
}
#[cfg(not(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
)))]
{
pub(crate) struct SyncWrapper(*const u8);
unsafe impl Sync for SyncWrapper {}
unsafe impl Send for SyncWrapper {}
use core::any::TypeId;
static MAP: std::sync::RwLock<TypeIdMap<SyncWrapper>> =
std::sync::RwLock::new(TypeIdMap::with_hasher(NoOpTypeIdBuildHasher));
{
let guard = MAP.read().unwrap();
if let Some(value) = guard.get(&TypeId::of::<T>()) {
return unsafe { &*(value.0 as *const T) };
}
}
let mut guard = MAP.write().unwrap();
let value =
guard
.entry(TypeId::of::<T>())
.or_insert(SyncWrapper(
alloc::boxed::Box::into_raw(alloc::boxed::Box::new(
<T as bytemuck::Zeroable>::zeroed(),
)) as *const u8,
));
unsafe { &*(value.0 as *const T) }
}
}
#[cfg(all(
feature = "alloc",
any(
feature = "std",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
)
))]
pub mod non_zeroable_global {
extern crate alloc;
use super::*;
struct Heap<T>(core::sync::atomic::AtomicPtr<T>);
unsafe impl<T> bytemuck::Zeroable for Heap<T> {}
unsafe impl<T: Sync> Sync for Heap<T> {}
#[derive(Debug)]
pub struct AlreadyInitialized;
pub fn init<T: Sync + 'static>(data: T) -> Result<(), AlreadyInitialized> {
use core::sync::atomic::Ordering;
let boxed = alloc::boxed::Box::into_raw(alloc::boxed::Box::new(data));
match global::<Heap<T>>().0.compare_exchange(
core::ptr::null_mut(),
boxed,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => Ok(()),
Err(_) => {
unsafe {
drop(alloc::boxed::Box::from_raw(boxed));
}
Err(AlreadyInitialized)
}
}
}
pub fn get<T: Sync + 'static>() -> Option<&'static T> {
use core::sync::atomic::Ordering;
let data = global::<Heap<T>>().0.load(Ordering::SeqCst);
if data.is_null() {
None
} else {
Some(unsafe { &*data })
}
}
pub fn get_or_init<T: Sync + 'static>(cons: impl Fn() -> T) -> &'static T {
use core::sync::atomic::Ordering;
let data = global::<Heap<T>>().0.load(Ordering::SeqCst);
if data.is_null() {
let _ = init::<_>(cons());
get::<_>().unwrap()
} else {
unsafe { &*data }
}
}
}
#[cfg(all(
feature = "alloc",
any(
feature = "std",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
)
))]
#[macro_export]
macro_rules! generic_static {
{static $ident:ident $(: &$type:ty)? = &$init:expr;} => {
#[allow(non_snake_case)]
let $ident $(: &'static $type)? = {
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
))] {
extern crate alloc;
let init = ||$init;
fn assert_sync_static<T: Sync + 'static>(_: &impl FnOnce() -> T) {}
assert_sync_static(&init);
fn make<Key: 'static, Value: Sync + 'static>(_: Key, _: &impl FnOnce()->Value)
-> &'static ::core::sync::atomic::AtomicPtr<Value> {
struct Holder<T, D> {
_marker: core::marker::PhantomData<T>,
value: core::sync::atomic::AtomicPtr<D>
}
unsafe impl<T, D> $crate::bytemuck::Zeroable for Holder<T,D>{}
unsafe impl<T, D> Sync for Holder<T,D>{}
&$crate::global::<Holder<Key, Value>>().value
}
let ptr = make(||(), &init);
let data = ptr.load(::core::sync::atomic::Ordering::SeqCst);
if data.is_null() {
let boxed = alloc::boxed::Box::into_raw(alloc::boxed::Box::new(init()));
if ptr
.compare_exchange(
::core::ptr::null_mut(),
boxed,
::core::sync::atomic::Ordering::SeqCst,
::core::sync::atomic::Ordering::SeqCst,
)
.is_err()
{
unsafe {
drop(alloc::boxed::Box::from_raw(boxed));
}
}
unsafe { &*boxed }
} else {
unsafe { &*data }
}
}
#[cfg(not(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm",
target_arch = "x86"
)))] {
#[cfg(not(feature = "std"))]
compile_error!("Unsupported platform, enable feature \"std\" to enable fallback");
struct SyncWrapper(*const u8);
unsafe impl Sync for SyncWrapper {}
unsafe impl Send for SyncWrapper {}
fn id<T: 'static>(_: T) -> core::any::TypeId {
core::any::TypeId::of::<T>()
}
let id = id(||());
static MAP: ::std::sync::RwLock<$crate::TypeIdMap<SyncWrapper>> =
::std::sync::RwLock::new($crate::TypeIdMap::with_hasher(
$crate::NoOpTypeIdBuildHasher,
));
{
let guard = MAP.read().unwrap();
if let Some(value) = guard.get(&id) {
break 'block unsafe { &*(value.0 as *const _) };
}
}
let mut guard = MAP.write().unwrap();
let value = guard
.entry(id)
.or_insert(SyncWrapper(
alloc::boxed::Box::into_raw(alloc::boxed::Box::new($init)) as *const u8,
));
unsafe { &*(value.0 as *const _) }
}
};
};
}
#[cfg(feature = "std")]
pub use with_std::*;
#[cfg(feature = "std")]
mod with_std {
use core::any::TypeId;
use core::hash::{BuildHasher, Hasher};
use std::collections::HashMap;
pub type TypeIdMap<T> = HashMap<TypeId, T, NoOpTypeIdBuildHasher>;
#[derive(Default)]
pub struct NoOpTypeIdBuildHasher;
impl BuildHasher for NoOpTypeIdBuildHasher {
type Hasher = NoOpTypeIdHasher;
fn build_hasher(&self) -> Self::Hasher {
NoOpTypeIdHasher(0)
}
}
#[doc(hidden)]
#[derive(Default)]
pub struct NoOpTypeIdHasher(u64);
impl Hasher for NoOpTypeIdHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
self.0 = bytes.iter().fold(self.0, |hash, b| {
hash.rotate_left(8).wrapping_add(*b as u64)
});
}
fn write_u64(&mut self, i: u64) {
self.0 = i
}
}
}
#[cfg(test)]
mod test {
use std::{
any::TypeId,
hash::{Hash, Hasher},
};
#[test]
fn test_local_global() {
use crate::local_global;
use core::sync::atomic::{AtomicI32, AtomicI64, Ordering};
let a = local_global::<AtomicI32>();
let b = local_global::<AtomicI64>();
assert_eq!(a.load(Ordering::Relaxed), 0);
a.store(69, Ordering::Relaxed);
assert_eq!(a.load(Ordering::Relaxed), 69);
assert_eq!(b.load(Ordering::Relaxed), 0);
assert_eq!(*local_global::<i64>(), 0);
core::hint::black_box(local_global::<AtomicI64>());
}
#[test]
fn test_macro() {
use core::sync::atomic::{AtomicI32, Ordering};
#[allow(clippy::extra_unused_type_parameters)]
fn get_and_inc<T: 'static>() -> i32 {
generic_static!(
static BLUB: &AtomicI32 = &AtomicI32::new(1);
);
let value = BLUB.load(Ordering::Relaxed);
BLUB.fetch_add(1, Ordering::Relaxed);
value
}
assert_eq!(get_and_inc::<bool>(), 1);
assert_eq!(get_and_inc::<bool>(), 2);
assert_eq!(get_and_inc::<i32>(), 1);
assert_eq!(get_and_inc::<bool>(), 3);
generic_static!(
static FOO_1: &AtomicI32 = &AtomicI32::new(0);
);
generic_static!(
static FOO_2: &AtomicI32 = &AtomicI32::new(69);
);
assert_eq!(FOO_1.load(Ordering::Relaxed), 0);
assert_eq!(FOO_2.load(Ordering::Relaxed), 69);
FOO_1.store(1, Ordering::Relaxed);
FOO_2.store(2, Ordering::Relaxed);
assert_eq!(FOO_1.load(Ordering::Relaxed), 1);
assert_eq!(FOO_2.load(Ordering::Relaxed), 2);
}
#[test]
fn test_macro_types() {
fn generic<T: Sync + 'static>(t: T) {
generic_static! {
static _FOO = &t;
}
}
generic(0);
generic(true);
}
#[test]
fn type_id_hash() {
TypeId::of::<()>().hash(&mut {
struct H;
impl Hasher for H {
fn finish(&self) -> u64 {
0
}
fn write(&mut self, _: &[u8]) {
unimplemented!()
}
fn write_u64(&mut self, _: u64) {}
}
H
});
}
}