use core::alloc::Layout;
use core::cell::UnsafeCell;
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicU8, Ordering};
use super::{SectionRange, SyncUnsafeCell};
#[doc(hidden)]
#[macro_export]
#[allow(unknown_lints, edition_2024_expr_fragment_specifier)]
macro_rules! __get_section_wasm {
(movable, name=$name:tt, type=$generic_ty:ty) => {
$crate::__get_section_wasm!(@emit
($name),
($crate::__support::wasm::LinkSectionMovableInfo),
($crate::__support::wasm::LinkSectionMovableInfo::new::<$generic_ty>(__LINK_SECTION_NAME)),
($crate::__support::MovableBounds)
);
};
($section_type:ident, name=$name:tt, type=$generic_ty:ty) => {
$crate::__get_section_wasm!(@emit
($name),
($crate::__support::wasm::LinkSectionInfo),
($crate::__support::wasm::LinkSectionInfo::new::<$generic_ty>(
__LINK_SECTION_NAME,
$crate::__support::wasm::section_has_slot!($section_type),
)),
($crate::__support::Bounds)
);
};
(@emit
($name:tt),
($info_ty:ty),
($info_init:expr),
($bounds_ty:path)
) => {
{
static __LINK_SECTION_NAME: &'static str = $crate::__support::section_name!(
string item data bare $name
);
$crate::__support::add_section_link_attribute!(
item data bounds $name
#[export_name = __]
#[used]
static __LINK_SECTION_INFO: $crate::__support::wasm::LinkSectionInfoLock<$info_ty> =
$crate::__support::wasm::LinkSectionInfoLock::new($info_init);
);
#[link_section = ".init_array.1"]
#[used]
#[allow(non_snake_case)]
static __LINK_SECTION_FLATTEN_FN_REF: extern "C" fn() = {
extern "C" fn __LINK_SECTION_FLATTEN_FN() {
unsafe {
$crate::__support::wasm::flatten(
&raw const __LINK_SECTION_INFO,
);
}
}
__LINK_SECTION_FLATTEN_FN
};
unsafe { <$bounds_ty>::new(&raw const __LINK_SECTION_INFO) }
}
};
}
pub use crate::__get_section_wasm as get_section;
#[doc(hidden)]
#[macro_export]
macro_rules! __import_section_info {
($info_ty:ty, $local_name:ident, $section:tt) => {
$crate::__add_section_link_attribute!(
item data bounds $section
#[link_name = __]
extern "C" {
static $local_name: $crate::__support::wasm::LinkSectionInfoLock<$info_ty>;
}
);
};
}
pub use crate::__import_section_info as import_section_info;
#[doc(hidden)]
#[macro_export]
macro_rules! __section_has_slot {
(reference) => {
true
};
($other:ident) => {
false
};
}
pub use crate::__section_has_slot as section_has_slot;
crate::__def_section_name! {
__section_name_wasm,
{
data bare => (".data" ".link_section.") __ ();
data section => (".data" ".link_section.") __ ();
code bare => (".text" ".link_section.") __ ();
code section => (".text" ".link_section.") __ ();
data bounds => (".data" ".link_section.") __ (".bounds");
}
AUXILIARY = ".";
REFS = ".r.";
MAX_LENGTH = 16;
HASH_LENGTH = 6;
VALID_SECTION_CHARS = "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
}
#[cfg(not(target_family = "wasm"))]
#[doc(hidden)]
#[macro_export]
#[allow(unknown_lints, edition_2024_expr_fragment_specifier)]
macro_rules! __register_wasm_item {
($($args:tt)*) => {};
}
#[cfg(target_family = "wasm")]
#[doc(hidden)]
#[macro_export]
#[allow(unknown_lints, edition_2024_expr_fragment_specifier)]
macro_rules! __register_wasm_item {
(movable, type=$ty:ty, value=$value:expr, slot=$slot:expr, section=$section:tt) => {
$crate::__register_wasm_item!(@emit
info_ty = ($crate::__support::wasm::LinkSectionMovableInfo),
ty = ($ty),
meta = ($crate::__support::wasm::LinkMetaSlot),
new = ($value, $slot as *const ()),
section = $section
);
};
($section_type:ident, type=$ty:ty, value=$value:expr, ref=$ident:ident, section=$section:tt) => {
$crate::__register_wasm_item!(@emit
info_ty = ($crate::__support::wasm::LinkSectionInfo),
ty = ($ty),
meta = ($crate::__support::wasm::LinkMetaSlot),
new = ($value, &$ident as *const _ as *const ()),
section = $section
);
};
($section_type:ident, type=$ty:ty, value=$value:expr, section=$section:tt) => {
$crate::__register_wasm_item!(@emit
info_ty = ($crate::__support::wasm::LinkSectionInfo),
ty = ($ty),
meta = ($crate::__support::wasm::LinkMeta),
new = ($value),
section = $section
);
};
(@emit
info_ty = ($info_ty:ty),
ty = ($ty:ty),
meta = ($meta:ty),
new = ($($new_args:expr),*),
section = $section:tt
) => {
#[used]
static __LINK_SECTION_CELL: $crate::__support::wasm::LinkCell<$ty, $meta> =
<$crate::__support::wasm::LinkCell<$ty, $meta>>::new($($new_args),*);
$crate::__import_section_info!(
$info_ty, __LINK_SECTION_INFO, $section
);
#[link_section = ".init_array.0"]
#[used] #[allow(non_snake_case)]
static __LINK_SECTION_ITEM_FN_REF: extern "C" fn() = {
extern "C" fn __LINK_SECTION_ITEM_FN() {
unsafe {
$crate::__support::wasm::register(
&raw const __LINK_SECTION_INFO,
__LINK_SECTION_CELL.as_cell_ptr(),
);
}
}
__LINK_SECTION_ITEM_FN
};
};
}
#[repr(u8)]
enum LinkSectionState {
Gathering = 0,
Flattened = 1,
}
#[repr(u8)]
enum LockState {
Unlocked = 0,
Locked = 1,
}
#[repr(C)]
pub struct LinkMeta {
next: UnsafeCell<*mut u8>,
}
#[repr(C)]
pub struct LinkMetaSlot {
next: UnsafeCell<*mut u8>,
slot: *const (),
}
const UNREGISTERED: *mut u8 = ::core::ptr::dangling_mut::<u8>();
const SLOT_OFFSET: usize = core::mem::size_of::<*mut u8>();
const fn meta_offset(size_of: usize) -> usize {
let align = core::mem::align_of::<*mut u8>();
(size_of + align - 1) & !(align - 1)
}
#[repr(C)]
pub struct LinkCell<T: 'static, M> {
value: UnsafeCell<T>,
meta: M,
}
unsafe impl<T: 'static + Sync, M> Sync for LinkCell<T, M> {}
impl<T: 'static> LinkCell<T, LinkMeta> {
pub const fn new(value: T) -> Self {
Self {
value: UnsafeCell::new(value),
meta: LinkMeta {
next: UnsafeCell::new(UNREGISTERED),
},
}
}
}
impl<T: 'static> LinkCell<T, LinkMetaSlot> {
pub const fn new(value: T, slot: *const ()) -> Self {
Self {
value: UnsafeCell::new(value),
meta: LinkMetaSlot {
next: UnsafeCell::new(UNREGISTERED),
slot,
},
}
}
}
impl<T: 'static, M> LinkCell<T, M> {
pub const fn as_cell_ptr(&self) -> *mut u8 {
self as *const LinkCell<T, M> as *mut u8
}
}
#[derive(Clone, Copy)]
pub struct LinkSection<I>(NonNull<LinkSectionInfoLock<I>>);
impl<I> LinkSection<I> {
pub const unsafe fn new(info_ptr: *const LinkSectionInfoLock<I>) -> Self {
Self(unsafe { NonNull::new_unchecked(info_ptr as *mut _) })
}
#[inline]
pub fn lock(&self) -> LinkSectionLockGuard<'_, I> {
let lock_state = unsafe { self.lock_ref() };
if lock_state
.compare_exchange(
LockState::Unlocked as _,
LockState::Locked as _,
Ordering::Acquire,
Ordering::Acquire,
)
.is_err()
{
panic!("Link section already locked: concurrent access is not supported");
}
LinkSectionLockGuard(lock_state, unsafe { self.as_mut() })
}
#[inline(always)]
unsafe fn lock_ref(&self) -> &AtomicU8 {
unsafe {
ptr::addr_of!((*self.0.as_ptr()).lock)
.as_ref()
.unwrap_unchecked()
}
}
#[inline(always)]
#[allow(clippy::mut_from_ref)]
unsafe fn as_mut(&self) -> &mut I {
unsafe {
let unsafe_cell = ptr::addr_of!((*self.0.as_ptr()).info);
UnsafeCell::raw_get(unsafe_cell).as_mut().unwrap_unchecked()
}
}
}
pub struct LinkSectionLockGuard<'a, I>(&'a AtomicU8, &'a mut I);
impl<'a, I> core::ops::Deref for LinkSectionLockGuard<'a, I> {
type Target = I;
fn deref(&self) -> &Self::Target {
self.1
}
}
impl<'a, I> core::ops::DerefMut for LinkSectionLockGuard<'a, I> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.1
}
}
impl<'a, I> Drop for LinkSectionLockGuard<'a, I> {
fn drop(&mut self) {
self.0.store(LockState::Unlocked as _, Ordering::Release);
}
}
#[repr(C)]
pub struct LinkSectionInfoLock<I> {
lock: AtomicU8,
info: UnsafeCell<I>,
}
unsafe impl<I> Sync for LinkSectionInfoLock<I> {}
impl<I> LinkSectionInfoLock<I> {
pub const fn new(info: I) -> Self {
Self {
lock: AtomicU8::new(LockState::Unlocked as _),
info: UnsafeCell::new(info),
}
}
}
#[repr(C)]
pub struct LinkSectionInfo {
state: u8,
has_slot: bool,
head: *mut u8,
count: usize,
start: *const (),
end: *const (),
size_of: usize,
align_of: usize,
name: &'static str,
}
impl LinkSectionInfo {
pub const fn new<T: 'static>(name: &'static str, has_slot: bool) -> Self {
Self {
state: LinkSectionState::Gathering as _,
has_slot,
head: ptr::null_mut(),
count: 0,
start: ptr::null(),
end: ptr::null(),
size_of: ::core::mem::size_of::<T>(),
align_of: ::core::mem::align_of::<T>(),
name,
}
}
unsafe fn push(&mut self, cell: *mut u8) {
unsafe {
let next = cell.add(meta_offset(self.size_of)) as *mut *mut u8;
if *next != UNREGISTERED {
return;
}
if self.state == LinkSectionState::Flattened as u8 {
panic!(
"link section '{}' flattened before all items were registered \
(reading section bounds from #[ctor(priority = 0)]? use >= 1)",
self.name
);
}
*next = self.head;
}
self.head = cell;
self.count += 1;
}
fn materialise_with(&mut self, mut on_slot: impl FnMut(usize, *const ())) {
if self.state == LinkSectionState::Flattened as u8 {
return;
}
let count = self.count;
if count == 0 {
let dangling = NonNull::<u8>::dangling().as_ptr() as *const ();
self.start = dangling;
self.end = dangling;
self.state = LinkSectionState::Flattened as u8;
return;
}
let size_of = self.size_of;
let bytes = count
.checked_mul(size_of)
.unwrap_or_else(|| panic!("link section '{}' size overflow", self.name));
let arr =
unsafe { allocate(Layout::from_size_align(bytes, self.align_of).unwrap()) as *mut u8 };
if arr.is_null() {
panic!("link section '{}' allocation failed", self.name);
}
let meta_offset = meta_offset(size_of);
let mut cell = self.head;
let mut i = count;
while !cell.is_null() {
i -= 1;
unsafe {
let dst = arr.add(i * size_of);
ptr::copy_nonoverlapping(cell, dst, size_of);
let meta = cell.add(meta_offset);
if self.has_slot {
let slot = *(meta.add(SLOT_OFFSET) as *const *const ());
if !slot.is_null() {
ptr::write(
UnsafeCell::raw_get(slot as *const UnsafeCell<*const ()>),
dst as *const (),
);
}
on_slot(i, slot);
}
cell = *(meta as *const *mut u8);
}
}
self.start = arr as *const ();
self.end = unsafe { arr.add(bytes) } as *const ();
self.state = LinkSectionState::Flattened as u8;
}
fn range(&self) -> SectionRange {
SectionRange::new(self.start, self.end)
}
}
#[repr(C)]
pub struct LinkSectionMovableInfo {
base: LinkSectionInfo,
backrefs_start: *const (),
backrefs_end: *const (),
}
const BACKREF_SIZE_OF: usize = ::core::mem::size_of::<crate::MovableBackref<()>>();
const BACKREF_ALIGN_OF: usize = ::core::mem::align_of::<crate::MovableBackref<()>>();
impl LinkSectionMovableInfo {
pub const fn new<T: 'static>(name: &'static str) -> Self {
Self {
base: LinkSectionInfo::new::<T>(name, true),
backrefs_start: ptr::null(),
backrefs_end: ptr::null(),
}
}
fn backrefs_range(&self) -> SectionRange {
SectionRange::new(self.backrefs_start, self.backrefs_end)
}
}
#[doc(hidden)]
pub trait WasmSection {
fn info_mut(&mut self) -> &mut LinkSectionInfo;
fn materialise(&mut self);
}
impl WasmSection for LinkSectionInfo {
fn info_mut(&mut self) -> &mut LinkSectionInfo {
self
}
fn materialise(&mut self) {
LinkSectionInfo::materialise_with(self, |_, _| {});
}
}
impl WasmSection for LinkSectionMovableInfo {
fn info_mut(&mut self) -> &mut LinkSectionInfo {
&mut self.base
}
fn materialise(&mut self) {
if self.base.state == LinkSectionState::Flattened as u8 {
return;
}
let count = self.base.count;
let name = self.base.name;
let (backrefs, backref_bytes) = if count == 0 {
let dangling = NonNull::<u8>::dangling().as_ptr() as *const ();
(dangling as *mut u8, 0)
} else {
let backref_bytes = count
.checked_mul(BACKREF_SIZE_OF)
.unwrap_or_else(|| panic!("link section '{}' backref size overflow", name));
let backrefs = unsafe {
allocate(Layout::from_size_align(backref_bytes, BACKREF_ALIGN_OF).unwrap())
as *mut u8
};
if backrefs.is_null() {
panic!("link section '{}' backref allocation failed", name);
}
(backrefs, backref_bytes)
};
self.base.materialise_with(|i, slot| unsafe {
let br = (backrefs as *mut crate::MovableBackref<()>).add(i);
ptr::write(
br,
crate::MovableBackref::new(slot as *const UnsafeCell<*const ()>),
);
});
self.backrefs_start = backrefs as *const ();
self.backrefs_end = unsafe { backrefs.add(backref_bytes) } as *const ();
}
}
pub unsafe fn register<S: WasmSection>(info_ptr: *const LinkSectionInfoLock<S>, cell: *mut u8) {
let link_section = unsafe { LinkSection::new(info_ptr) };
unsafe { link_section.lock().info_mut().push(cell) };
}
pub unsafe fn flatten<S: WasmSection>(info_ptr: *const LinkSectionInfoLock<S>) {
let link_section = unsafe { LinkSection::new(info_ptr) };
link_section.lock().materialise();
}
#[cfg(target_family = "wasm")]
unsafe fn allocate(layout: Layout) -> *mut () {
use alloc::alloc::alloc;
unsafe { alloc(layout) as _ }
}
#[cfg(not(target_family = "wasm"))]
unsafe fn allocate(_layout: Layout) -> *mut () {
unreachable!("placeholder for non-WASM platforms")
}
pub struct Bounds(LinkSection<LinkSectionInfo>);
impl Bounds {
pub const unsafe fn new(info_ptr: *const LinkSectionInfoLock<LinkSectionInfo>) -> Self {
unsafe { Self(LinkSection::new(info_ptr)) }
}
pub fn range(&self) -> SectionRange {
let mut info = self.0.lock();
info.materialise();
info.range()
}
}
pub struct MovableBounds(LinkSection<LinkSectionMovableInfo>);
impl MovableBounds {
pub const unsafe fn new(info_ptr: *const LinkSectionInfoLock<LinkSectionMovableInfo>) -> Self {
unsafe { Self(LinkSection::new(info_ptr)) }
}
pub fn range(&self) -> SectionRange {
let mut info = self.0.lock();
info.materialise();
info.base.range()
}
pub fn backrefs_range(&self) -> SectionRange {
let mut info = self.0.lock();
info.materialise();
info.backrefs_range()
}
}
#[repr(C)]
pub struct RefStorage<T: 'static> {
ptr: UnsafeCell<*const T>,
section: *const LinkSectionInfoLock<LinkSectionInfo>,
}
impl<T> RefStorage<T> {
pub const fn new(section: *const LinkSectionInfoLock<LinkSectionInfo>) -> Self {
Self {
ptr: UnsafeCell::new(ptr::null()),
section,
}
}
pub fn as_ptr(&self) -> *const T {
unsafe { *self.ptr.get() }
}
pub fn get(&self) -> &T {
unsafe {
if (*self.ptr.get()).is_null() {
flatten(self.section);
}
self.as_ptr().as_ref().expect("Ref not initialized")
}
}
}
#[repr(C)]
pub struct MovableRefStorage<T: 'static> {
slot: SyncUnsafeCell<*const T>,
section: *const LinkSectionInfoLock<LinkSectionMovableInfo>,
}
impl<T> MovableRefStorage<T> {
pub const fn new(
ptr: *const T,
section: *const LinkSectionInfoLock<LinkSectionMovableInfo>,
) -> Self {
Self {
slot: SyncUnsafeCell::new(ptr),
section,
}
}
pub const fn as_ptr(&self) -> *const T {
unsafe { *self.slot.get() }
}
pub fn get(&self) -> &T {
unsafe {
if (*self.slot.get()).is_null() {
flatten(self.section);
}
self.as_ptr().as_ref().expect("MovableRef not initialized")
}
}
}