use wasmtime::component::{Component, Instance, Linker, ResourceTable, Val};
use wasmtime::{Engine, Result, Store, ToWasmtimeResult as _, bail, error::Context as _};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView, p2};
use wasmtime_wizer::Wizer;
fn fail_wizening(msg: &str, wasm: &[u8]) -> Result<()> {
let _ = env_logger::try_init();
let wasm = wat::parse_bytes(wasm)?;
log::debug!(
"testing wizening failure for wasm:\n{}",
wasmprinter::print_bytes(&wasm).to_wasmtime_result()?
);
match Wizer::new().instrument_component(&wasm) {
Ok(_) => bail!("expected wizening to fail"),
Err(e) => {
let err = format!("{e}");
if !err.contains(msg) {
bail!("unexpected error: {err}");
}
Ok(())
}
}
}
#[test]
fn unsupported_constructs() -> Result<()> {
fail_wizening(
"does not currently support component imports",
br#"(component
(import "x" (component))
)"#,
)?;
fail_wizening(
"nested components with modules not currently supported",
br#"(component
(component (core module))
)"#,
)?;
fail_wizening(
"nested components with modules not currently supported",
br#"(component
(component)
(component (core module))
)"#,
)?;
fail_wizening(
"wizer does not currently support module imports",
br#"(component
(component (import "x" (core module)))
)"#,
)?;
fail_wizening(
"wizer does not currently support module aliases",
br#"(component
(core module $a)
(component
(core instance (instantiate $a))
)
)"#,
)?;
fail_wizening(
"wizer does not currently support component aliases",
br#"(component
(component $a)
(component
(instance (instantiate $a))
)
)"#,
)?;
fail_wizening(
"does not currently support component aliases",
br#"(component
(import "x" (instance $i (export "x" (component))))
(alias export $i "x" (component $c))
)"#,
)?;
fail_wizening(
"does not currently support module imports",
br#"(component
(import "x" (core module))
)"#,
)?;
fail_wizening(
"does not currently support module exports",
br#"(component
(core module $x)
(export "x" (core module $x))
)"#,
)?;
fail_wizening(
"does not currently support module aliases",
br#"(component
(import "x" (instance $i (export "x" (core module))))
(alias export $i "x" (core module $c))
)"#,
)?;
fail_wizening(
"does not currently support component start functions",
br#"(component
(import "f" (func $f))
(start $f)
)"#,
)?;
fail_wizening(
"modules may be instantiated at most once",
br#"(component
(core module $a)
(core instance $a1 (instantiate $a))
(core instance $a2 (instantiate $a))
)"#,
)?;
Ok(())
}
struct Ctx {
wasi: WasiCtx,
table: ResourceTable,
}
impl WasiView for Ctx {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView {
ctx: &mut self.wasi,
table: &mut self.table,
}
}
}
fn store() -> Result<Store<Ctx>> {
let engine = Engine::default();
Ok(Store::new(
&engine,
Ctx {
wasi: WasiCtxBuilder::new().build(),
table: ResourceTable::new(),
},
))
}
async fn instantiate(store: &mut Store<Ctx>, component: &Component) -> Result<Instance> {
let mut linker = Linker::new(store.engine());
p2::add_to_linker_async(&mut linker)?;
linker.root().func_new("x", |_, _, _, _| -> Result<()> {
wasmtime::bail!("trapped in wizer import of `x`")
})?;
linker.instantiate_async(store, component).await
}
async fn wizen(wasm: &[u8]) -> Result<Vec<u8>> {
let _ = env_logger::try_init();
log::debug!(
"=== PreWizened Wasm ==========================================================\n\
{}\n\
===========================================================================",
wasmprinter::print_bytes(&wasm).unwrap()
);
let mut store = store()?;
let wasm = Wizer::new()
.run_component(&mut store, &wasm, instantiate)
.await?;
log::debug!(
"=== Wizened Wasm ==========================================================\n\
{}\n\
===========================================================================",
wasmprinter::print_bytes(&wasm).unwrap()
);
if log::log_enabled!(log::Level::Debug) {
std::fs::write("test.wasm", &wasm).unwrap();
}
Ok(wasm)
}
async fn wizen_and_run_wat(expected: i32, wat: &str) -> Result<()> {
let wasm = wat::parse_str(wat)?;
let wasm = wizen(&wasm).await?;
run_wasm(0, expected, &wasm).await
}
async fn run_wasm(input: i32, expected: i32, wasm: &[u8]) -> Result<()> {
let mut store = store()?;
let module =
Component::new(store.engine(), wasm).context("Wasm test case failed to compile")?;
let mut linker = Linker::new(store.engine());
p2::add_to_linker_async(&mut linker)?;
linker.root().func_new("x", |_, _, _, _| -> Result<()> {
wasmtime::bail!("trapped in runtime import of `x`")
})?;
let instance = linker.instantiate_async(&mut store, &module).await?;
let run = instance.get_func(&mut store, "run").ok_or_else(|| {
wasmtime::format_err!("the test Wasm component does not export a `run` function")
})?;
let mut actual = [Val::U8(0)];
run.call_async(&mut store, &[Val::S32(input)], &mut actual)
.await?;
let actual = match actual[0] {
Val::S32(x) => x,
_ => wasmtime::bail!("expected an s32 result"),
};
wasmtime::ensure!(
expected == actual,
"expected `{expected}`, found `{actual}`",
);
Ok(())
}
#[tokio::test]
async fn simple() -> Result<()> {
wizen_and_run_wat(
42,
r#"(component
(core module $m
(func (export "init"))
(func (export "run") (param i32) (result i32)
i32.const 42
)
)
(core instance $i (instantiate $m))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn snapshot_global_i32() -> Result<()> {
wizen_and_run_wat(
1,
r#"(component
(core module $m
(global $g (mut i32) i32.const 0)
(func (export "init") (global.set $g (i32.const 1)))
(func (export "run") (param i32) (result i32) global.get $g)
)
(core instance $i (instantiate $m))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn snapshot_global_i64() -> Result<()> {
wizen_and_run_wat(
1,
r#"(component
(core module $m
(global $g (mut i64) i64.const 0)
(func (export "init") (global.set $g (i64.const 1)))
(func (export "run") (param i32) (result i32)
global.get $g
i32.wrap_i64
)
)
(core instance $i (instantiate $m))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn snapshot_global_f32() -> Result<()> {
wizen_and_run_wat(
1,
r#"(component
(core module $m
(global $g (mut f32) f32.const 0)
(func (export "init") (global.set $g (f32.const 1)))
(func (export "run") (param i32) (result i32)
global.get $g
i32.trunc_f32_s)
)
(core instance $i (instantiate $m))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn snapshot_global_f64() -> Result<()> {
wizen_and_run_wat(
1,
r#"(component
(core module $m
(global $g (mut f64) f64.const 0)
(func (export "init") (global.set $g (f64.const 1)))
(func (export "run") (param i32) (result i32)
global.get $g
i32.trunc_f64_s)
)
(core instance $i (instantiate $m))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[test]
fn v128_globals() -> Result<()> {
fail_wizening(
"component wizening does not support v128 globals",
br#"(component
(core module $a
(global (export "x") (mut v128) (v128.const i32x4 1 2 3 4))
)
(core instance (instantiate $a))
)"#,
)
}
#[tokio::test]
async fn snapshot_memory() -> Result<()> {
wizen_and_run_wat(
201,
r#"(component
(core module $m
(memory 1)
(func (export "init")
i32.const 200
i32.const 100
i32.store
i32.const 300
i32.const 101
i32.store
)
(func (export "run") (param i32) (result i32)
i32.const 200
i32.load
i32.const 300
i32.load
i32.add
)
)
(core instance $i (instantiate $m))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn nested_components() -> Result<()> {
wizen_and_run_wat(
42,
r#"(component
(component $a)
(instance (instantiate $a))
(instance (export "hi") (instantiate $a))
(component $b
(type $t string)
(import "x" (type (eq $t)))
(component $a)
(instance (instantiate $a))
(instance (export "hi") (instantiate $a))
)
(type $x string)
(instance (instantiate $b
(with "x" (type $x))
))
(instance (export "hi2") (instantiate $b
(with "x" (type $x))
))
(core module $m
(func (export "init"))
(func (export "run") (param i32) (result i32) i32.const 42)
)
(core instance $i (instantiate $m))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn multiple_modules() -> Result<()> {
wizen_and_run_wat(
100 + 101 + 200 + 201 + 7 + 112,
r#"(component
(core module $a
(memory 1)
(global $g (export "g") (mut i32) (i32.const 0))
(func (export "init")
i32.const 200
i32.const 100
i32.store
i32.const 300
i32.const 101
i32.store
)
(func (export "run") (param i32) (result i32)
i32.const 200
i32.load
i32.const 300
i32.load
i32.add
global.get $g
i32.add
)
)
(core instance $a (instantiate $a))
(core module $b
(import "a" "g" (global $g (mut i32)))
(import "a" "init" (func $init))
(import "a" "run" (func $run (param i32) (result i32)))
(memory (export "mem") 1)
(func (export "init")
call $init
i32.const 400
i32.const 200
i32.store
i32.const 500
i32.const 201
i32.store
i32.const 111
global.set $g
)
(func (export "run") (param i32) (result i32)
i32.const 400
i32.load
i32.const 500
i32.load
i32.add
i32.const 0
call $run
i32.add
)
)
(core instance $b (instantiate $b (with "a" (instance $a))))
(core module $c
(import "a" "g" (global $g (mut i32)))
(import "b" "init" (func $init))
(import "b" "run" (func $run (param i32) (result i32)))
(import "b" "mem" (memory 1))
(func (export "init")
call $init
i32.const 1
memory.grow
i32.const -1
i32.eq
if unreachable end
i32.const 65536
i32.const 7
i32.store
;; overwrite a#init with a different value, make sure this
;; one is snapshot
i32.const 112
global.set $g
)
(func (export "run") (param i32) (result i32)
i32.const 65536
i32.load
i32.const 0
call $run
i32.add
)
)
(core instance $c (instantiate $c
(with "a" (instance $a))
(with "b" (instance $b))
))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $c "run")))
(func (export "wizer-initialize") (canon lift (core func $c "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn export_is_removed() -> Result<()> {
let wasm = wizen(&wat::parse_str(
r#"(component
(core module $a
(func (export "init"))
)
(core instance $a (instantiate $a))
(func $a (canon lift (core func $a "init")))
(export "wizer-initialize" (func $a))
)"#,
)?)
.await?;
let names = exports(&wasm);
assert!(names.is_empty());
let wasm = wizen(&wat::parse_str(
r#"(component
(core module $a
(func (export "init"))
)
(core instance $a (instantiate $a))
(func $a (canon lift (core func $a "init")))
(export "other" (func $a))
(export "wizer-initialize" (func $a))
)"#,
)?)
.await?;
let names = exports(&wasm);
assert_eq!(names, ["other"]);
let wasm = wizen(&wat::parse_str(
r#"(component
(core module $a
(func (export "init"))
)
(core instance $a (instantiate $a))
(func $a (canon lift (core func $a "init")))
(export "other1" (func $a))
(export "wizer-initialize" (func $a))
(export "other2" (func $a))
)"#,
)?)
.await?;
let names = exports(&wasm);
assert_eq!(names, ["other1", "other2"]);
let wasm = wizen(&wat::parse_str(
r#"(component
(core module $a
(func (export "init"))
)
(core instance $a (instantiate $a))
(func $a (canon lift (core func $a "init")))
(export "other1" (func $a))
(export "other2" (func $a))
(export "wizer-initialize" (func $a))
)"#,
)?)
.await?;
let names = exports(&wasm);
assert_eq!(names, ["other1", "other2"]);
let wasm = wizen(&wat::parse_str(
r#"(component
(core module $a
(func (export "init"))
)
(core instance $a (instantiate $a))
(func $a (canon lift (core func $a "init")))
(export "wizer-initialize" (func $a))
(export "other1" (func $a))
(export "other2" (func $a))
)"#,
)?)
.await?;
let names = exports(&wasm);
assert_eq!(names, ["other1", "other2"]);
let wasm = wizen(&wat::parse_str(
r#"(component
(core module $a
(func (export "init"))
)
(core instance $a (instantiate $a))
(func $a (canon lift (core func $a "init")))
(export $x "other1" (func $a))
(export "wizer-initialize" (func $a))
(export "other2" (func $x))
)"#,
)?)
.await?;
let names = exports(&wasm);
assert_eq!(names, ["other1", "other2"]);
let wasm = wizen(&wat::parse_str(
r#"(component
(import "x" (func))
(core module $a
(func (export "init"))
)
(core instance $a (instantiate $a))
(func $a (canon lift (core func $a "init")))
(export $x "other1" (func $a))
(export "wizer-initialize" (func $a))
(export "other2" (func $x))
)"#,
)?)
.await?;
let names = exports(&wasm);
assert_eq!(names, ["other1", "other2"]);
return Ok(());
fn exports(wasm: &[u8]) -> Vec<&str> {
wasmparser::Parser::new(0)
.parse_all(&wasm)
.filter_map(|r| r.ok())
.filter_map(|payload| match payload {
wasmparser::Payload::ComponentExportSection(s) => Some(s),
_ => None,
})
.flat_map(|section| section.into_iter().map(|e| e.unwrap().name.0))
.collect()
}
}
#[tokio::test]
async fn leave_wasip1_initialize() -> Result<()> {
wizen_and_run_wat(
42,
r#"(component
(core module $m
(func (export "init"))
(func (export "run") (param i32) (result i32)
i32.const 42
)
(func (export "_initialize"))
)
(core instance $i (instantiate $m))
(alias core export $i "_initialize" (core func $initialize))
(core module $shim
(import "" "_initialize" (func $initialize))
(start $initialize)
)
(core instance $shim (instantiate $shim (with "" (instance
(export "_initialize" (func $initialize))
))))
(func (export "run") (param "arg" s32) (result s32) (canon lift (core func $i "run")))
(func (export "wizer-initialize") (canon lift (core func $i "init")))
)"#,
)
.await?;
Ok(())
}
#[tokio::test]
async fn rust_regex() -> Result<()> {
let _ = env_logger::try_init();
let component = test_programs_artifacts::wizer_regex_component_bytes!();
let mut store = store()?;
let wizened_component = Wizer::new()
.keep_init_func(true)
.run_component(&mut store, component, instantiate)
.await
.context("Wizer::run_component")?;
run_wasm(1, 42, &wizened_component)
.await
.context("run_wasm")?;
Ok(())
}