tinywasm 0.9.0

A tiny WebAssembly interpreter
Documentation
use eyre::Result;
use tinywasm::engine::{Config, FuelPolicy};
use tinywasm::{ExecProgress, ModuleInstance, types::WasmValue};

#[cfg(feature = "std")]
use std::time::Duration;

const FIBONACCI_WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm");
const ADD_WASM: &[u8] = include_bytes!("../../../examples/wasm/add.wasm");

#[test]
fn typed_resume_matches_non_budgeted_call() -> Result<()> {
    let module = tinywasm::parse_bytes(FIBONACCI_WASM)?;

    let mut store_full = tinywasm::Store::default();
    let instance_full = ModuleInstance::instantiate(&mut store_full, &module, None)?;
    let func_full = instance_full.func::<i32, i32>(&store_full, "fibonacci_recursive")?;
    let expected = func_full.call(&mut store_full, 20)?;

    let mut store_budgeted = tinywasm::Store::default();
    let instance_budgeted = ModuleInstance::instantiate(&mut store_budgeted, &module, None)?;
    let func_budgeted = instance_budgeted.func::<i32, i32>(&store_budgeted, "fibonacci_recursive")?;

    let mut exec = func_budgeted.call_resumable(&mut store_budgeted, 20)?;
    let mut saw_suspended = false;
    let actual = loop {
        match exec.resume_with_fuel(64)? {
            ExecProgress::Completed(value) => break value,
            ExecProgress::Suspended => saw_suspended = true,
        }
    };

    assert!(saw_suspended, "expected at least one suspension for recursive fibonacci");
    assert_eq!(actual, expected);

    Ok(())
}

#[test]
fn untyped_resume_supports_zero_fuel() -> Result<()> {
    let module = tinywasm::parse_bytes(ADD_WASM)?;
    let mut store = tinywasm::Store::default();
    let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
    let func = instance.func_untyped(&store, "add")?;

    let mut exec = func.call_resumable(&mut store, &[WasmValue::I32(20), WasmValue::I32(22)])?;
    assert!(matches!(exec.resume_with_fuel(0)?, ExecProgress::Suspended));

    match exec.resume_with_fuel(16)? {
        ExecProgress::Completed(values) => {
            assert_eq!(values, vec![WasmValue::I32(42)])
        }
        ExecProgress::Suspended => panic!("expected completion"),
    }

    Ok(())
}

#[test]
fn weighted_call_fuel_requires_more_rounds() -> Result<()> {
    let module = tinywasm::parse_bytes(FIBONACCI_WASM)?;

    let mut per_instr_store = tinywasm::Store::default();
    let instance_per_instr = ModuleInstance::instantiate(&mut per_instr_store, &module, None)?;
    let func_per_instr = instance_per_instr.func::<i32, i32>(&per_instr_store, "fibonacci_recursive")?;

    let mut weighted_store =
        tinywasm::Store::new(tinywasm::Engine::new(Config::new().with_fuel_policy(FuelPolicy::Weighted)));
    let instance_weighted = ModuleInstance::instantiate(&mut weighted_store, &module, None)?;
    let func_weighted = instance_weighted.func::<i32, i32>(&weighted_store, "fibonacci_recursive")?;

    let fuel = 64;
    let n = 20;

    let mut per_exec = func_per_instr.call_resumable(&mut per_instr_store, n)?;
    let mut per_rounds = 0;
    let per_result = loop {
        per_rounds += 1;
        match per_exec.resume_with_fuel(fuel)? {
            ExecProgress::Completed(value) => break value,
            ExecProgress::Suspended => {}
        }
    };

    let mut weighted_exec = func_weighted.call_resumable(&mut weighted_store, n)?;
    let mut weighted_rounds = 0;
    let weighted_result = loop {
        weighted_rounds += 1;
        match weighted_exec.resume_with_fuel(fuel)? {
            ExecProgress::Completed(value) => break value,
            ExecProgress::Suspended => {}
        }
    };

    assert_eq!(weighted_result, per_result);
    assert!(weighted_rounds >= per_rounds, "weighted call fuel should not use fewer rounds than per-instruction");

    Ok(())
}

#[cfg(feature = "std")]
#[test]
fn time_budget_zero_suspends_then_completes() -> Result<()> {
    let module = tinywasm::parse_bytes(ADD_WASM)?;
    let mut store = tinywasm::Store::default();
    let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
    let func = instance.func::<(i32, i32), i32>(&store, "add")?;

    let mut exec = func.call_resumable(&mut store, (20, 22))?;
    assert!(matches!(exec.resume_with_time_budget(Duration::ZERO)?, ExecProgress::Suspended));

    match exec.resume_with_time_budget(Duration::from_millis(1))? {
        ExecProgress::Completed(value) => assert_eq!(value, 42),
        ExecProgress::Suspended => panic!("expected completion"),
    }

    Ok(())
}