use super::num_helpers::TinywasmFloatExt;
use super::stack::{CallFrame, ValueStack};
use crate::store::Globals;
use crate::{Error, Result, Store, WasmValue, interpreter::simd::Value128};
use tinywasm_types::{BinOp, BinOp128, CmpOp, GlobalAddr, LocalAddr, WasmType};
pub(crate) type Value32 = u32;
pub(crate) type Value64 = u64;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ValueRef(u32);
impl ValueRef {
const HOST_ANY_TAG: u32 = 1 << 30;
pub(crate) const NULL: Self = Self(0);
#[inline]
pub(crate) const fn from_raw(raw: u32) -> Self {
Self(raw)
}
#[inline]
pub(crate) const fn from_category_addr(addr: u32) -> Self {
let Some(raw) = addr.checked_add(1) else { panic!("reference address does not fit in the runtime encoding") };
let Some(raw) = raw.checked_mul(2) else { panic!("reference address does not fit in the runtime encoding") };
Self(raw)
}
#[inline]
pub(crate) const fn from_i31(value: i32) -> Self {
Self(((value as u32) << 1) | 1)
}
#[inline]
pub(crate) const fn try_from_host_any(addr: u32) -> Option<Self> {
if addr >= Self::HOST_ANY_TAG - 1 {
return None;
}
Some(Self::from_category_addr(addr | Self::HOST_ANY_TAG))
}
pub(crate) const fn is_host_any(self) -> bool {
matches!(self.addr(), Some(addr) if addr & Self::HOST_ANY_TAG != 0)
}
#[inline]
pub(crate) const fn addr(self) -> Option<u32> {
if self.is_null() || self.is_i31() { None } else { Some(self.0 / 2 - 1) }
}
#[inline]
pub(crate) const fn is_null(self) -> bool {
self.0 == Self::NULL.0
}
#[inline]
pub(crate) const fn is_i31(self) -> bool {
self.0 & 1 != 0
}
#[inline]
pub(crate) const fn i31_s(self) -> Option<i32> {
if self.is_i31() { Some((self.0 as i32) >> 1) } else { None }
}
#[inline]
pub(crate) const fn i31_u(self) -> Option<u32> {
if self.is_i31() { Some(self.0 >> 1) } else { None }
}
#[inline]
pub(crate) const fn raw(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RuntimeValue {
Value32(Value32),
Value64(Value64),
Value128(Value128),
ValueRef(ValueRef),
}
impl RuntimeValue {
pub(crate) fn into_wasm(self, store: &mut Store, ty: WasmType) -> Result<WasmValue> {
Ok(match (self, ty) {
(Self::Value32(value), WasmType::I32) => WasmValue::I32(value as i32),
(Self::Value32(value), WasmType::F32) => WasmValue::F32(f32::from_bits(value)),
(Self::Value64(value), WasmType::I64) => WasmValue::I64(value as i64),
(Self::Value64(value), WasmType::F64) => WasmValue::F64(f64::from_bits(value)),
(Self::Value128(value), WasmType::V128) => WasmValue::V128(value.0),
(Self::ValueRef(value), WasmType::Ref(ty)) => WasmValue::Ref(store.decode_ref(value, ty)?),
_ => return Err(Error::other("internal value does not match its WebAssembly type")),
})
}
}
pub(crate) trait InternalValue: Copy + Default {
fn stack_push(stack: &mut ValueStack, value: Self) -> Result<(), crate::Trap>;
fn stack_pop(stack: &mut ValueStack) -> Self;
fn stack_peek(stack: &ValueStack) -> Self;
fn stack_update(stack: &mut ValueStack, f: impl FnOnce(Self) -> Self) -> Self;
fn stack_select(stack: &mut ValueStack);
fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self;
fn local_push(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr) -> Result<(), crate::Trap>;
fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self);
fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, f: impl FnOnce(Self) -> Self) -> Self;
fn local_copy(stack: &mut ValueStack, frame: &CallFrame, from: LocalAddr, to: LocalAddr);
fn global_get(globals: &Globals, addr: GlobalAddr) -> Self;
fn global_set(globals: &mut Globals, addr: GlobalAddr, value: Self);
fn global_update(globals: &mut Globals, addr: GlobalAddr, f: impl FnOnce(Self) -> Self) -> Self;
}
macro_rules! impl_internalvalue {
(
$(
$stack:ident, $stack_base:ident, $global_get:ident, $global_set:ident, $outer:ty,
|$to_stack_v:ident| $to_stack:expr,
|$from_stack_v:ident| $from_stack:expr
)*
) => {
$(
impl InternalValue for $outer {
#[inline(always)]
fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self {
let $from_stack_v =
*stack.$stack.get(frame.locals_base.$stack_base as usize + index as usize);
$from_stack
}
#[inline(always)]
fn local_push(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr) -> Result<(), crate::Trap> {
stack.$stack.push_copy(frame.locals_base.$stack_base as usize + index as usize)
}
#[inline(always)]
fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) {
let $to_stack_v = value;
let abs_index = frame.locals_base.$stack_base as usize + index as usize;
stack.$stack.set(abs_index, $to_stack);
}
#[inline(always)]
fn local_update(
stack: &mut ValueStack,
frame: &CallFrame,
index: LocalAddr,
f: impl FnOnce(Self) -> Self,
) -> Self {
let abs_index = frame.locals_base.$stack_base as usize + index as usize;
let $from_stack_v = *stack.$stack.get(abs_index);
let value = f($from_stack);
let $to_stack_v = value;
stack.$stack.set(abs_index, $to_stack);
value
}
#[inline(always)]
fn local_copy(stack: &mut ValueStack, frame: &CallFrame, from: LocalAddr, to: LocalAddr) {
let from = frame.locals_base.$stack_base as usize + from as usize;
let to = frame.locals_base.$stack_base as usize + to as usize;
stack.$stack.copy(from, to);
}
#[inline(always)]
fn global_get(globals: &Globals, addr: GlobalAddr) -> Self {
let $from_stack_v = globals.$global_get(addr);
$from_stack
}
#[inline(always)]
fn global_set(globals: &mut Globals, addr: GlobalAddr, value: Self) {
let $to_stack_v = value;
globals.$global_set(addr, $to_stack);
}
#[inline(always)]
fn global_update(globals: &mut Globals, addr: GlobalAddr, f: impl FnOnce(Self) -> Self) -> Self {
let $from_stack_v = globals.$global_get(addr);
let value = f($from_stack);
let $to_stack_v = value;
globals.$global_set(addr, $to_stack);
value
}
#[inline(always)]
fn stack_push(stack: &mut ValueStack, value: Self) -> Result<(), crate::Trap> {
let $to_stack_v = value;
cold_err!(stack.$stack.push($to_stack))?;
Ok(())
}
#[inline(always)]
fn stack_pop(stack: &mut ValueStack) -> Self {
let $from_stack_v = stack.$stack.pop();
$from_stack
}
#[inline(always)]
fn stack_peek(stack: &ValueStack) -> Self {
let $from_stack_v = *stack.$stack.last();
$from_stack
}
#[inline(always)]
fn stack_update(stack: &mut ValueStack, f: impl FnOnce(Self) -> Self) -> Self {
let index = stack.$stack.len() - 1;
let $from_stack_v = *stack.$stack.get(index);
let value = f($from_stack);
let $to_stack_v = value;
stack.$stack.set(index, $to_stack);
value
}
#[inline(always)]
fn stack_select(stack: &mut ValueStack) {
let cond = stack.stack_32.pop() as i32;
let val2 = stack.$stack.pop();
if cond == 0 {
let val1 = stack.$stack.len() - 1;
stack.$stack.set(val1, val2);
}
}
}
)*
};
}
impl_internalvalue! {
stack_32, s32, get_32, set_32, u32, |v| v, |v| v
stack_64, s64, get_64, set_64, u64, |v| v, |v| v
stack_32, s32, get_32, set_32, i32, |v| v as u32, |v| v as i32
stack_64, s64, get_64, set_64, i64, |v| v as u64, |v| v as i64
stack_32, s32, get_32, set_32, f32, |v| f32::to_bits(v), |v| f32::from_bits(v)
stack_64, s64, get_64, set_64, f64, |v| f64::to_bits(v), |v| f64::from_bits(v)
stack_32, s32, get_32, set_32, ValueRef, |v| v.raw(), |v| ValueRef(v)
stack_128, s128, get_128, set_128, Value128, |v| v, |v| v
}
pub(crate) trait BinOpExt<T>: Copy {
fn exec(self, lhs: T, rhs: T) -> T;
}
pub(crate) trait CmpOpExt<T>: Copy {
fn cmp(self, lhs: T, rhs: T) -> bool;
}
macro_rules! impl_value_ops {
($unsigned:ty, $signed:ty, $binop:ident, $cmp:ident) => {
impl BinOpExt<$unsigned> for BinOp {
#[inline(always)]
fn exec(self, lhs: $unsigned, rhs: $unsigned) -> $unsigned {
$binop(self, lhs, rhs)
}
}
impl CmpOpExt<$signed> for CmpOp {
#[inline(always)]
fn cmp(self, lhs: $signed, rhs: $signed) -> bool {
$cmp(self, lhs, rhs)
}
}
};
}
impl_value_ops!(Value32, i32, exec_binop_32, exec_cmp_32);
impl_value_ops!(Value64, i64, exec_binop_64, exec_cmp_64);
fn exec_binop_32(op: BinOp, lhs: u32, rhs: u32) -> u32 {
match op {
BinOp::IAdd => lhs.wrapping_add(rhs),
BinOp::ISub => lhs.wrapping_sub(rhs),
BinOp::IMul => lhs.wrapping_mul(rhs),
BinOp::IAnd => lhs & rhs,
BinOp::IOr => lhs | rhs,
BinOp::IXor => lhs ^ rhs,
BinOp::IShl => (lhs as i32).wrapping_shl(rhs) as u32,
BinOp::IShrS => (lhs as i32).wrapping_shr(rhs) as u32,
BinOp::IShrU => lhs.wrapping_shr(rhs),
BinOp::IRotl => (lhs as i32).rotate_left(rhs) as u32,
BinOp::IRotr => (lhs as i32).rotate_right(rhs) as u32,
BinOp::FAdd => (f32::from_bits(lhs) + f32::from_bits(rhs)).to_bits(),
BinOp::FSub => (f32::from_bits(lhs) - f32::from_bits(rhs)).to_bits(),
BinOp::FMul => (f32::from_bits(lhs) * f32::from_bits(rhs)).to_bits(),
BinOp::FDiv => (f32::from_bits(lhs) / f32::from_bits(rhs)).to_bits(),
BinOp::FMin => f32::from_bits(lhs).tw_minimum(f32::from_bits(rhs)).to_bits(),
BinOp::FMax => f32::from_bits(lhs).tw_maximum(f32::from_bits(rhs)).to_bits(),
BinOp::FCopysign => f32::from_bits(lhs).copysign(f32::from_bits(rhs)).to_bits(),
}
}
fn exec_binop_64(op: BinOp, lhs: u64, rhs: u64) -> u64 {
match op {
BinOp::IAdd => lhs.wrapping_add(rhs),
BinOp::ISub => lhs.wrapping_sub(rhs),
BinOp::IMul => lhs.wrapping_mul(rhs),
BinOp::IAnd => lhs & rhs,
BinOp::IOr => lhs | rhs,
BinOp::IXor => lhs ^ rhs,
BinOp::IShl => (lhs as i64).wrapping_shl(rhs as u32) as u64,
BinOp::IShrS => (lhs as i64).wrapping_shr(rhs as u32) as u64,
BinOp::IShrU => lhs.wrapping_shr(rhs as u32),
BinOp::IRotl => (lhs as i64).rotate_left(rhs as u32) as u64,
BinOp::IRotr => (lhs as i64).rotate_right(rhs as u32) as u64,
BinOp::FAdd => (f64::from_bits(lhs) + f64::from_bits(rhs)).to_bits(),
BinOp::FSub => (f64::from_bits(lhs) - f64::from_bits(rhs)).to_bits(),
BinOp::FMul => (f64::from_bits(lhs) * f64::from_bits(rhs)).to_bits(),
BinOp::FDiv => (f64::from_bits(lhs) / f64::from_bits(rhs)).to_bits(),
BinOp::FMin => f64::from_bits(lhs).tw_minimum(f64::from_bits(rhs)).to_bits(),
BinOp::FMax => f64::from_bits(lhs).tw_maximum(f64::from_bits(rhs)).to_bits(),
BinOp::FCopysign => f64::from_bits(lhs).copysign(f64::from_bits(rhs)).to_bits(),
}
}
fn exec_cmp_32(op: CmpOp, lhs: i32, rhs: i32) -> bool {
match op {
CmpOp::Eq => lhs == rhs,
CmpOp::Ne => lhs != rhs,
CmpOp::LtS => lhs < rhs,
CmpOp::LtU => (lhs as u32) < (rhs as u32),
CmpOp::GtS => lhs > rhs,
CmpOp::GtU => (lhs as u32) > (rhs as u32),
CmpOp::LeS => lhs <= rhs,
CmpOp::LeU => (lhs as u32) <= (rhs as u32),
CmpOp::GeS => lhs >= rhs,
CmpOp::GeU => (lhs as u32) >= (rhs as u32),
}
}
fn exec_cmp_64(op: CmpOp, lhs: i64, rhs: i64) -> bool {
match op {
CmpOp::Eq => lhs == rhs,
CmpOp::Ne => lhs != rhs,
CmpOp::LtS => lhs < rhs,
CmpOp::LtU => (lhs as u64) < (rhs as u64),
CmpOp::GtS => lhs > rhs,
CmpOp::GtU => (lhs as u64) > (rhs as u64),
CmpOp::LeS => lhs <= rhs,
CmpOp::LeU => (lhs as u64) <= (rhs as u64),
CmpOp::GeS => lhs >= rhs,
CmpOp::GeU => (lhs as u64) >= (rhs as u64),
}
}
impl BinOpExt<Value128> for BinOp128 {
#[inline(always)]
fn exec(self, lhs: Value128, rhs: Value128) -> Value128 {
match self {
BinOp128::And => lhs.v128_and(rhs),
BinOp128::AndNot => lhs.v128_andnot(rhs),
BinOp128::Or => lhs.v128_or(rhs),
BinOp128::Xor => lhs.v128_xor(rhs),
BinOp128::I64x2Add => lhs.i64x2_add(rhs),
BinOp128::I64x2Mul => lhs.i64x2_mul(rhs),
}
}
}
#[cfg(test)]
mod tests {
use super::ValueRef;
#[test]
fn value_ref_remains_four_bytes() {
assert_eq!(core::mem::size_of::<ValueRef>(), 4);
}
}