#![cfg_attr(feature = "gc", allow(irrefutable_let_patterns))]
use crate::vmcontext::{VMFuncRef, VMTableDefinition};
use crate::{GcStore, SendSyncPtr, Store, VMGcRef};
use anyhow::{bail, ensure, format_err, Error, Result};
use sptr::Strict;
use std::ops::Range;
use std::ptr::{self, NonNull};
use wasmtime_environ::{
TablePlan, Trap, WasmHeapType, WasmRefType, FUNCREF_INIT_BIT, FUNCREF_MASK,
};
pub enum TableElement {
FuncRef(*mut VMFuncRef),
GcRef(Option<VMGcRef>),
UninitFunc,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum TableElementType {
Func,
GcRef,
}
impl TableElementType {
fn matches(&self, val: &TableElement) -> bool {
match (val, self) {
(TableElement::FuncRef(_), TableElementType::Func) => true,
(TableElement::GcRef(_), TableElementType::GcRef) => true,
_ => false,
}
}
}
unsafe impl Send for TableElement where VMGcRef: Send {}
unsafe impl Sync for TableElement where VMGcRef: Sync {}
impl TableElement {
pub(crate) unsafe fn into_func_ref_asserting_initialized(self) -> *mut VMFuncRef {
match self {
Self::FuncRef(e) => e,
Self::UninitFunc => panic!("Uninitialized table element value outside of table slot"),
Self::GcRef(_) => panic!("GC reference is not a function reference"),
}
}
pub(crate) fn is_uninit(&self) -> bool {
match self {
Self::UninitFunc => true,
_ => false,
}
}
}
impl From<*mut VMFuncRef> for TableElement {
fn from(f: *mut VMFuncRef) -> TableElement {
TableElement::FuncRef(f)
}
}
impl From<Option<VMGcRef>> for TableElement {
fn from(x: Option<VMGcRef>) -> TableElement {
TableElement::GcRef(x)
}
}
impl From<VMGcRef> for TableElement {
fn from(x: VMGcRef) -> TableElement {
TableElement::GcRef(Some(x))
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
struct TaggedFuncRef(*mut VMFuncRef);
impl TaggedFuncRef {
const UNINIT: TaggedFuncRef = TaggedFuncRef(ptr::null_mut());
fn from(ptr: *mut VMFuncRef) -> Self {
let masked = Strict::map_addr(ptr, |a| a | FUNCREF_INIT_BIT);
TaggedFuncRef(masked)
}
fn into_table_element(self) -> TableElement {
let ptr = self.0;
if ptr.is_null() {
TableElement::UninitFunc
} else {
let unmasked = Strict::map_addr(ptr, |a| a & FUNCREF_MASK);
TableElement::FuncRef(unmasked)
}
}
}
pub type FuncTableElem = Option<SendSyncPtr<VMFuncRef>>;
pub enum StaticTable {
Func(StaticFuncTable),
GcRef(StaticGcRefTable),
}
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)
}
}
pub struct StaticFuncTable {
data: SendSyncPtr<[FuncTableElem]>,
size: u32,
}
pub struct StaticGcRefTable {
data: SendSyncPtr<[Option<VMGcRef>]>,
size: u32,
}
pub enum DynamicTable {
Func(DynamicFuncTable),
GcRef(DynamicGcRefTable),
}
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)
}
}
pub struct DynamicFuncTable {
elements: Vec<FuncTableElem>,
maximum: Option<u32>,
}
pub struct DynamicGcRefTable {
elements: Vec<Option<VMGcRef>>,
maximum: Option<u32>,
}
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<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()
}
}
fn wasm_to_table_type(ty: WasmRefType) -> TableElementType {
match ty.heap_type {
WasmHeapType::Func | WasmHeapType::Concrete(_) | WasmHeapType::NoFunc => {
TableElementType::Func
}
WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
TableElementType::GcRef
}
}
}
impl Table {
pub fn new_dynamic(plan: &TablePlan, store: &mut dyn Store) -> Result<Self> {
Self::limit_new(plan, store)?;
match wasm_to_table_type(plan.table.wasm_ty) {
TableElementType::Func => Ok(Self::from(DynamicFuncTable {
elements: vec![None; usize::try_from(plan.table.minimum).unwrap()],
maximum: plan.table.maximum,
})),
TableElementType::GcRef => Ok(Self::from(DynamicGcRefTable {
elements: (0..usize::try_from(plan.table.minimum).unwrap())
.map(|_| None)
.collect(),
maximum: plan.table.maximum,
})),
}
}
pub unsafe fn new_static(
plan: &TablePlan,
data: SendSyncPtr<[u8]>,
store: &mut dyn Store,
) -> Result<Self> {
Self::limit_new(plan, store)?;
let size = plan.table.minimum;
let max = plan
.table
.maximum
.map_or(usize::MAX, |x| usize::try_from(x).unwrap());
match wasm_to_table_type(plan.table.wasm_ty) {
TableElementType::Func => {
let len = {
let data = data.as_non_null().as_ref();
let (before, data, after) = data.align_to::<FuncTableElem>();
assert!(before.is_empty());
assert!(after.is_empty());
data.len()
};
ensure!(
usize::try_from(plan.table.minimum).unwrap() <= len,
"initial table size of {} exceeds the pooling allocator's \
configured maximum table size of {len} elements",
plan.table.minimum,
);
let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
data.as_non_null().cast::<FuncTableElem>(),
std::cmp::min(len, max),
));
Ok(Self::from(StaticFuncTable { data, size }))
}
TableElementType::GcRef => {
let len = {
let data = data.as_non_null().as_ref();
let (before, data, after) = data.align_to::<Option<VMGcRef>>();
assert!(before.is_empty());
assert!(after.is_empty());
data.len()
};
ensure!(
usize::try_from(plan.table.minimum).unwrap() <= len,
"initial table size of {} exceeds the pooling allocator's \
configured maximum table size of {len} elements",
plan.table.minimum,
);
let data = SendSyncPtr::new(NonNull::slice_from_raw_parts(
data.as_non_null().cast::<Option<VMGcRef>>(),
std::cmp::min(len, max),
));
Ok(Self::from(StaticGcRefTable { data, size }))
}
}
}
fn limit_new(plan: &TablePlan, store: &mut dyn Store) -> Result<()> {
if !store.table_growing(0, plan.table.minimum, plan.table.maximum)? {
bail!(
"table minimum size of {} elements exceeds table limits",
plan.table.minimum
);
}
Ok(())
}
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
}
}
}
#[cfg(feature = "pooling-allocator")]
pub(crate) fn is_static(&self) -> bool {
matches!(self, Table::Static(_))
}
pub fn size(&self) -> u32 {
match self {
Table::Static(StaticTable::Func(StaticFuncTable { size, .. })) => *size,
Table::Static(StaticTable::GcRef(StaticGcRefTable { size, .. })) => *size,
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => {
elements.len().try_into().unwrap()
}
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
elements.len().try_into().unwrap()
}
}
}
pub fn maximum(&self) -> Option<u32> {
match self {
Table::Static(StaticTable::Func(StaticFuncTable { data, .. })) => {
Some(u32::try_from(data.len()).unwrap())
}
Table::Static(StaticTable::GcRef(StaticGcRefTable { data, .. })) => {
Some(u32::try_from(data.len()).unwrap())
}
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { maximum, .. })) => *maximum,
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { maximum, .. })) => *maximum,
}
}
pub fn init_func(
&mut self,
dst: u32,
items: impl ExactSizeIterator<Item = *mut VMFuncRef>,
) -> Result<(), Trap> {
let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
let elements = self
.funcrefs_mut()
.get_mut(dst..)
.and_then(|s| s.get_mut(..items.len()))
.ok_or(Trap::TableOutOfBounds)?;
for (item, slot) in items.zip(elements) {
*slot = TaggedFuncRef::from(item);
}
Ok(())
}
pub fn init_gc_refs(
&mut self,
dst: u32,
items: impl ExactSizeIterator<Item = Option<VMGcRef>>,
) -> Result<(), Trap> {
let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?;
let elements = self
.gc_refs_mut()
.get_mut(dst..)
.and_then(|s| s.get_mut(..items.len()))
.ok_or(Trap::TableOutOfBounds)?;
for (item, slot) in items.zip(elements) {
*slot = item;
}
Ok(())
}
pub fn fill(
&mut self,
gc_store: &mut GcStore,
dst: u32,
val: TableElement,
len: u32,
) -> Result<(), Trap> {
let start = dst as usize;
let end = start
.checked_add(len as usize)
.ok_or_else(|| Trap::TableOutOfBounds)?;
if end > self.size() as usize {
return Err(Trap::TableOutOfBounds);
}
match val {
TableElement::FuncRef(f) => {
self.funcrefs_mut()[start..end].fill(TaggedFuncRef::from(f));
}
TableElement::GcRef(r) => {
for slot in &mut self.gc_refs_mut()[start..end] {
gc_store.write_gc_ref(slot, r.as_ref());
}
if let Some(r) = r {
gc_store.drop_gc_ref(r);
}
}
TableElement::UninitFunc => {
self.funcrefs_mut()[start..end].fill(TaggedFuncRef::UNINIT);
}
}
Ok(())
}
pub unsafe fn grow(
&mut self,
delta: u32,
init_value: TableElement,
store: &mut dyn Store,
) -> Result<Option<u32>, Error> {
let old_size = self.size();
if delta == 0 {
return Ok(Some(old_size));
}
let new_size = match old_size.checked_add(delta) {
Some(s) => s,
None => {
store.table_grow_failed(format_err!("overflow calculating new table size"))?;
return Ok(None);
}
};
if !store.table_growing(old_size, new_size, self.maximum())? {
return Ok(None);
}
if let Some(max) = self.maximum() {
if new_size > max {
store.table_grow_failed(format_err!("Table maximum size exceeded"))?;
return Ok(None);
}
}
debug_assert!(self.type_matches(&init_value));
match self {
Table::Static(StaticTable::Func(StaticFuncTable { data, size })) => {
unsafe {
debug_assert!(data.as_ref()[*size as usize..new_size as usize]
.iter()
.all(|x| x.is_none()));
}
*size = new_size;
}
Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => {
unsafe {
debug_assert!(data.as_ref()[*size as usize..new_size as usize]
.iter()
.all(|x| x.is_none()));
}
*size = new_size;
}
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => {
elements.resize(usize::try_from(new_size).unwrap(), None);
}
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
elements.resize_with(usize::try_from(new_size).unwrap(), || None);
}
}
self.fill(store.gc_store(), old_size, init_value, delta)
.expect("table should not be out of bounds");
Ok(Some(old_size))
}
pub fn get(&self, gc_store: &mut GcStore, index: u32) -> Option<TableElement> {
let index = usize::try_from(index).ok()?;
match self.element_type() {
TableElementType::Func => self
.funcrefs()
.get(index)
.copied()
.map(|e| e.into_table_element()),
TableElementType::GcRef => self.gc_refs().get(index).map(|r| {
let r = r.as_ref().map(|r| gc_store.clone_gc_ref(r));
TableElement::GcRef(r)
}),
}
}
pub fn set(&mut self, index: u32, elem: TableElement) -> Result<(), ()> {
let index = usize::try_from(index).map_err(|_| ())?;
match elem {
TableElement::FuncRef(f) => {
*self.funcrefs_mut().get_mut(index).ok_or(())? = TaggedFuncRef::from(f);
}
TableElement::UninitFunc => {
*self.funcrefs_mut().get_mut(index).ok_or(())? = TaggedFuncRef::UNINIT;
}
TableElement::GcRef(e) => {
*self.gc_refs_mut().get_mut(index).ok_or(())? = e;
}
}
Ok(())
}
pub unsafe fn copy(
gc_store: &mut GcStore,
dst_table: *mut Self,
src_table: *mut Self,
dst_index: u32,
src_index: u32,
len: u32,
) -> Result<(), Trap> {
if src_index
.checked_add(len)
.map_or(true, |n| n > (*src_table).size())
|| dst_index
.checked_add(len)
.map_or(true, |m| m > (*dst_table).size())
{
return Err(Trap::TableOutOfBounds);
}
debug_assert!(
(*dst_table).element_type() == (*src_table).element_type(),
"table element type mismatch"
);
let src_range = src_index as usize..src_index as usize + len as usize;
let dst_range = dst_index as usize..dst_index as usize + len as usize;
if ptr::eq(dst_table, src_table) {
(*dst_table).copy_elements_within(gc_store, dst_range, src_range);
} else {
Self::copy_elements(gc_store, &mut *dst_table, &*src_table, dst_range, src_range);
}
Ok(())
}
pub fn vmtable(&mut self) -> VMTableDefinition {
match self {
Table::Static(StaticTable::Func(StaticFuncTable { data, size })) => VMTableDefinition {
base: data.as_ptr().cast(),
current_elements: *size,
},
Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => {
VMTableDefinition {
base: data.as_ptr().cast(),
current_elements: *size,
}
}
Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => {
VMTableDefinition {
base: elements.as_mut_ptr().cast(),
current_elements: elements.len().try_into().unwrap(),
}
}
Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => {
VMTableDefinition {
base: elements.as_mut_ptr().cast(),
current_elements: elements.len().try_into().unwrap(),
}
}
}
}
fn type_matches(&self, val: &TableElement) -> bool {
self.element_type().matches(val)
}
fn funcrefs(&self) -> &[TaggedFuncRef] {
assert_eq!(self.element_type(), TableElementType::Func);
match self {
Self::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => unsafe {
std::slice::from_raw_parts(elements.as_ptr().cast(), elements.len())
},
Self::Static(StaticTable::Func(StaticFuncTable { data, size })) => unsafe {
std::slice::from_raw_parts(data.as_ptr().cast(), usize::try_from(*size).unwrap())
},
_ => unreachable!(),
}
}
fn funcrefs_mut(&mut self) -> &mut [TaggedFuncRef] {
assert_eq!(self.element_type(), TableElementType::Func);
match self {
Self::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => unsafe {
std::slice::from_raw_parts_mut(elements.as_mut_ptr().cast(), elements.len())
},
Self::Static(StaticTable::Func(StaticFuncTable { data, size })) => unsafe {
std::slice::from_raw_parts_mut(
data.as_ptr().cast(),
usize::try_from(*size).unwrap(),
)
},
_ => 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()[..usize::try_from(*size).unwrap()]
},
_ => 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()[..usize::try_from(*size).unwrap()]
},
_ => unreachable!(),
}
}
fn copy_elements(
gc_store: &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 => {
dst_table.funcrefs_mut()[dst_range]
.copy_from_slice(&src_table.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) {
gc_store.write_gc_ref(
&mut dst_table.gc_refs_mut()[dst],
src_table.gc_refs()[src].as_ref(),
);
}
}
}
}
fn copy_elements_within(
&mut self,
gc_store: &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 => {
self.funcrefs_mut().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();
gc_store.write_gc_ref(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();
gc_store.write_gc_ref(dst, src);
}
}
}
}
}
}
impl Default for Table {
fn default() -> Self {
Self::from(StaticFuncTable {
data: SendSyncPtr::new(NonNull::from(&mut [])),
size: 0,
})
}
}