use core::{arch::asm, cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit, ptr::Pointee};
pub struct AmoOnceRef<'a, T: ?Sized> {
lock: UnsafeCell<u32>,
ptr: UnsafeCell<*const ()>,
meta: UnsafeCell<MaybeUninit<<T as Pointee>::Metadata>>,
_lifetime: PhantomData<&'a ()>,
}
unsafe impl<T: ?Sized> Send for AmoOnceRef<'static, T> {}
unsafe impl<T: ?Sized + Sync> Sync for AmoOnceRef<'static, T> {}
impl<'a, T: ?Sized> AmoOnceRef<'a, T> {
#[inline]
pub const fn new() -> Self {
Self {
lock: UnsafeCell::new(0),
ptr: UnsafeCell::new(core::ptr::null()),
meta: UnsafeCell::new(MaybeUninit::uninit()),
_lifetime: PhantomData,
}
}
pub fn try_call_once(&self, r#ref: &'a T) -> bool {
let ptr = r#ref as *const T;
let locked: u32;
unsafe {
asm!(
"
lw {locked}, ({lock})
bnez {locked}, 1f
amoswap.w.aq {locked}, {one}, ({lock})
1: ",
lock = in(reg) self.lock.get(),
one = in(reg) 1,
locked = out(reg) locked,
);
}
if locked == 0 {
unsafe {
(*self.meta.get()) = MaybeUninit::new(core::ptr::metadata(ptr));
#[cfg(target_pointer_width = "32")]
asm!(
"amoswap.w.rl zero, {src}, ({dst})",
src = in(reg) ptr as *const (),
dst = in(reg) self.ptr.get(),
);
#[cfg(target_pointer_width = "64")]
asm!(
"amoswap.d.rl zero, {src}, ({dst})",
src = in(reg) ptr as *const (),
dst = in(reg) self.ptr.get(),
);
}
true
} else {
false
}
}
#[allow(unused)]
pub fn call_once(&self, r#ref: &'static T) -> Result<&T, &T> {
if self.try_call_once(r#ref) {
Ok(r#ref)
} else {
Err(self.wait())
}
}
pub fn wait(&self) -> &T {
loop {
let ptr = unsafe { *self.ptr.get() };
if !ptr.is_null() {
return unsafe { self.build_ref_unchecked(ptr) };
}
}
}
pub fn get(&self) -> Option<&T> {
let ptr: *const ();
unsafe {
#[cfg(target_pointer_width = "32")]
asm!(" lw {dst}, ({src})
bnez {dst}, 1f
amoadd.w.aq {dst}, zero, ({src})
1: ",
src = in(reg) self.ptr.get(),
dst = out(reg) ptr,
);
#[cfg(target_pointer_width = "64")]
asm!(" ld {dst}, ({src})
bnez {dst}, 1f
amoadd.d.aq {dst}, zero, ({src})
1: ",
src = in(reg) self.ptr.get(),
dst = out(reg) ptr,
);
}
if !ptr.is_null() {
Some(unsafe { self.build_ref_unchecked(ptr) })
} else {
None
}
}
unsafe fn build_ref_unchecked(&self, ptr: *const ()) -> &T {
&*core::ptr::from_raw_parts(ptr, (*self.meta.get()).assume_init())
}
}