tinywasm 0.9.0

A tiny WebAssembly interpreter
Documentation
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use eyre::Result;
use tinywasm::engine::{Config, FuelPolicy};
use tinywasm::types::Module;
use tinywasm::{Engine, ExecProgress, FuncContext, FunctionTyped, HostFunction, Imports, ModuleInstance, Store};

const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm");
const FUEL_PER_ROUND: u32 = 512;
const TIME_BUDGET_PER_ROUND: core::time::Duration = core::time::Duration::from_micros(50);
const BENCH_MEASUREMENT_TIME: core::time::Duration = core::time::Duration::from_secs(10);

fn tinywasm_parse() -> Result<Module> {
    let parser = tinywasm_parser::Parser::new();
    Ok(parser.parse_module_bytes(WASM)?)
}

fn setup_typed_func(module: &Module, engine: Option<Engine>) -> Result<(Store, FunctionTyped<(), ()>)> {
    let mut store = match engine {
        Some(engine) => Store::new(engine),
        None => Store::default(),
    };

    let mut imports = Imports::default();
    imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _: i32| Ok(())));

    let instance = ModuleInstance::instantiate(&mut store, module, Some(imports))?;
    let func = instance.func::<(), ()>(&store, "hello")?;
    Ok((store, func))
}

fn run_call(store: &mut Store, func: &FunctionTyped<(), ()>) -> Result<()> {
    func.call(store, ())?;
    Ok(())
}

fn run_resume_with_fuel(store: &mut Store, func: &FunctionTyped<(), ()>) -> Result<()> {
    let mut execution = func.call_resumable(store, ())?;
    loop {
        match execution.resume_with_fuel(FUEL_PER_ROUND)? {
            ExecProgress::Completed(_) => return Ok(()),
            ExecProgress::Suspended => {}
        }
    }
}

fn run_resume_with_time_budget(store: &mut Store, func: &FunctionTyped<(), ()>) -> Result<()> {
    let mut execution = func.call_resumable(store, ())?;
    loop {
        match execution.resume_with_time_budget(TIME_BUDGET_PER_ROUND)? {
            ExecProgress::Completed(_) => return Ok(()),
            ExecProgress::Suspended => {}
        }
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    let module = tinywasm_parse().expect("tinywasm_parse");
    let mut group = c.benchmark_group("tinywasm_modes");
    group.measurement_time(BENCH_MEASUREMENT_TIME);

    let per_instruction_engine = Engine::new(Config::new().with_fuel_policy(FuelPolicy::PerInstruction));
    group.bench_function("resume_fuel_per_instruction", |b| {
        b.iter_batched_ref(
            || setup_typed_func(&module, Some(per_instruction_engine.clone())).expect("setup fuel per-instruction"),
            |(store, func)| run_resume_with_fuel(store, func).expect("run fuel per-instruction"),
            BatchSize::LargeInput,
        )
    });

    let weighted_engine = Engine::new(Config::new().with_fuel_policy(FuelPolicy::Weighted));
    group.bench_function("resume_fuel_weighted", |b| {
        b.iter_batched_ref(
            || setup_typed_func(&module, Some(weighted_engine.clone())).expect("setup fuel weighted"),
            |(store, func)| run_resume_with_fuel(store, func).expect("run fuel weighted"),
            BatchSize::LargeInput,
        )
    });

    group.bench_function("resume_time_budget", |b| {
        b.iter_batched_ref(
            || setup_typed_func(&module, None).expect("setup time budget"),
            |(store, func)| run_resume_with_time_budget(store, func).expect("run time budget"),
            BatchSize::LargeInput,
        )
    });

    group.bench_function("call", |b| {
        b.iter_batched_ref(
            || setup_typed_func(&module, None).expect("setup call"),
            |(store, func)| run_call(store, func).expect("run call"),
            BatchSize::LargeInput,
        )
    });

    group.finish();
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);