use alloc::vec::Vec;
use core::hint::cold_path;
use tinywasm_types::{ExternRef, FuncRef, ValueCounts, WasmType, WasmValue};
use super::StackBase;
use crate::{
Result, Trap,
engine::{Config, StackConfig},
interpreter::*,
};
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) struct ValueStack {
pub(crate) stack_32: Stack<Value32>,
pub(crate) stack_64: Stack<Value64>,
pub(crate) stack_128: Stack<Value128>,
}
#[cfg_attr(feature = "debug", derive(Debug))]
pub(crate) struct Stack<T: Copy + Default> {
data: Vec<T>,
max_size: usize,
dynamic: bool,
}
impl<T: Copy + Default> Stack<T> {
pub(crate) fn new(config: StackConfig) -> Self {
Self { data: Vec::with_capacity(config.initial_size), max_size: config.max_size, dynamic: config.dynamic }
}
pub(crate) fn clear(&mut self) {
self.data.clear();
}
#[inline(always)]
pub(crate) fn len(&self) -> usize {
self.data.len()
}
#[inline(always)]
pub(crate) fn push(&mut self, value: T) -> Result<(), Trap> {
if !self.ensure_capacity_for(self.data.len() + 1) {
cold_path();
return Err(Trap::ValueStackOverflow);
}
self.data.push(value);
Ok(())
}
#[inline(always)]
pub(crate) fn pop(&mut self) -> T {
self.data.pop().unwrap_or_else(|| {
cold_path();
unreachable!("ValueStack underflow, this is a bug");
})
}
#[inline(always)]
pub(crate) fn last(&self) -> &T {
self.data.last().unwrap_or_else(|| {
cold_path();
unreachable!("ValueStack underflow, this is a bug");
})
}
#[inline(always)]
pub(crate) fn get(&self, index: usize) -> &T {
self.data.get(index).unwrap_or_else(|| {
cold_path();
unreachable!("Stack index out of bounds, this is a bug");
})
}
#[inline(always)]
pub(crate) fn set(&mut self, index: usize, value: T) {
*self.data.get_mut(index).unwrap_or_else(|| {
cold_path();
unreachable!("Stack index out of bounds, this is a bug");
}) = value;
}
#[inline(always)]
pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) {
let len = self.data.len();
debug_assert!(n <= len);
if n >= len {
return;
}
let keep = (len - n).min(end_keep);
self.data.copy_within(len - keep..len, n);
self.data.truncate(n + keep);
}
#[inline(always)]
pub(crate) fn truncate_to(&mut self, n: usize) {
debug_assert!(n <= self.data.len());
self.data.truncate(n);
}
#[inline(always)]
pub(crate) fn truncate_to_one_tail(&mut self, n: usize) {
debug_assert!(n < self.data.len());
let Some(last) = self.data.pop() else {
cold_path();
unreachable!("ValueStack underflow, this is a bug");
};
self.data.truncate(n);
self.data.push(last);
}
#[inline(always)]
pub(crate) fn enter_locals(&mut self, param_count: usize, local_count: usize) -> Result<u32, Trap> {
debug_assert!(param_count <= local_count);
debug_assert!(param_count <= self.data.len());
let len = self.data.len();
let start = len - param_count;
let end = start + local_count;
if end > self.data.capacity() {
cold_path();
if end > self.max_size || !self.dynamic {
return Err(Trap::ValueStackOverflow);
}
let cap = self.data.capacity();
let target = end.max(cap.max(1).saturating_mul(2)).min(self.max_size);
if self.data.try_reserve_exact(target - len).is_err() {
return Err(Trap::ValueStackOverflow);
}
}
self.data.resize(end, T::default());
Ok(start as u32)
}
fn ensure_capacity_for(&mut self, required_len: usize) -> bool {
let cap = self.data.capacity();
if required_len > cap {
cold_path();
if required_len > self.max_size || !self.dynamic {
return false;
}
let doubled = cap.max(1).saturating_mul(2);
let target = required_len.max(doubled).min(self.max_size);
let additional = target - cap;
if self.data.try_reserve_exact(additional).is_err() {
return false;
}
}
true
}
#[inline(always)]
pub(crate) fn select_many(&mut self, count: usize, condition: bool) {
if count == 0 {
return;
}
let len = self.data.len();
let needed = count.checked_mul(2).unwrap_or_else(|| {
cold_path();
unreachable!("Stack underflow, this is a bug");
});
if len < needed {
cold_path();
unreachable!("Stack underflow, this is a bug");
}
if !condition {
let dst = len - needed;
let src = len - count;
self.data.copy_within(src..len, dst);
}
self.data.truncate(len - count);
}
}
impl ValueStack {
pub(crate) fn new(config: &Config) -> Self {
Self {
stack_32: Stack::new(config.value_stack_32),
stack_64: Stack::new(config.value_stack_64),
stack_128: Stack::new(config.value_stack_128),
}
}
pub(crate) fn clear(&mut self) {
self.stack_32.clear();
self.stack_64.clear();
self.stack_128.clear();
}
#[inline(always)]
pub(crate) fn len(&self) -> usize {
self.stack_32.len() + self.stack_64.len() + self.stack_128.len()
}
#[inline(always)]
pub(crate) fn push<T: InternalValue>(&mut self, value: T) -> Result<(), Trap> {
T::stack_push(self, value)
}
#[inline]
pub(crate) fn select_multi(&mut self, counts: ValueCounts) {
let condition = i32::stack_pop(self) != 0;
self.stack_32.select_many(counts.c32 as usize, condition);
self.stack_64.select_many(counts.c64 as usize, condition);
self.stack_128.select_many(counts.c128 as usize, condition);
}
pub(crate) fn pop_types<'a>(
&'a mut self,
val_types: impl IntoIterator<Item = &'a WasmType>,
) -> impl core::iter::Iterator<Item = WasmValue> {
val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type))
}
#[inline(always)]
pub(crate) fn enter_locals(&mut self, params: &ValueCounts, locals: &ValueCounts) -> Result<StackBase, Trap> {
let locals_base32 = self.stack_32.enter_locals(params.c32 as usize, locals.c32 as usize)?;
let locals_base64 = self.stack_64.enter_locals(params.c64 as usize, locals.c64 as usize)?;
let locals_base128 = self.stack_128.enter_locals(params.c128 as usize, locals.c128 as usize)?;
Ok(StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128 })
}
#[inline(always)]
pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCounts) {
self.stack_32.truncate_keep(base.s32 as usize, keep.c32 as usize);
self.stack_64.truncate_keep(base.s64 as usize, keep.c64 as usize);
self.stack_128.truncate_keep(base.s128 as usize, keep.c128 as usize);
}
#[inline(always)]
pub(crate) fn truncate_to_base(&mut self, base: StackBase) {
self.stack_32.truncate_to(base.s32 as usize);
self.stack_64.truncate_to(base.s64 as usize);
self.stack_128.truncate_to(base.s128 as usize);
}
pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) -> Result<(), Trap> {
match value {
TinyWasmValue::Value32(v) => self.stack_32.push(v)?,
TinyWasmValue::Value64(v) => self.stack_64.push(v)?,
TinyWasmValue::Value128(v) => self.stack_128.push(v)?,
TinyWasmValue::ValueRef(v) => self.stack_32.push(v.raw())?,
}
Ok(())
}
pub(crate) fn pop_wasmvalue(&mut self, val_type: WasmType) -> WasmValue {
match val_type {
WasmType::I32 => WasmValue::I32(i32::stack_pop(self)),
WasmType::I64 => WasmValue::I64(i64::stack_pop(self)),
WasmType::F32 => WasmValue::F32(f32::stack_pop(self)),
WasmType::F64 => WasmValue::F64(f64::stack_pop(self)),
WasmType::RefExtern => WasmValue::RefExtern(ExternRef::from_raw(ValueRef::stack_pop(self).raw())),
WasmType::RefFunc => WasmValue::RefFunc(FuncRef::from_raw(ValueRef::stack_pop(self).raw())),
WasmType::V128 => WasmValue::V128(Value128::stack_pop(self).into()),
}
}
pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) -> Result<(), Trap> {
for value in values {
match value {
WasmValue::I32(v) => self.stack_32.push(*v as u32)?,
WasmValue::I64(v) => self.stack_64.push(*v as u64)?,
WasmValue::F32(v) => self.stack_32.push(v.to_bits())?,
WasmValue::F64(v) => self.stack_64.push(v.to_bits())?,
WasmValue::RefExtern(v) => self.stack_32.push(v.raw())?,
WasmValue::RefFunc(v) => self.stack_32.push(v.raw())?,
WasmValue::V128(v) => self.stack_128.push((*v).into())?,
}
}
Ok(())
}
}