use crate::externref::VMExternRef;
use crate::table::{Table, TableElementType};
use crate::vmcontext::VMFuncRef;
use crate::{Instance, TrapReason};
use anyhow::Result;
use std::mem;
use std::ptr::{self, NonNull};
use std::time::{Duration, Instant};
use wasmtime_environ::{
DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, Trap,
};
pub mod trampolines {
use crate::{Instance, TrapReason, VMContext};
macro_rules! libcall {
(
$(
$( #[$attr:meta] )*
$name:ident( vmctx: vmctx $(, $pname:ident: $param:ident )* ) $( -> $result:ident )?;
)*
) => {paste::paste! {
$(
// The actual libcall itself, which has the `pub` name here, is
// defined via the `wasm_to_libcall_trampoline!` macro on
// supported platforms or otherwise in inline assembly for
// platforms like s390x which don't have stable `global_asm!`
// yet.
extern "C" {
#[allow(missing_docs)]
#[allow(improper_ctypes)]
#[wasmtime_versioned_export_macros::versioned_link]
pub fn $name(
vmctx: *mut VMContext,
$( $pname: libcall!(@ty $param), )*
) $(-> libcall!(@ty $result))?;
}
wasm_to_libcall_trampoline!($name ; [<impl_ $name>]);
#[cfg_attr(target_arch = "s390x", wasmtime_versioned_export_macros::versioned_export)]
unsafe extern "C" fn [<impl_ $name>](
vmctx: *mut VMContext,
$( $pname : libcall!(@ty $param), )*
) $( -> libcall!(@ty $result))? {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
Instance::from_vmctx(vmctx, |instance| {
super::$name(instance, $($pname),*)
})
}));
match result {
Ok(ret) => LibcallResult::convert(ret),
Err(panic) => crate::traphandlers::resume_panic(panic),
}
}
#[allow(non_upper_case_globals)]
#[used]
static [<impl_ $name _ref>]: unsafe extern "C" fn(
*mut VMContext,
$( $pname : libcall!(@ty $param), )*
) $( -> libcall!(@ty $result))? = [<impl_ $name>];
)*
}};
(@ty i32) => (u32);
(@ty i64) => (u64);
(@ty reference) => (*mut u8);
(@ty pointer) => (*mut u8);
(@ty vmctx) => (*mut VMContext);
}
wasmtime_environ::foreach_builtin_function!(libcall);
trait LibcallResult {
type Abi;
unsafe fn convert(self) -> Self::Abi;
}
impl LibcallResult for () {
type Abi = ();
unsafe fn convert(self) {}
}
impl<T, E> LibcallResult for Result<T, E>
where
E: Into<TrapReason>,
{
type Abi = T;
unsafe fn convert(self) -> T {
match self {
Ok(t) => t,
Err(e) => crate::traphandlers::raise_trap(e.into()),
}
}
}
impl LibcallResult for *mut u8 {
type Abi = *mut u8;
unsafe fn convert(self) -> *mut u8 {
self
}
}
}
fn memory32_grow(
instance: &mut Instance,
delta: u64,
memory_index: u32,
) -> Result<*mut u8, TrapReason> {
let memory_index = MemoryIndex::from_u32(memory_index);
let result =
match instance
.memory_grow(memory_index, delta)
.map_err(|error| TrapReason::User {
error,
needs_backtrace: true,
})? {
Some(size_in_bytes) => size_in_bytes / (wasmtime_environ::WASM_PAGE_SIZE as usize),
None => usize::max_value(),
};
Ok(result as *mut _)
}
unsafe fn table_grow(
instance: &mut Instance,
table_index: u32,
delta: u32,
init_value: *mut u8,
) -> Result<u32> {
let table_index = TableIndex::from_u32(table_index);
let element = match instance.table_element_type(table_index) {
TableElementType::Func => (init_value as *mut VMFuncRef).into(),
TableElementType::Extern => {
let init_value = if init_value.is_null() {
None
} else {
Some(VMExternRef::clone_from_raw(init_value))
};
init_value.into()
}
};
Ok(match instance.table_grow(table_index, delta, element)? {
Some(r) => r,
None => -1_i32 as u32,
})
}
use table_grow as table_grow_func_ref;
use table_grow as table_grow_externref;
unsafe fn table_fill(
instance: &mut Instance,
table_index: u32,
dst: u32,
val: *mut u8,
len: u32,
) -> Result<(), Trap> {
let table_index = TableIndex::from_u32(table_index);
let table = &mut *instance.get_table(table_index);
match table.element_type() {
TableElementType::Func => {
let val = val as *mut VMFuncRef;
table.fill(dst, val.into(), len)
}
TableElementType::Extern => {
let val = if val.is_null() {
None
} else {
Some(VMExternRef::clone_from_raw(val))
};
table.fill(dst, val.into(), len)
}
}
}
use table_fill as table_fill_func_ref;
use table_fill as table_fill_externref;
unsafe fn table_copy(
instance: &mut Instance,
dst_table_index: u32,
src_table_index: u32,
dst: u32,
src: u32,
len: u32,
) -> Result<(), Trap> {
let dst_table_index = TableIndex::from_u32(dst_table_index);
let src_table_index = TableIndex::from_u32(src_table_index);
let dst_table = instance.get_table(dst_table_index);
let src_range = src..(src.checked_add(len).unwrap_or(u32::MAX));
let src_table = instance.get_table_with_lazy_init(src_table_index, src_range);
Table::copy(dst_table, src_table, dst, src, len)
}
fn table_init(
instance: &mut Instance,
table_index: u32,
elem_index: u32,
dst: u32,
src: u32,
len: u32,
) -> Result<(), Trap> {
let table_index = TableIndex::from_u32(table_index);
let elem_index = ElemIndex::from_u32(elem_index);
instance.table_init(table_index, elem_index, dst, src, len)
}
fn elem_drop(instance: &mut Instance, elem_index: u32) {
let elem_index = ElemIndex::from_u32(elem_index);
instance.elem_drop(elem_index)
}
fn memory_copy(
instance: &mut Instance,
dst_index: u32,
dst: u64,
src_index: u32,
src: u64,
len: u64,
) -> Result<(), Trap> {
let src_index = MemoryIndex::from_u32(src_index);
let dst_index = MemoryIndex::from_u32(dst_index);
instance.memory_copy(dst_index, dst, src_index, src, len)
}
fn memory_fill(
instance: &mut Instance,
memory_index: u32,
dst: u64,
val: u32,
len: u64,
) -> Result<(), Trap> {
let memory_index = MemoryIndex::from_u32(memory_index);
instance.memory_fill(memory_index, dst, val as u8, len)
}
fn memory_init(
instance: &mut Instance,
memory_index: u32,
data_index: u32,
dst: u64,
src: u32,
len: u32,
) -> Result<(), Trap> {
let memory_index = MemoryIndex::from_u32(memory_index);
let data_index = DataIndex::from_u32(data_index);
instance.memory_init(memory_index, data_index, dst, src, len)
}
fn ref_func(instance: &mut Instance, func_index: u32) -> *mut u8 {
instance
.get_func_ref(FuncIndex::from_u32(func_index))
.expect("ref_func: funcref should always be available for given func index")
.cast()
}
fn data_drop(instance: &mut Instance, data_index: u32) {
let data_index = DataIndex::from_u32(data_index);
instance.data_drop(data_index)
}
unsafe fn table_get_lazy_init_func_ref(
instance: &mut Instance,
table_index: u32,
index: u32,
) -> *mut u8 {
let table_index = TableIndex::from_u32(table_index);
let table = instance.get_table_with_lazy_init(table_index, std::iter::once(index));
let elem = (*table)
.get(index)
.expect("table access already bounds-checked");
elem.into_ref_asserting_initialized()
}
unsafe fn drop_externref(_instance: &mut Instance, externref: *mut u8) {
let externref = externref as *mut crate::externref::VMExternData;
let externref = NonNull::new(externref).unwrap().into();
crate::externref::VMExternData::drop_and_dealloc(externref);
}
unsafe fn activations_table_insert_with_gc(instance: &mut Instance, externref: *mut u8) {
let externref = VMExternRef::clone_from_raw(externref);
let limits = *instance.runtime_limits();
let (activations_table, module_info_lookup) = (*instance.store()).externref_activations_table();
activations_table.insert_without_gc(externref.clone());
activations_table.insert_with_gc(limits, externref, module_info_lookup);
}
unsafe fn externref_global_get(instance: &mut Instance, index: u32) -> *mut u8 {
let index = GlobalIndex::from_u32(index);
let limits = *instance.runtime_limits();
let global = instance.defined_or_imported_global_ptr(index);
match (*global).as_externref().clone() {
None => ptr::null_mut(),
Some(externref) => {
let raw = externref.as_raw();
let (activations_table, module_info_lookup) =
(*instance.store()).externref_activations_table();
activations_table.insert_with_gc(limits, externref, module_info_lookup);
raw
}
}
}
unsafe fn externref_global_set(instance: &mut Instance, index: u32, externref: *mut u8) {
let externref = if externref.is_null() {
None
} else {
Some(VMExternRef::clone_from_raw(externref))
};
let index = GlobalIndex::from_u32(index);
let global = instance.defined_or_imported_global_ptr(index);
let old = mem::replace((*global).as_externref_mut(), externref);
drop(old);
}
fn memory_atomic_notify(
instance: &mut Instance,
memory_index: u32,
addr_index: u64,
count: u32,
) -> Result<u32, Trap> {
let memory = MemoryIndex::from_u32(memory_index);
instance
.get_runtime_memory(memory)
.atomic_notify(addr_index, count)
}
fn memory_atomic_wait32(
instance: &mut Instance,
memory_index: u32,
addr_index: u64,
expected: u32,
timeout: u64,
) -> Result<u32, Trap> {
let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));
let memory = MemoryIndex::from_u32(memory_index);
Ok(instance
.get_runtime_memory(memory)
.atomic_wait32(addr_index, expected, timeout)? as u32)
}
fn memory_atomic_wait64(
instance: &mut Instance,
memory_index: u32,
addr_index: u64,
expected: u64,
timeout: u64,
) -> Result<u32, Trap> {
let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));
let memory = MemoryIndex::from_u32(memory_index);
Ok(instance
.get_runtime_memory(memory)
.atomic_wait64(addr_index, expected, timeout)? as u32)
}
unsafe fn out_of_gas(instance: &mut Instance) -> Result<()> {
(*instance.store()).out_of_gas()
}
unsafe fn new_epoch(instance: &mut Instance) -> Result<u64> {
(*instance.store()).new_epoch()
}
#[allow(missing_docs)]
pub mod relocs {
pub extern "C" fn floorf32(f: f32) -> f32 {
f.floor()
}
pub extern "C" fn floorf64(f: f64) -> f64 {
f.floor()
}
pub extern "C" fn ceilf32(f: f32) -> f32 {
f.ceil()
}
pub extern "C" fn ceilf64(f: f64) -> f64 {
f.ceil()
}
pub extern "C" fn truncf32(f: f32) -> f32 {
f.trunc()
}
pub extern "C" fn truncf64(f: f64) -> f64 {
f.trunc()
}
const TOINT_32: f32 = 1.0 / f32::EPSILON;
const TOINT_64: f64 = 1.0 / f64::EPSILON;
pub extern "C" fn nearestf32(x: f32) -> f32 {
let i = x.to_bits();
let e = i >> 23 & 0xff;
if e >= 0x7f_u32 + 23 {
if e == 0xff {
if i & 0x7fffff != 0 {
return f32::from_bits(i | (1 << 22));
}
}
x
} else {
(x.abs() + TOINT_32 - TOINT_32).copysign(x)
}
}
pub extern "C" fn nearestf64(x: f64) -> f64 {
let i = x.to_bits();
let e = i >> 52 & 0x7ff;
if e >= 0x3ff_u64 + 52 {
if e == 0x7ff {
if i & 0xfffffffffffff != 0 {
return f64::from_bits(i | (1 << 51));
}
}
x
} else {
(x.abs() + TOINT_64 - TOINT_64).copysign(x)
}
}
pub extern "C" fn fmaf32(a: f32, b: f32, c: f32) -> f32 {
a.mul_add(b, c)
}
pub extern "C" fn fmaf64(a: f64, b: f64, c: f64) -> f64 {
a.mul_add(b, c)
}
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::__m128i;
#[cfg(target_arch = "x86_64")]
#[allow(improper_ctypes_definitions)]
pub extern "C" fn x86_pshufb(a: __m128i, b: __m128i) -> __m128i {
union U {
reg: __m128i,
mem: [u8; 16],
}
unsafe {
let a = U { reg: a }.mem;
let b = U { reg: b }.mem;
let select = |arr: &[u8; 16], byte: u8| {
if byte & 0x80 != 0 {
0x00
} else {
arr[(byte & 0xf) as usize]
}
};
U {
mem: [
select(&a, b[0]),
select(&a, b[1]),
select(&a, b[2]),
select(&a, b[3]),
select(&a, b[4]),
select(&a, b[5]),
select(&a, b[6]),
select(&a, b[7]),
select(&a, b[8]),
select(&a, b[9]),
select(&a, b[10]),
select(&a, b[11]),
select(&a, b[12]),
select(&a, b[13]),
select(&a, b[14]),
select(&a, b[15]),
],
}
.reg
}
}
}