tinywasm 0.11.0

A tiny WebAssembly interpreter
Documentation
use tinywasm::types::{RefValue, WasmValue};
use tinywasm::{Engine, ExecProgress, ModuleInstance, Store, engine::Config};

const MODULE: &str = r#"
    (module
      (type $node (struct (field i32)))
      (type $bytes (array (mut i8)))
      (func (export "new") (result anyref)
        (struct.new $node (i32.const 42)))
      (func (export "new-extern") (result externref)
        (extern.convert_any (struct.new $node (i32.const 42))))
      (func (export "read") (param anyref) (result i32)
        (struct.get $node 0 (ref.cast (ref $node) (local.get 0))))
      (func (export "read-extern") (param externref) (result i32)
        (struct.get $node 0
          (ref.cast (ref $node) (any.convert_extern (local.get 0)))))
      (func (export "churn")
        (local $i i32)
        (loop $loop
          (drop (array.new_default $bytes (i32.const 16)))
          (local.set $i (i32.add (local.get $i) (i32.const 1)))
          (br_if $loop (i32.lt_u (local.get $i) (i32.const 100)))))
    )
"#;

fn store() -> Store {
    Store::new(Engine::new(Config::new().with_gc_collection_threshold(1)))
}

#[test]
fn host_result_is_rooted_and_rejected_by_another_store() {
    let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap();
    let mut first_store = store();
    let first = ModuleInstance::instantiate(&mut first_store, &module, None).unwrap();
    let new = first.func_untyped(&first_store, "new").unwrap();
    let read = first.func_untyped(&first_store, "read").unwrap();
    let churn = first.func_untyped(&first_store, "churn").unwrap();

    let mut results = [WasmValue::Ref(RefValue::Null)];
    new.call(&mut first_store, &[], &mut results).unwrap();
    let [value] = results;
    assert!(matches!(value, WasmValue::Ref(RefValue::Any(_))));
    churn.call(&mut first_store, &[], &mut []).unwrap();
    let mut results = [WasmValue::I32(0)];
    read.call(&mut first_store, std::slice::from_ref(&value), &mut results).unwrap();
    assert_eq!(results, [WasmValue::I32(42)]);

    let mut second_store = store();
    let second = ModuleInstance::instantiate(&mut second_store, &module, None).unwrap();
    let other_read = second.func_untyped(&second_store, "read").unwrap();
    assert!(other_read.call(&mut second_store, &[value], &mut [WasmValue::I32(0)]).is_err());
}

#[test]
fn externalized_gc_result_is_rooted() {
    let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap();
    let mut store = store();
    let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap();
    let new = instance.func_untyped(&store, "new-extern").unwrap();
    let read = instance.func_untyped(&store, "read-extern").unwrap();
    let churn = instance.func_untyped(&store, "churn").unwrap();

    let mut results = [WasmValue::Ref(RefValue::Null)];
    new.call(&mut store, &[], &mut results).unwrap();
    let [value] = results;
    assert!(matches!(value, WasmValue::Ref(RefValue::Extern(_))));
    churn.call(&mut store, &[], &mut []).unwrap();
    let mut results = [WasmValue::I32(0)];
    read.call(&mut store, &[value], &mut results).unwrap();
    assert_eq!(results, [WasmValue::I32(42)]);
}

#[test]
fn host_externref_does_not_alias_a_gc_object() {
    let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap();
    let mut store = store();
    let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap();
    let new = instance.func_untyped(&store, "new").unwrap();
    let read = instance.func_untyped(&store, "read-extern").unwrap();

    new.call(&mut store, &[], &mut [WasmValue::Ref(RefValue::Null)]).unwrap();
    let host_ref = tinywasm::ExternRef::try_new(&mut store, 0).unwrap().into();
    assert!(read.call(&mut store, &[host_ref], &mut [WasmValue::I32(0)]).is_err());
}

#[test]
fn resumable_gc_result_is_rooted() {
    let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap();
    let mut store = store();
    let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap();
    let new = instance.func_untyped(&store, "new").unwrap();
    let read = instance.func_untyped(&store, "read").unwrap();
    let churn = instance.func_untyped(&store, "churn").unwrap();

    let mut results = [WasmValue::Ref(RefValue::Null)];
    {
        let mut execution = new.call_resumable(&mut store, &[], &mut results).unwrap();
        match execution.resume_with_fuel(1000).unwrap() {
            ExecProgress::Completed(()) => {}
            ExecProgress::Suspended => panic!("constructor unexpectedly suspended"),
        }
    }
    let [value] = results;

    churn.call(&mut store, &[], &mut []).unwrap();
    let mut results = [WasmValue::I32(0)];
    read.call(&mut store, &[value], &mut results).unwrap();
    assert_eq!(results, [WasmValue::I32(42)]);
}

#[test]
fn element_initializers_root_previous_gc_values() {
    let module = tinywasm::parse_bytes(
        &wat::parse_str(
            r#"
                (module
                  (type $node (struct (field i32)))
                  (table 2 (ref null $node))
                  (elem (i32.const 0) (ref $node)
                    (struct.new $node (i32.const 1))
                    (struct.new $node (i32.const 2)))
                  (func (export "first") (result i32)
                    (struct.get $node 0 (table.get 0 (i32.const 0)))))
            "#,
        )
        .unwrap(),
    )
    .unwrap();
    let mut store = store();
    let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap();

    let mut results = [WasmValue::I32(0)];
    instance.func_untyped(&store, "first").unwrap().call(&mut store, &[], &mut results).unwrap();
    assert_eq!(results, [WasmValue::I32(1)]);
}