use super::{
AnyHandleAndEntity,
DataAddr,
ElemAddr,
ExportsIter,
Extern,
FuncAddr,
GlobalAddr,
HandleAndEntity,
InstanceEntityBuilder,
InstanceLayout,
MemoryAddr,
TableAddr,
ThinPtr,
};
use crate::{
ElementSegment,
Func,
Global,
Memory,
Module,
Table,
collections::Map,
memory::DataSegment,
store::StoreInner,
};
use alloc::{
alloc::{alloc, handle_alloc_error},
boxed::Box,
vec::Vec,
};
use core::{
alloc::Layout,
mem::{needs_drop, offset_of},
ptr::{self, NonNull},
};
#[derive(Debug)]
#[repr(C)]
pub struct InstanceEntity {
header: InstanceEntityHeader,
handles: [AnyHandleAndEntity],
}
#[derive(Debug)]
#[repr(C)]
struct InstanceEntityHeader {
len_handles: u32,
state: InstanceState,
exports: Map<Box<str>, Extern>,
layout: InstanceLayout,
}
const HANDLES_OFFSET: usize = {
#[repr(C)]
struct HandlesOffset {
header: InstanceEntityHeader,
handles: [AnyHandleAndEntity; 0],
}
offset_of!(HandlesOffset, handles)
};
const _: () = assert!(!needs_drop::<AnyHandleAndEntity>());
#[derive(Debug, Copy, Clone)]
pub enum InstanceState {
Uninitialized,
Initialized,
WarmedUp,
}
fn too_many_handles(len: usize) -> ! {
panic!("out of memory: too many instance handles: {len}")
}
fn layout_for_handles(len: usize) -> Layout {
let Ok(array) = Layout::array::<AnyHandleAndEntity>(len) else {
too_many_handles(len)
};
let Ok((layout, offset)) = Layout::new::<InstanceEntityHeader>().extend(array) else {
too_many_handles(len)
};
debug_assert_eq!(offset, HANDLES_OFFSET);
layout.pad_to_align()
}
fn handles_offset(entity: &InstanceEntity) -> usize {
(&raw const entity.handles).cast::<u8>().addr() - ptr::from_ref(entity).cast::<u8>().addr()
}
impl InstanceEntityHeader {
#[inline]
fn len_handles(&self) -> u32 {
self.len_handles
}
#[inline]
fn layout(&self) -> &InstanceLayout {
&self.layout
}
}
impl InstanceEntity {
pub fn new_uninit() -> Box<Self> {
Self::alloc(
InstanceState::Uninitialized,
Map::new(),
InstanceLayout::uninit(),
[],
)
}
pub(super) fn new_init<I>(
exports: Map<Box<str>, Extern>,
layout: InstanceLayout,
handles: I,
) -> Box<Self>
where
I: IntoIterator<Item = AnyHandleAndEntity, IntoIter: ExactSizeIterator>,
{
Self::alloc(InstanceState::Initialized, exports, layout, handles)
}
fn alloc<I>(
state: InstanceState,
exports: Map<Box<str>, Extern>,
layout: InstanceLayout,
handles: I,
) -> Box<Self>
where
I: IntoIterator<Item = AnyHandleAndEntity>,
{
let handles = Vec::from_iter(handles);
let len = handles.len();
let Ok(len_handles) = u32::try_from(len) else {
too_many_handles(len)
};
let header = InstanceEntityHeader {
len_handles,
state,
exports,
layout,
};
let alloc_layout = layout_for_handles(len);
let Some(ptr) = NonNull::new(unsafe { alloc(alloc_layout) }) else {
handle_alloc_error(alloc_layout)
};
unsafe {
ptr.cast::<InstanceEntityHeader>().write(header);
ptr.byte_add(HANDLES_OFFSET)
.cast::<AnyHandleAndEntity>()
.copy_from_nonoverlapping(NonNull::from(handles.as_slice()).cast(), len);
}
let ptr = ptr::slice_from_raw_parts_mut(ptr.as_ptr().cast::<AnyHandleAndEntity>(), len)
as *mut InstanceEntity;
let entity = unsafe { Box::from_raw(ptr) };
assert_eq!(
handles_offset(&entity),
HANDLES_OFFSET,
"unexpected offset of the trailing instance handles buffer",
);
debug_assert_eq!(Layout::for_value::<Self>(&entity), alloc_layout);
entity
}
pub fn build(module: &Module) -> InstanceEntityBuilder {
InstanceEntityBuilder::new(module)
}
pub fn is_initialized(&self) -> bool {
matches!(
self.header.state,
InstanceState::Initialized | InstanceState::WarmedUp
)
}
#[inline]
pub fn layout(&self) -> &InstanceLayout {
self.header.layout()
}
#[inline]
fn entry(&self, addr: impl Into<u32>) -> Option<&AnyHandleAndEntity> {
self.handles.get(addr.into() as usize)
}
pub fn warmup(&mut self, store: &mut StoreInner) {
assert!(
!matches!(self.header.state, InstanceState::Uninitialized),
"must not warm-up the cache of an uninitialized instance",
);
if matches!(self.header.state, InstanceState::WarmedUp) {
return;
}
self.header.state = InstanceState::WarmedUp;
let layout = self.header.layout;
macro_rules! warmup {
($addr:ident, $handle:ty) => {{
let mut index = 0;
while let Some(addr) = layout.$addr(index) {
let entry = &mut self.handles[u32::from(addr) as usize];
let entry = unsafe { entry.typed_mut::<$handle>() };
entry.warmup(store);
index += 1;
}
}};
}
warmup!(memory_addr, Memory);
warmup!(global_addr, Global);
warmup!(table_addr, Table);
warmup!(func_addr, Func);
warmup!(elem_addr, ElementSegment);
warmup!(data_addr, DataSegment);
}
#[inline]
pub fn get_memory(&self, addr: MemoryAddr) -> Option<Memory> {
let entry = self.entry(addr)?;
Some(unsafe { entry.typed_ref::<Memory>() }.handle())
}
#[inline]
pub fn get_table(&self, addr: TableAddr) -> Option<Table> {
let entry = self.entry(addr)?;
Some(unsafe { entry.typed_ref::<Table>() }.handle())
}
#[inline]
pub fn get_global(&self, addr: GlobalAddr) -> Option<Global> {
let entry = self.entry(addr)?;
Some(unsafe { entry.typed_ref::<Global>() }.handle())
}
#[inline]
pub fn get_func(&self, addr: FuncAddr) -> Option<Func> {
let entry = self.entry(addr)?;
Some(unsafe { entry.typed_ref::<Func>() }.handle())
}
#[inline]
pub fn get_data(&self, addr: DataAddr) -> Option<DataSegment> {
let entry = self.entry(addr)?;
Some(unsafe { entry.typed_ref::<DataSegment>() }.handle())
}
#[inline]
pub fn get_elem(&self, addr: ElemAddr) -> Option<ElementSegment> {
let entry = self.entry(addr)?;
Some(unsafe { entry.typed_ref::<ElementSegment>() }.handle())
}
pub fn get_export(&self, name: &str) -> Option<Extern> {
self.header.exports.get(name).copied()
}
pub fn exports(&self) -> ExportsIter<'_> {
ExportsIter::new(self.header.exports.iter())
}
}
#[cfg(test)]
impl InstanceEntity {
pub(super) fn handles(&self) -> &[AnyHandleAndEntity] {
&self.handles
}
pub(super) fn len_handles(&self) -> u32 {
self.header.len_handles()
}
}
macro_rules! impl_get_entry {
(
$(
$(#[$attr:meta])*
pub unsafe fn $get:ident(self, addr: $addr_ty:ty) -> &HandleAndEntity<$handle:ty>;
)*
) => {
$(
$(#[$attr])*
///
/// # Safety
///
/// In addition to the requirements of [`ThinPtr::as_ref`] the caller must ensure
#[doc = concat!("that the entry at `addr` stores a [`", stringify!($handle), "`] handle.")]
#[inline]
pub unsafe fn $get<'a>(self, addr: $addr_ty) -> Option<&'a HandleAndEntity<$handle>> {
let entry = unsafe { self.entry(u32::from(addr)) }?;
Some(unsafe { entry.typed_ref::<$handle>() })
}
)*
};
}
impl ThinPtr<InstanceEntity> {
#[inline]
unsafe fn header<'a>(self) -> &'a InstanceEntityHeader {
unsafe { self.cast::<InstanceEntityHeader>().as_ref() }
}
#[inline]
unsafe fn handles(self) -> NonNull<AnyHandleAndEntity> {
unsafe { self.cast::<u8>().byte_add(HANDLES_OFFSET).cast() }
}
#[inline]
unsafe fn entry<'a>(self, addr: u32) -> Option<&'a AnyHandleAndEntity> {
if addr >= unsafe { self.header() }.len_handles() {
return None;
}
Some(unsafe { self.handles().add(addr as usize).as_ref() })
}
#[inline]
pub unsafe fn layout<'a>(self) -> &'a InstanceLayout {
unsafe { self.header() }.layout()
}
impl_get_entry! {
pub unsafe fn get_memory(self, addr: MemoryAddr) -> &HandleAndEntity<Memory>;
pub unsafe fn get_global(self, addr: GlobalAddr) -> &HandleAndEntity<Global>;
pub unsafe fn get_table(self, addr: TableAddr) -> &HandleAndEntity<Table>;
pub unsafe fn get_func(self, addr: FuncAddr) -> &HandleAndEntity<Func>;
pub unsafe fn get_elem(self, addr: ElemAddr) -> &HandleAndEntity<ElementSegment>;
pub unsafe fn get_data(self, addr: DataAddr) -> &HandleAndEntity<DataSegment>;
}
#[inline]
pub unsafe fn as_ref<'a>(self) -> &'a InstanceEntity {
let len_handles = unsafe {
(&raw const (*self.cast::<InstanceEntityHeader>().as_ptr()).len_handles).read()
};
let ptr = ptr::slice_from_raw_parts(
self.cast::<AnyHandleAndEntity>().as_ptr(),
len_handles as usize,
) as *const InstanceEntity;
unsafe { &*ptr }
}
}