tinywasm 0.11.0

A tiny WebAssembly interpreter
Documentation
use alloc::{format, vec::Vec};
use tinywasm_types::*;

use super::{State, default_value};
use crate::interpreter::{InternalValue, RuntimeValue, Value32, Value64, Value128, ValueRef};
use crate::{Error, Result, Trap};

fn resolve<T: Copy>(items: &[T], index: u32, kind: &str) -> Result<T> {
    items.get(index as usize).copied().ok_or_else(|| Error::Other(format!("{kind} {index} not found")))
}

fn pop_value(stack: &mut Vec<RuntimeValue>, storage: StorageType) -> Result<RuntimeValue> {
    let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?;
    match (storage, value) {
        (StorageType::I8, RuntimeValue::Value32(value)) => Ok(RuntimeValue::Value32(value as u8 as u32)),
        (StorageType::I16, RuntimeValue::Value32(value)) => Ok(RuntimeValue::Value32(value as u16 as u32)),
        (StorageType::Value(WasmType::I32 | WasmType::F32), value @ RuntimeValue::Value32(_))
        | (StorageType::Value(WasmType::I64 | WasmType::F64), value @ RuntimeValue::Value64(_))
        | (StorageType::Value(WasmType::V128), value @ RuntimeValue::Value128(_))
        | (StorageType::Value(WasmType::Ref(_)), value @ RuntimeValue::ValueRef(_)) => Ok(value),
        _ => Err(Error::other("type mismatch in GC constant")),
    }
}

fn value_ref(value: &RuntimeValue) -> Option<ValueRef> {
    match value {
        RuntimeValue::ValueRef(value) => Some(*value),
        _ => None,
    }
}

fn alloc_object(
    state: &mut State,
    stack: &mut Vec<RuntimeValue>,
    type_addr: TypeAddr,
    values: Vec<RuntimeValue>,
) -> Result<()> {
    let roots = stack.iter().filter_map(value_ref);
    let reference = state.alloc_gc_object(type_addr, values, roots)?;
    stack.push(RuntimeValue::ValueRef(reference));
    Ok(())
}

#[inline]
pub(super) fn eval_const(
    state: &mut State,
    instructions: &[ConstInstruction],
    global_addrs: &[GlobalAddr],
    func_addrs: &[FuncAddr],
    type_addrs: &[TypeAddr],
) -> Result<RuntimeValue> {
    use ConstInstruction::*;

    if let [instruction] = instructions {
        match instruction {
            I32Const(value) => return Ok(RuntimeValue::Value32(*value as u32)),
            I64Const(value) => return Ok(RuntimeValue::Value64(*value as u64)),
            F32Const(value) => return Ok(RuntimeValue::Value32(value.to_bits())),
            F64Const(value) => return Ok(RuntimeValue::Value64(value.to_bits())),
            V128Const(value) => return Ok(RuntimeValue::Value128(Value128(*value))),
            GlobalGet32(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                return Ok(RuntimeValue::Value32(Value32::global_get(&state.globals, addr)));
            }
            GlobalGet64(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                return Ok(RuntimeValue::Value64(Value64::global_get(&state.globals, addr)));
            }
            GlobalGet128(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                return Ok(RuntimeValue::Value128(Value128::global_get(&state.globals, addr)));
            }
            GlobalGetRef(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                return Ok(RuntimeValue::ValueRef(ValueRef::global_get(&state.globals, addr)));
            }
            RefNull(_) => return Ok(RuntimeValue::ValueRef(ValueRef::NULL)),
            RefFunc(func) => {
                let addr = resolve(func_addrs, func.index(), "function")?;
                return Ok(RuntimeValue::ValueRef(ValueRef::from_category_addr(addr)));
            }
            _ => {}
        }
    }

    let mut stack = Vec::new();
    for instruction in instructions {
        match instruction {
            I32Const(value) => stack.push(RuntimeValue::Value32(*value as u32)),
            I64Const(value) => stack.push(RuntimeValue::Value64(*value as u64)),
            F32Const(value) => stack.push(RuntimeValue::Value32(value.to_bits())),
            F64Const(value) => stack.push(RuntimeValue::Value64(value.to_bits())),
            V128Const(value) => stack.push(RuntimeValue::Value128(Value128(*value))),
            GlobalGet32(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                stack.push(RuntimeValue::Value32(Value32::global_get(&state.globals, addr)));
            }
            GlobalGet64(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                stack.push(RuntimeValue::Value64(Value64::global_get(&state.globals, addr)));
            }
            GlobalGet128(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                stack.push(RuntimeValue::Value128(Value128::global_get(&state.globals, addr)));
            }
            GlobalGetRef(index) => {
                let addr = resolve(global_addrs, *index, "global")?;
                stack.push(RuntimeValue::ValueRef(ValueRef::global_get(&state.globals, addr)));
            }
            RefNull(_) => stack.push(RuntimeValue::ValueRef(ValueRef::NULL)),
            RefFunc(func) => {
                let addr = resolve(func_addrs, func.index(), "function")?;
                stack.push(RuntimeValue::ValueRef(ValueRef::from_category_addr(addr)));
            }
            RefI31 => {
                let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?;
                let RuntimeValue::Value32(value) = value else {
                    return Err(Error::other("type mismatch in const ref.i31"));
                };
                stack.push(RuntimeValue::ValueRef(ValueRef::from_i31(value as i32)));
            }
            AnyConvertExtern | ExternConvertAny => {
                let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?;
                if !matches!(value, RuntimeValue::ValueRef(_)) {
                    return Err(Error::other("type mismatch in const reference conversion"));
                }
                stack.push(value);
            }
            StructNew(type_index) | StructNewDefault(type_index) => {
                let type_addr = resolve(type_addrs, *type_index, "type")?;
                let field_count = state
                    .get_type(type_addr)
                    .as_struct()
                    .ok_or_else(|| Error::other("GC constant type is not a struct"))?
                    .fields
                    .len();
                let default = matches!(instruction, StructNewDefault(_));
                state.check_gc_allocation(type_addr, field_count)?;
                let mut values = Vec::new();
                cold_err!(values.try_reserve_exact(field_count)).map_err(|_| Trap::OutOfMemory)?;
                if default {
                    values.extend(
                        state
                            .get_type(type_addr)
                            .as_struct()
                            .unwrap()
                            .fields
                            .iter()
                            .map(|field| default_value(field.storage)),
                    );
                } else {
                    for index in (0..field_count).rev() {
                        let storage = state.get_type(type_addr).as_struct().unwrap().fields[index].storage;
                        values.push(pop_value(&mut stack, storage)?);
                    }
                    values.reverse();
                }
                alloc_object(state, &mut stack, type_addr, values)?;
            }
            ArrayNew(type_index) | ArrayNewDefault(type_index) => {
                let type_addr = resolve(type_addrs, *type_index, "type")?;
                let storage = state
                    .get_type(type_addr)
                    .as_array()
                    .ok_or_else(|| Error::other("GC constant type is not an array"))?
                    .field
                    .storage;
                let Some(RuntimeValue::Value32(len)) = stack.pop() else {
                    return Err(Error::other("type mismatch in const array length"));
                };
                state.check_gc_allocation(type_addr, len as usize)?;
                let value = if matches!(instruction, ArrayNewDefault(_)) {
                    default_value(storage)
                } else {
                    pop_value(&mut stack, storage)?
                };
                let mut values = Vec::new();
                cold_err!(values.try_reserve_exact(len as usize)).map_err(|_| Trap::OutOfMemory)?;
                values.resize(len as usize, value);
                alloc_object(state, &mut stack, type_addr, values)?;
            }
            ArrayNewFixed(type_index, len) => {
                let type_addr = resolve(type_addrs, *type_index, "type")?;
                let storage = state
                    .get_type(type_addr)
                    .as_array()
                    .ok_or_else(|| Error::other("GC constant type is not an array"))?
                    .field
                    .storage;
                state.check_gc_allocation(type_addr, *len as usize)?;
                let mut values = Vec::new();
                cold_err!(values.try_reserve_exact(*len as usize)).map_err(|_| Trap::OutOfMemory)?;
                for _ in 0..*len {
                    values.push(pop_value(&mut stack, storage)?);
                }
                values.reverse();
                alloc_object(state, &mut stack, type_addr, values)?;
            }
            I32Add | I32Sub | I32Mul => {
                let rhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?;
                let lhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?;
                let (RuntimeValue::Value32(lhs), RuntimeValue::Value32(rhs)) = (lhs, rhs) else {
                    return cold!(Err(Error::other("type mismatch in const i32 op")));
                };
                let out = match instruction {
                    I32Add => (lhs as i32).wrapping_add(rhs as i32),
                    I32Sub => (lhs as i32).wrapping_sub(rhs as i32),
                    I32Mul => (lhs as i32).wrapping_mul(rhs as i32),
                    _ => unreachable!(),
                };
                stack.push(RuntimeValue::Value32(out as u32));
            }
            I64Add | I64Sub | I64Mul => {
                let rhs = stack.pop();
                let lhs = stack.pop();
                let (Some(RuntimeValue::Value64(lhs)), Some(RuntimeValue::Value64(rhs))) = (lhs, rhs) else {
                    return cold!(Err(Error::other("type mismatch in const i64 op")));
                };
                let out = match instruction {
                    I64Add => (lhs as i64).wrapping_add(rhs as i64),
                    I64Sub => (lhs as i64).wrapping_sub(rhs as i64),
                    I64Mul => (lhs as i64).wrapping_mul(rhs as i64),
                    _ => unreachable!(),
                };
                stack.push(RuntimeValue::Value64(out as u64));
            }
        }
    }

    let Some(value) = stack.pop() else {
        return cold!(Err(Error::other("empty const expression")));
    };
    if !stack.is_empty() {
        return cold!(Err(Error::other("const expression did not reduce to single value")));
    }
    Ok(value)
}