use crate::prelude::*;
use crate::runtime::store::StoreResourceLimiter;
use crate::runtime::vm::stack_switching::VMContObj;
use crate::runtime::vm::vmcontext::{VMFuncRef, VMTableDefinition};
use crate::runtime::vm::{GcStore, SendSyncPtr, VMGcRef, VmPtr};
use core::alloc::Layout;
use core::mem;
use core::ops::Range;
use core::ptr::{self, NonNull};
use core::slice;
use core::{cmp, usize};
use wasmtime_environ::{
FUNCREF_INIT_BIT, FUNCREF_MASK, IndexType, Trap, Tunables, WasmHeapTopType, WasmRefType,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum TableElementType {
Func,
GcRef,
Cont,
}
impl TableElementType {
pub fn element_size(&self) -> usize {
match self {
TableElementType::Func => core::mem::size_of::<FuncTableElem>(),
TableElementType::GcRef => core::mem::size_of::<Option<VMGcRef>>(),
TableElementType::Cont => core::mem::size_of::<ContTableElem>(),
}
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
struct MaybeTaggedFuncRef(Option<VmPtr<VMFuncRef>>);
impl MaybeTaggedFuncRef {
fn from(ptr: Option<NonNull<VMFuncRef>>, lazy_init: bool) -> Self {
let maybe_tagged = if lazy_init {
Some(match ptr {
Some(ptr) => ptr.map_addr(|a| a | FUNCREF_INIT_BIT),
None => NonNull::new(core::ptr::without_provenance_mut(FUNCREF_INIT_BIT)).unwrap(),
})
} else {
ptr
};
MaybeTaggedFuncRef(maybe_tagged.map(Into::into))
}
fn into_funcref(self, lazy_init: bool) -> Option<Option<NonNull<VMFuncRef>>> {
let ptr = self.0;
if lazy_init && ptr.is_none() {
None
} else {
Some(ptr.and_then(|ptr| NonNull::new(ptr.as_ptr().map_addr(|a| a & FUNCREF_MASK))))
}
}
}
pub type FuncTableElem = Option<SendSyncPtr<VMFuncRef>>;
pub type ContTableElem = Option<VMContObj>;
#[cfg(feature = "pooling-allocator")]
pub const NOMINAL_MAX_TABLE_ELEM_SIZE: usize = {
let sizes = [
core::mem::size_of::<FuncTableElem>(),
core::mem::size_of::<Option<VMGcRef>>(),
];
const fn slice_max(data: &[usize]) -> usize {
match data {
[] => 0,
[head, tail @ ..] => {
let tail_max = slice_max(tail);
if *head >= tail_max { *head } else { tail_max }
}
}
}
slice_max(&sizes)
};
pub enum StaticTable {
Func(StaticFuncTable),
GcRef(StaticGcRefTable),
Cont(StaticContTable),
}
impl From<StaticFuncTable> for StaticTable {
fn from(value: StaticFuncTable) -> Self {
Self::Func(value)
}
}
impl From<StaticGcRefTable> for StaticTable {
fn from(value: StaticGcRefTable) -> Self {
Self::GcRef(value)
}
}
impl From<StaticContTable> for StaticTable {
fn from(value: StaticContTable) -> Self {
Self::Cont(value)
}
}
pub struct StaticFuncTable {
data: SendSyncPtr<[FuncTableElem]>,
size: usize,
lazy_init: bool,
}
pub struct StaticGcRefTable {
data: SendSyncPtr<[Option<VMGcRef>]>,
size: usize,
}
pub struct StaticContTable {
data: SendSyncPtr<[ContTableElem]>,
size: usize,
}
pub enum DynamicTable {
Func(DynamicFuncTable),
GcRef(DynamicGcRefTable),
Cont(DynamicContTable),
}
impl From<DynamicFuncTable> for DynamicTable {
fn from(value: DynamicFuncTable) -> Self {
Self::Func(value)
}
}
impl From<DynamicGcRefTable> for DynamicTable {
fn from(value: DynamicGcRefTable) -> Self {
Self::GcRef(value)
}
}
impl From<DynamicContTable> for DynamicTable {
fn from(value: DynamicContTable) -> Self {
Self::Cont(value)
}
}
pub struct DynamicFuncTable {
elements: TryVec<FuncTableElem>,
maximum: Option<usize>,
lazy_init: bool,
}
pub struct DynamicGcRefTable {
elements: TryVec<Option<VMGcRef>>,
maximum: Option<usize>,
}
pub struct DynamicContTable {
elements: TryVec<ContTableElem>,
maximum: Option<usize>,
}
pub enum Table {
Static(StaticTable),
Dynamic(DynamicTable),
}
impl From<StaticTable> for Table {
fn from(value: StaticTable) -> Self {
Self::Static(value)
}
}
impl From<StaticFuncTable> for Table {
fn from(value: StaticFuncTable) -> Self {
let t: StaticTable = value.into();
t.into()
}
}
impl From<StaticGcRefTable> for Table {
fn from(value: StaticGcRefTable) -> Self {
let t: StaticTable = value.into();
t.into()
}
}
impl From<StaticContTable> for Table {
fn from(value: StaticContTable) -> Self {
let t: StaticTable = value.into();
t.into()
}
}
impl From<DynamicTable> for Table {
fn from(value: DynamicTable) -> Self {
Self::Dynamic(value)
}
}
impl From<DynamicFuncTable> for Table {
fn from(value: DynamicFuncTable) -> Self {
let t: DynamicTable = value.into();
t.into()
}
}
impl From<DynamicGcRefTable> for Table {
fn from(value: DynamicGcRefTable) -> Self {
let t: DynamicTable = value.into();
t.into()
}
}
impl From<DynamicContTable> for Table {
fn from(value: DynamicContTable) -> Self {
let t: DynamicTable = value.into();
t.into()
}
}
pub(crate) fn wasm_to_table_type(ty: WasmRefType) -> TableElementType {
match ty.heap_type.top() {
WasmHeapTopType::Func => TableElementType::Func,
WasmHeapTopType::Any | WasmHeapTopType::Extern => TableElementType::GcRef,
WasmHeapTopType::Cont => TableElementType::Cont,
WasmHeapTopType::Exn => TableElementType::GcRef,
}
}
unsafe fn alloc_dynamic_table_elements<T>(len: usize) -> Result<TryVec<Option<T>>> {
debug_assert!(
unsafe {
core::mem::MaybeUninit::<Option<T>>::zeroed()
.assume_init()
.is_none()
},
"null table elements are represented with zeroed memory"
);
if len == 0 {
return Ok(TryVec::new());
}
let align = mem::align_of::<Option<T>>();
let size = mem::size_of::<Option<T>>();
let size = size.next_multiple_of(align);
let size = size.checked_mul(len).unwrap();
let layout = Layout::from_size_align(size, align)?;
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
if ptr.is_null() {
return Err(OutOfMemory::new(size).into());
}
let elems = unsafe { TryVec::<Option<T>>::from_raw_parts(ptr.cast(), len, len) };
debug_assert!(elems.iter().all(|e| e.is_none()));
Ok(elems)
}
impl Table {
pub async fn new_dynamic(
ty: &wasmtime_environ::Table,
tunables: &Tunables,
limiter: Option<&mut StoreResourceLimiter<'_>>,
) -> Result<Self> {
let (minimum, maximum) = Self::limit_new(ty, limiter).await?;
match wasm_to_table_type(ty.ref_type) {
TableElementType::Func => Ok(Self::from(DynamicFuncTable {
elements: unsafe { alloc_dynamic_table_elements(minimum)? },
maximum,
lazy_init: tunables.table_lazy_init,
})),
TableElementType::GcRef => Ok(Self::from(DynamicGcRefTable {
elements: unsafe { alloc_dynamic_table_elements(minimum)? },
maximum,
})),
TableElementType::Cont => {
let mut elements = TryVec::new();
elements.resize_with(minimum, || None)?;
Ok(Self::from(DynamicContTable { elements, maximum }))
}
}
}
pub async unsafe fn new_static(
ty: &wasmtime_environ::Table,
tunables: &Tunables,
data: SendSyncPtr<[u8]>,
limiter: Option<&mut StoreResourceLimiter<'_>>,
) -> Result<Self> {
let (minimum, maximum) = Self::limit_new(ty, limiter).await?;
let size = minimum;
let max = maximum.unwrap_or(usize::MAX);
match wasm_to_table_type(ty.ref_type) {
TableElementType::Func => {
let len = {
let (before, data, after) = unsafe {
let data = data.as_non_null().as_ref();
data.align_to::<FuncTableElem>()
};
assert!(before.is_empty());
assert!(after.is_empty());
data.len()
};
ensure!(
usize::try_from(ty.limits.min).unwrap() <= len,
"initial table size of {} exceeds the pooling allocator's \
configured maximum table size of {len} elements",
ty.limits.min,
);
let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
data.as_non_null().cast::<FuncTableElem>(),
cmp::min(len, max),
));
Ok(Self::from(StaticFuncTable {
data,
size,
lazy_init: tunables.table_lazy_init,
}))
}
TableElementType::GcRef => {
let len = {
let (before, data, after) = unsafe {
let data = data.as_non_null().as_ref();
data.align_to::<Option<VMGcRef>>()
};
assert!(before.is_empty());
assert!(after.is_empty());
data.len()
};
ensure!(
usize::try_from(ty.limits.min).unwrap() <= len,
"initial table size of {} exceeds the pooling allocator's \
configured maximum table size of {len} elements",
ty.limits.min,
);
let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
data.as_non_null().cast::<Option<VMGcRef>>(),
cmp::min(len, max),
));
Ok(Self::from(StaticGcRefTable { data, size }))
}
TableElementType::Cont => {
let len = {
let (before, data, after) = unsafe {
let data = data.as_non_null().as_ref();
data.align_to::<ContTableElem>()
};
assert!(before.is_empty());
assert!(after.is_empty());
data.len()
};
ensure!(
usize::try_from(ty.limits.min).unwrap() <= len,
"initial table size of {} exceeds the pooling allocator's \
configured maximum table size of {len} elements",
ty.limits.min,
);
let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
data.as_non_null().cast::<ContTableElem>(),
cmp::min(len, max),
));
Ok(Self::from(StaticContTable { data, size }))
}
}
}
async fn limit_new(
ty: &wasmtime_environ::Table,
limiter: Option<&mut StoreResourceLimiter<'_>>,
) -> Result<(usize, Option<usize>)> {
let absolute_max = usize::MAX;
let minimum = usize::try_from(ty.limits.min).ok();
let maximum = match (ty.limits.max, ty.idx_type) {
(Some(max), _) => usize::try_from(max).ok(),
(None, IndexType::I64) => usize::try_from(u64::MAX).ok(),
(None, IndexType::I32) => usize::try_from(u32::MAX).ok(),
};
if let Some(limiter) = limiter {
if !limiter
.table_growing(0, minimum.unwrap_or(absolute_max), maximum)
.await?
{
bail!(
"table minimum size of {} elements exceeds table limits",
ty.limits.min
);
}
}
let minimum = minimum.ok_or_else(|| {
format_err!(
"table minimum size of {} elements exceeds table limits",
ty.limits.min
)
})?;
Ok((minimum, maximum))
}
pub fn element_type(&self) -> TableElementType {
match self {
Table::Static(StaticTable::Func(_)) | Table::Dynamic(DynamicTable::Func(_)) => {
TableElementType::Func
}
Table::Static(StaticTable::GcRef(_)) | Table::Dynamic(DynamicTable::GcRef(_)) => {
TableElementType::GcRef
}
Table::Static(StaticTable::Cont(_)) | Table::Dynamic(DynamicTable::Cont(_)) => {
TableElementType::Cont
}
}
}
#[cfg(feature = "pooling-allocator")]
pub(crate) fn is_static(&self) -> bool {
matches!(self, Table::Static(_))
}
pub fn size(&self) -> usize {
match self {
Table::Static(StaticTable::Func(StaticFuncTable { size, .. })) => *size,
Table::Static(StaticTable::GcRef(StaticGcRefTable { size, .. })) => *size,
Table::Static(StaticTable::Cont(StaticContTable { size, .. })) => *size,
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => elements.len(),
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
elements.len()
}
Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => elements.len(),
}
}
pub fn maximum(&self) -> Option<usize> {
match self {
Table::Static(StaticTable::Cont(StaticContTable { data, .. })) => Some(data.len()),
Table::Static(StaticTable::Func(StaticFuncTable { data, .. })) => Some(data.len()),
Table::Static(StaticTable::GcRef(StaticGcRefTable { data, .. })) => Some(data.len()),
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { maximum, .. })) => *maximum,
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { maximum, .. })) => *maximum,
Table::Dynamic(DynamicTable::Cont(DynamicContTable { maximum, .. })) => *maximum,
}
}
pub fn fill_func(
&mut self,
dst: u64,
val: Option<NonNull<VMFuncRef>>,
len: u64,
) -> Result<(), Trap> {
let range = self.validate_fill(dst, len)?;
let (funcrefs, lazy_init) = self.funcrefs_mut();
funcrefs[range].fill(MaybeTaggedFuncRef::from(val, lazy_init));
Ok(())
}
pub fn fill_gc_ref(
&mut self,
mut gc_store: Option<&mut GcStore>,
dst: u64,
val: Option<&VMGcRef>,
len: u64,
) -> Result<(), Trap> {
let range = self.validate_fill(dst, len)?;
for slot in &mut self.gc_refs_mut()[range] {
GcStore::write_gc_ref_optional_store(gc_store.as_deref_mut(), slot, val);
}
Ok(())
}
pub fn fill_cont(&mut self, dst: u64, val: Option<VMContObj>, len: u64) -> Result<(), Trap> {
let range = self.validate_fill(dst, len)?;
self.contrefs_mut()[range].fill(val);
Ok(())
}
fn validate_fill(&mut self, dst: u64, len: u64) -> Result<Range<usize>, Trap> {
let start = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?;
let end = start
.checked_add(len)
.ok_or_else(|| Trap::TableOutOfBounds)?;
if end > self.size() {
return Err(Trap::TableOutOfBounds);
}
Ok(start..end)
}
pub async unsafe fn grow_func(
&mut self,
limiter: Option<&mut StoreResourceLimiter<'_>>,
delta: u64,
init_value: Option<SendSyncPtr<VMFuncRef>>,
) -> Result<Option<usize>, Error> {
self._grow(delta, limiter, |me, base, len| {
me.fill_func(base, init_value.map(|p| p.as_non_null()), len)
})
.await
}
pub async unsafe fn grow_gc_ref(
&mut self,
limiter: Option<&mut StoreResourceLimiter<'_>>,
gc_store: Option<&mut GcStore>,
delta: u64,
init_value: Option<&VMGcRef>,
) -> Result<Option<usize>, Error> {
self._grow(delta, limiter, |me, base, len| {
me.fill_gc_ref(gc_store, base, init_value, len)
})
.await
}
pub async unsafe fn grow_cont(
&mut self,
limiter: Option<&mut StoreResourceLimiter<'_>>,
delta: u64,
init_value: Option<VMContObj>,
) -> Result<Option<usize>, Error> {
self._grow(delta, limiter, |me, base, len| {
me.fill_cont(base, init_value, len)
})
.await
}
async fn _grow(
&mut self,
delta: u64,
mut limiter: Option<&mut StoreResourceLimiter<'_>>,
fill: impl FnOnce(&mut Self, u64, u64) -> Result<(), Trap>,
) -> Result<Option<usize>, Error> {
let old_size = self.size();
if delta == 0 {
return Ok(Some(old_size));
}
let delta = usize::try_from(delta).map_err(|_| Trap::TableOutOfBounds)?;
let new_size = match old_size.checked_add(delta) {
Some(s) => s,
None => {
if let Some(limiter) = limiter {
limiter
.table_grow_failed(format_err!("overflow calculating new table size"))?;
}
return Ok(None);
}
};
if let Some(limiter) = &mut limiter {
if !limiter
.table_growing(old_size, new_size, self.maximum())
.await?
{
return Ok(None);
}
}
if let Some(max) = self.maximum() {
if new_size > max {
if let Some(limiter) = limiter {
limiter.table_grow_failed(format_err!("Table maximum size exceeded"))?;
}
return Ok(None);
}
}
match self {
Table::Static(StaticTable::Func(StaticFuncTable { data, size, .. })) => {
unsafe {
debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none()));
}
*size = new_size;
}
Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => {
unsafe {
debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none()));
}
*size = new_size;
}
Table::Static(StaticTable::Cont(StaticContTable { data, size })) => {
unsafe {
debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none()));
}
*size = new_size;
}
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => {
elements.resize_with(new_size, || None)?;
}
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
elements.resize_with(new_size, || None)?;
}
Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => {
elements.resize_with(new_size, || None)?;
}
}
fill(
self,
u64::try_from(old_size).unwrap(),
u64::try_from(delta).unwrap(),
)
.expect("table should not be out of bounds");
Ok(Some(old_size))
}
pub fn get_func(&self, index: u64) -> Result<Option<NonNull<VMFuncRef>>, Trap> {
match self.get_func_maybe_init(index)? {
Some(elem) => Ok(elem),
None => panic!("function index should have been initialized"),
}
}
pub fn get_func_maybe_init(
&self,
index: u64,
) -> Result<Option<Option<NonNull<VMFuncRef>>>, Trap> {
let index = usize::try_from(index).map_err(|_| Trap::TableOutOfBounds)?;
let (funcrefs, lazy_init) = self.funcrefs();
Ok(funcrefs
.get(index)
.ok_or(Trap::TableOutOfBounds)?
.into_funcref(lazy_init))
}
pub fn get_gc_ref(&self, index: u64) -> Result<Option<&VMGcRef>, Trap> {
let index = usize::try_from(index).map_err(|_| Trap::TableOutOfBounds)?;
let gcref = self.gc_refs().get(index).ok_or(Trap::TableOutOfBounds)?;
Ok(gcref.as_ref())
}
pub fn get_cont(&self, index: u64) -> Result<Option<VMContObj>, Trap> {
let index = usize::try_from(index).map_err(|_| Trap::TableOutOfBounds)?;
let cont = self.contrefs().get(index).ok_or(Trap::TableOutOfBounds)?;
Ok(*cont)
}
pub fn set_func(&mut self, index: u64, elem: Option<NonNull<VMFuncRef>>) -> Result<(), Trap> {
let trap = Trap::TableOutOfBounds;
let index: usize = index.try_into().map_err(|_| trap)?;
let (funcrefs, lazy_init) = self.funcrefs_mut();
*funcrefs.get_mut(index).ok_or(trap)? = MaybeTaggedFuncRef::from(elem, lazy_init);
Ok(())
}
pub fn set_gc_ref(
&mut self,
store: Option<&mut GcStore>,
index: u64,
elem: Option<&VMGcRef>,
) -> Result<(), Trap> {
let trap = Trap::TableOutOfBounds;
let index: usize = index.try_into().map_err(|_| trap)?;
GcStore::write_gc_ref_optional_store(
store,
self.gc_refs_mut().get_mut(index).ok_or(trap)?,
elem,
);
Ok(())
}
pub fn copy_to(
&self,
dst: &mut Table,
gc_store: Option<&mut GcStore>,
dst_index: u64,
src_index: u64,
len: u64,
) -> Result<(), Trap> {
let (src_range, dst_range) = Table::validate_copy(self, dst, dst_index, src_index, len)?;
Self::copy_elements(gc_store, dst, self, dst_range, src_range);
Ok(())
}
pub fn copy_within(
&mut self,
gc_store: Option<&mut GcStore>,
dst_index: u64,
src_index: u64,
len: u64,
) -> Result<(), Trap> {
let (src_range, dst_range) = Table::validate_copy(self, self, dst_index, src_index, len)?;
self.copy_elements_within(gc_store, dst_range, src_range);
Ok(())
}
fn validate_copy(
src: &Table,
dst: &Table,
dst_index: u64,
src_index: u64,
len: u64,
) -> Result<(Range<usize>, Range<usize>), Trap> {
let src_index = usize::try_from(src_index).map_err(|_| Trap::TableOutOfBounds)?;
let dst_index = usize::try_from(dst_index).map_err(|_| Trap::TableOutOfBounds)?;
let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?;
if src_index.checked_add(len).map_or(true, |n| n > src.size())
|| dst_index.checked_add(len).map_or(true, |m| m > dst.size())
{
return Err(Trap::TableOutOfBounds);
}
debug_assert!(
dst.element_type() == src.element_type(),
"table element type mismatch"
);
let src_range = src_index..src_index + len;
let dst_range = dst_index..dst_index + len;
Ok((src_range, dst_range))
}
pub fn vmtable(&mut self) -> VMTableDefinition {
match self {
Table::Static(StaticTable::Func(StaticFuncTable { data, size, .. })) => {
VMTableDefinition {
base: data.cast().into(),
current_elements: *size,
}
}
Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => {
VMTableDefinition {
base: data.cast().into(),
current_elements: *size,
}
}
Table::Static(StaticTable::Cont(StaticContTable { data, size })) => VMTableDefinition {
base: data.cast().into(),
current_elements: *size,
},
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => {
VMTableDefinition {
base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(),
current_elements: elements.len(),
}
}
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
VMTableDefinition {
base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(),
current_elements: elements.len(),
}
}
Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => {
VMTableDefinition {
base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(),
current_elements: elements.len(),
}
}
}
}
fn funcrefs(&self) -> (&[MaybeTaggedFuncRef], bool) {
assert_eq!(self.element_type(), TableElementType::Func);
match self {
Self::Dynamic(DynamicTable::Func(DynamicFuncTable {
elements,
lazy_init,
..
})) => (
unsafe { slice::from_raw_parts(elements.as_ptr().cast(), elements.len()) },
*lazy_init,
),
Self::Static(StaticTable::Func(StaticFuncTable {
data,
size,
lazy_init,
})) => (
unsafe { slice::from_raw_parts(data.as_ptr().cast(), *size) },
*lazy_init,
),
_ => unreachable!(),
}
}
fn funcrefs_mut(&mut self) -> (&mut [MaybeTaggedFuncRef], bool) {
assert_eq!(self.element_type(), TableElementType::Func);
match self {
Self::Dynamic(DynamicTable::Func(DynamicFuncTable {
elements,
lazy_init,
..
})) => (
unsafe { slice::from_raw_parts_mut(elements.as_mut_ptr().cast(), elements.len()) },
*lazy_init,
),
Self::Static(StaticTable::Func(StaticFuncTable {
data,
size,
lazy_init,
})) => (
unsafe { slice::from_raw_parts_mut(data.as_ptr().cast(), *size) },
*lazy_init,
),
_ => unreachable!(),
}
}
fn gc_refs(&self) -> &[Option<VMGcRef>] {
assert_eq!(self.element_type(), TableElementType::GcRef);
match self {
Self::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => elements,
Self::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => unsafe {
&data.as_non_null().as_ref()[..*size]
},
_ => unreachable!(),
}
}
fn contrefs(&self) -> &[Option<VMContObj>] {
assert_eq!(self.element_type(), TableElementType::Cont);
match self {
Self::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => unsafe {
slice::from_raw_parts(elements.as_ptr().cast(), elements.len())
},
Self::Static(StaticTable::Cont(StaticContTable { data, size })) => unsafe {
slice::from_raw_parts(data.as_ptr().cast(), *size)
},
_ => unreachable!(),
}
}
fn contrefs_mut(&mut self) -> &mut [Option<VMContObj>] {
assert_eq!(self.element_type(), TableElementType::Cont);
match self {
Self::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => unsafe {
slice::from_raw_parts_mut(elements.as_mut_ptr().cast(), elements.len())
},
Self::Static(StaticTable::Cont(StaticContTable { data, size })) => unsafe {
slice::from_raw_parts_mut(data.as_ptr().cast(), *size)
},
_ => unreachable!(),
}
}
pub fn gc_refs_mut(&mut self) -> &mut [Option<VMGcRef>] {
assert_eq!(self.element_type(), TableElementType::GcRef);
match self {
Self::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => elements,
Self::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => unsafe {
&mut data.as_non_null().as_mut()[..*size]
},
_ => unreachable!(),
}
}
fn copy_elements(
mut gc_store: Option<&mut GcStore>,
dst_table: &mut Self,
src_table: &Self,
dst_range: Range<usize>,
src_range: Range<usize>,
) {
debug_assert!(!ptr::eq(dst_table, src_table));
let ty = dst_table.element_type();
match ty {
TableElementType::Func => {
let (dst_funcrefs, _lazy_init) = dst_table.funcrefs_mut();
let (src_funcrefs, _lazy_init) = src_table.funcrefs();
dst_funcrefs[dst_range].copy_from_slice(&src_funcrefs[src_range]);
}
TableElementType::GcRef => {
assert_eq!(
dst_range.end - dst_range.start,
src_range.end - src_range.start
);
assert!(dst_range.end <= dst_table.gc_refs().len());
assert!(src_range.end <= src_table.gc_refs().len());
for (dst, src) in dst_range.zip(src_range) {
GcStore::write_gc_ref_optional_store(
gc_store.as_deref_mut(),
&mut dst_table.gc_refs_mut()[dst],
src_table.gc_refs()[src].as_ref(),
);
}
}
TableElementType::Cont => {
dst_table.contrefs_mut()[dst_range]
.copy_from_slice(&src_table.contrefs()[src_range]);
}
}
}
fn copy_elements_within(
&mut self,
mut gc_store: Option<&mut GcStore>,
dst_range: Range<usize>,
src_range: Range<usize>,
) {
assert_eq!(
dst_range.end - dst_range.start,
src_range.end - src_range.start
);
if src_range.start == dst_range.start {
return;
}
let ty = self.element_type();
match ty {
TableElementType::Func => {
let (funcrefs, _lazy_init) = self.funcrefs_mut();
funcrefs.copy_within(src_range, dst_range.start);
}
TableElementType::GcRef => {
let elements = self.gc_refs_mut();
if dst_range.start < src_range.start {
for (d, s) in dst_range.zip(src_range) {
let (ds, ss) = elements.split_at_mut(s);
let dst = &mut ds[d];
let src = ss[0].as_ref();
GcStore::write_gc_ref_optional_store(gc_store.as_deref_mut(), dst, src);
}
} else {
for (s, d) in src_range.rev().zip(dst_range.rev()) {
let (ss, ds) = elements.split_at_mut(d);
let dst = &mut ds[0];
let src = ss[s].as_ref();
GcStore::write_gc_ref_optional_store(gc_store.as_deref_mut(), dst, src);
}
}
}
TableElementType::Cont => {
self.contrefs_mut().copy_within(src_range, dst_range.start);
}
}
}
}
impl Default for Table {
fn default() -> Self {
Self::from(StaticFuncTable {
data: SendSyncPtr::new(NonNull::from(&mut [])),
size: 0,
lazy_init: false,
})
}
}