use crate::Trap;
use crate::VMExternRef;
use crate::VMFuncRef;
use crate::store::MaybeInstanceOwned;
use crate::vmcontext::VMTableDefinition;
use bytesize::ByteSize;
use std::cell::UnsafeCell;
use std::convert::TryFrom;
use std::fmt;
use std::ptr::NonNull;
use wasmer_types::TableStyle;
use wasmer_types::{TableType, TrapCode, Type as ValType};
#[derive(Debug, Clone)]
pub enum TableElement {
ExternRef(Option<VMExternRef>),
FuncRef(Option<VMFuncRef>),
}
impl From<TableElement> for RawTableElement {
fn from(other: TableElement) -> Self {
match other {
TableElement::ExternRef(extern_ref) => Self { extern_ref },
TableElement::FuncRef(func_ref) => Self { func_ref },
}
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union RawTableElement {
pub(crate) extern_ref: Option<VMExternRef>,
pub(crate) func_ref: Option<VMFuncRef>,
}
#[cfg(test)]
#[test]
fn table_element_size_test() {
use std::mem::size_of;
assert_eq!(size_of::<RawTableElement>(), size_of::<VMExternRef>());
assert_eq!(size_of::<RawTableElement>(), size_of::<VMFuncRef>());
}
impl fmt::Debug for RawTableElement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("RawTableElement").finish()
}
}
impl Default for RawTableElement {
fn default() -> Self {
Self { func_ref: None }
}
}
impl Default for TableElement {
fn default() -> Self {
Self::FuncRef(None)
}
}
const TABLE_MAX_SIZE: usize = ByteSize::mib(128).as_u64() as usize;
#[derive(Debug)]
pub struct VMTable {
vec: Vec<RawTableElement>,
maximum: Option<u32>,
table: TableType,
style: TableStyle,
vm_table_definition: MaybeInstanceOwned<VMTableDefinition>,
}
impl VMTable {
pub fn new(table: &TableType, style: &TableStyle) -> Result<Self, String> {
unsafe { Self::new_inner(table, style, None) }
}
pub fn get_runtime_size(&self) -> u32 {
self.vec.len() as u32
}
pub unsafe fn from_definition(
table: &TableType,
style: &TableStyle,
vm_table_location: NonNull<VMTableDefinition>,
) -> Result<Self, String> {
unsafe { Self::new_inner(table, style, Some(vm_table_location)) }
}
unsafe fn new_inner(
table: &TableType,
style: &TableStyle,
vm_table_location: Option<NonNull<VMTableDefinition>>,
) -> Result<Self, String> {
unsafe {
match table.ty {
ValType::FuncRef | ValType::ExternRef => (),
ty => {
return Err(format!(
"tables of types other than funcref or externref ({ty})",
));
}
};
if let Some(max) = table.maximum
&& max < table.minimum
{
return Err(format!(
"Table minimum ({}) is larger than maximum ({})!",
table.minimum, max
));
}
if table.minimum as usize > TABLE_MAX_SIZE {
return Err(format!(
"Table minimum ({}) is larger than maximum allowed size ({TABLE_MAX_SIZE})!",
table.minimum
));
}
if let Some(max) = table.maximum
&& max as usize > TABLE_MAX_SIZE
{
return Err(format!(
"Table maximum ({max}) is larger than maximum allowed size ({TABLE_MAX_SIZE})!",
));
}
let table_minimum = usize::try_from(table.minimum)
.map_err(|_| "Table minimum is bigger than usize".to_string())?;
let mut vec = vec![RawTableElement::default(); table_minimum];
let base = vec.as_mut_ptr();
match style {
TableStyle::CallerChecksSignature => Ok(Self {
vec,
maximum: table.maximum,
table: *table,
style: style.clone(),
vm_table_definition: if let Some(table_loc) = vm_table_location {
{
let mut ptr = table_loc;
let td = ptr.as_mut();
td.base = base as _;
td.current_elements = table_minimum as _;
}
MaybeInstanceOwned::Instance(table_loc)
} else {
MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(VMTableDefinition {
base: base as _,
current_elements: table_minimum as _,
})))
},
}),
}
}
}
fn get_vm_table_definition(&self) -> NonNull<VMTableDefinition> {
self.vm_table_definition.as_ptr()
}
pub fn ty(&self) -> &TableType {
&self.table
}
pub fn style(&self) -> &TableStyle {
&self.style
}
pub fn size(&self) -> u32 {
unsafe {
let td_ptr = self.get_vm_table_definition();
let td = td_ptr.as_ref();
td.current_elements
}
}
pub fn grow(&mut self, delta: u32, init_value: TableElement) -> Option<u32> {
if self.table.readonly {
return None;
}
let size = self.size();
let new_len = size.checked_add(delta)?;
if self.maximum.is_some_and(|max| new_len > max) {
return None;
}
if new_len == size {
debug_assert_eq!(delta, 0);
return Some(size);
}
self.vec
.resize(usize::try_from(new_len).unwrap(), init_value.into());
unsafe {
let mut td_ptr = self.get_vm_table_definition();
let td = td_ptr.as_mut();
td.current_elements = new_len;
td.base = self.vec.as_mut_ptr() as _;
}
Some(size)
}
pub fn get(&self, index: u32) -> Option<TableElement> {
let raw_data = self.vec.get(index as usize).cloned()?;
Some(match self.table.ty {
ValType::ExternRef => TableElement::ExternRef(unsafe { raw_data.extern_ref }),
ValType::FuncRef => TableElement::FuncRef(unsafe { raw_data.func_ref }),
_ => todo!("getting invalid type from table, handle this error"),
})
}
pub fn set(&mut self, index: u32, reference: TableElement) -> Result<(), Trap> {
self.set_with_construction(index, reference, false)
}
pub(crate) fn set_with_construction(
&mut self,
index: u32,
reference: TableElement,
in_construction: bool,
) -> Result<(), Trap> {
if !in_construction && self.table.readonly {
return Err(Trap::lib(TrapCode::ReadonlyTableModified));
}
match self.vec.get_mut(index as usize) {
Some(slot) => {
match (self.table.ty, reference) {
(ValType::ExternRef, r @ TableElement::ExternRef(_)) => {
*slot = r.into();
}
(ValType::FuncRef, r @ TableElement::FuncRef(_)) => {
*slot = r.into();
}
(ty, v) => {
panic!("Attempted to set a table of type {ty} with the value {v:?}")
}
};
Ok(())
}
None => Err(Trap::lib(TrapCode::TableAccessOutOfBounds)),
}
}
pub fn vmtable(&self) -> NonNull<VMTableDefinition> {
self.get_vm_table_definition()
}
pub fn copy(
&mut self,
src_table: &Self,
dst_index: u32,
src_index: u32,
len: u32,
) -> Result<(), Trap> {
if self.table.readonly {
return Err(Trap::lib(TrapCode::ReadonlyTableModified));
}
if src_index
.checked_add(len)
.is_none_or(|n| n > src_table.size())
{
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
}
if dst_index.checked_add(len).is_none_or(|m| m > self.size()) {
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
}
let srcs = src_index..src_index + len;
let dsts = dst_index..dst_index + len;
if dst_index <= src_index {
for (s, d) in (srcs).zip(dsts) {
self.set(d, src_table.get(s).unwrap())?;
}
} else {
for (s, d) in srcs.rev().zip(dsts.rev()) {
self.set(d, src_table.get(s).unwrap())?;
}
}
Ok(())
}
pub fn copy_on_write(&self) -> Result<Self, String> {
let mut ret = Self::new(&self.table, &self.style)?;
ret.copy(self, 0, 0, self.size())
.map_err(|trap| format!("failed to copy the table - {trap:?}"))?;
Ok(ret)
}
pub fn copy_within(&mut self, dst_index: u32, src_index: u32, len: u32) -> Result<(), Trap> {
if self.table.readonly {
return Err(Trap::lib(TrapCode::ReadonlyTableModified));
}
if src_index.checked_add(len).is_none_or(|n| n > self.size()) {
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
}
if dst_index.checked_add(len).is_none_or(|m| m > self.size()) {
return Err(Trap::lib(TrapCode::TableAccessOutOfBounds));
}
let srcs = src_index..src_index + len;
let dsts = dst_index..dst_index + len;
if dst_index <= src_index {
for (s, d) in (srcs).zip(dsts) {
self.set(d, self.get(s).unwrap())?;
}
} else {
for (s, d) in srcs.rev().zip(dsts.rev()) {
self.set(d, self.get(s).unwrap())?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{TableElement, VMTable};
use wasmer_types::{TableStyle, TableType, Type};
#[test]
fn readonly_table_rejects_grow() {
let mut ty = TableType::new(Type::FuncRef, 0, Some(0));
ty.readonly = true;
let mut table = VMTable::new(&ty, &TableStyle::CallerChecksSignature).unwrap();
assert_eq!(table.grow(0, TableElement::FuncRef(None)), None);
}
}