Implement shared host functions. (#2625)

* Implement defining host functions at the Config level.

This commit introduces defining host functions at the `Config` rather than with
`Func` tied to a `Store`.

The intention here is to enable a host to define all of the functions once
with a `Config` and then use a `Linker` (or directly with
`Store::get_host_func`) to use the functions when instantiating a module.

This should help improve the performance of use cases where a `Store` is
short-lived and redefining the functions at every module instantiation is a
noticeable performance hit.

This commit adds `add_to_config` to the code generation for Wasmtime's `Wasi`
type.

The new method adds the WASI functions to the given config as host functions.

This commit adds context functions to `Store`: `get` to get a context of a
particular type and `set` to set the context on the store.

For safety, `set` cannot replace an existing context value of the same type.

`Wasi::set_context` was added to set the WASI context for a `Store` when using
`Wasi::add_to_config`.

* Add `Config::define_host_func_async`.

* Make config "async" rather than store.

This commit moves the concept of "async-ness" to `Config` rather than `Store`.

Note: this is a breaking API change for anyone that's already adopted the new
async support in Wasmtime.

Now `Config::new_async` is used to create an "async" config and any `Store`
associated with that config is inherently "async".

This is needed for async shared host functions to have some sanity check during their
execution (async host functions, like "async" `Func`, need to be called with
the "async" variants).

* Update async function tests to smoke async shared host functions.

This commit updates the async function tests to also smoke the shared host
functions, plus `Func::wrap0_async`.

This also changes the "wrap async" method names on `Config` to
`wrap$N_host_func_async` to slightly better match what is on `Func`.

* Move the instance allocator into `Engine`.

This commit moves the instantiated instance allocator from `Config` into
`Engine`.

This makes certain settings in `Config` no longer order-dependent, which is how
`Config` should ideally be.

This also removes the confusing concept of the "default" instance allocator,
instead opting to construct the on-demand instance allocator when needed.

This does alter the semantics of the instance allocator as now each `Engine`
gets its own instance allocator rather than sharing a single one between all
engines created from a configuration.

* Make `Engine::new` return `Result`.

This is a breaking API change for anyone using `Engine::new`.

As creating the pooling instance allocator may fail (likely cause is not enough
memory for the provided limits), instead of panicking when creating an
`Engine`, `Engine::new` now returns a `Result`.

* Remove `Config::new_async`.

This commit removes `Config::new_async` in favor of treating "async support" as
any other setting on `Config`.

The setting is `Config::async_support`.

* Remove order dependency when defining async host functions in `Config`.

This commit removes the order dependency where async support must be enabled on
the `Config` prior to defining async host functions.

The check is now delayed to when an `Engine` is created from the config.

* Update WASI example to use shared `Wasi::add_to_config`.

This commit updates the WASI example to use `Wasi::add_to_config`.

As only a single store and instance are used in the example, it has no semantic
difference from the previous example, but the intention is to steer users
towards defining WASI on the config and only using `Wasi::add_to_linker` when
more explicit scoping of the WASI context is required.
This commit is contained in:
Peter Huene
2021-03-11 08:14:03 -08:00
committed by GitHub
parent cc84c693a3
commit 54c07d8f16
45 changed files with 2123 additions and 809 deletions

View File

@@ -6,8 +6,26 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use wasmtime::*;
fn async_store() -> Store {
let engine = Engine::default();
Store::new_async(&engine)
Store::new(&Engine::new(Config::new().async_support(true)).unwrap())
}
fn run_smoke_test(func: &Func) {
run(func.call_async(&[])).unwrap();
run(func.call_async(&[])).unwrap();
let future1 = func.call_async(&[]);
let future2 = func.call_async(&[]);
run(future2).unwrap();
run(future1).unwrap();
}
fn run_smoke_get0_test(func: &Func) {
let func = func.get0_async::<()>().unwrap();
run(func()).unwrap();
run(func()).unwrap();
let future1 = func();
let future2 = func();
run(future2).unwrap();
run(future1).unwrap();
}
#[test]
@@ -19,12 +37,43 @@ fn smoke() {
(),
move |_caller, _state, _params, _results| Box::new(async { Ok(()) }),
);
run(func.call_async(&[])).unwrap();
run(func.call_async(&[])).unwrap();
let future1 = func.call_async(&[]);
let future2 = func.call_async(&[]);
run(future2).unwrap();
run(future1).unwrap();
run_smoke_test(&func);
run_smoke_get0_test(&func);
let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| {
Box::new(async { Ok(()) })
});
run_smoke_test(&func);
run_smoke_get0_test(&func);
}
#[test]
fn smoke_host_func() {
let mut config = Config::new();
config.async_support(true);
config.define_host_func_async(
"",
"first",
FuncType::new(None, None),
move |_caller, _params, _results| Box::new(async { Ok(()) }),
);
config.wrap0_host_func_async("", "second", move |_caller: Caller<'_>| {
Box::new(async { Ok(()) })
});
let store = Store::new(&Engine::new(&config).unwrap());
let func = store
.get_host_func("", "first")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
let func = store
.get_host_func("", "second")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
}
#[test]
@@ -41,35 +90,54 @@ fn smoke_with_suspension() {
})
},
);
run(func.call_async(&[])).unwrap();
run(func.call_async(&[])).unwrap();
let future1 = func.call_async(&[]);
let future2 = func.call_async(&[]);
run(future2).unwrap();
run(future1).unwrap();
run_smoke_test(&func);
run_smoke_get0_test(&func);
let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| {
Box::new(async {
PendingOnce::default().await;
Ok(())
})
});
run_smoke_test(&func);
run_smoke_get0_test(&func);
}
#[test]
fn smoke_get_with_suspension() {
let store = async_store();
let func = Func::new_async(
&store,
fn smoke_host_func_with_suspension() {
let mut config = Config::new();
config.async_support(true);
config.define_host_func_async(
"",
"first",
FuncType::new(None, None),
(),
move |_caller, _state, _params, _results| {
move |_caller, _params, _results| {
Box::new(async {
PendingOnce::default().await;
Ok(())
})
},
);
let func = func.get0_async::<()>().unwrap();
run(func()).unwrap();
run(func()).unwrap();
let future1 = func();
let future2 = func();
run(future2).unwrap();
run(future1).unwrap();
config.wrap0_host_func_async("", "second", move |_caller: Caller<'_>| {
Box::new(async {
PendingOnce::default().await;
Ok(())
})
});
let store = Store::new(&Engine::new(&config).unwrap());
let func = store
.get_host_func("", "first")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
let func = store
.get_host_func("", "second")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
}
#[test]
@@ -304,8 +372,8 @@ fn dummy_waker() -> Waker {
#[test]
fn iloop_with_fuel() {
let engine = Engine::new(Config::new().consume_fuel(true));
let store = Store::new_async(&engine);
let engine = Engine::new(Config::new().async_support(true).consume_fuel(true)).unwrap();
let store = Store::new(&engine);
store.out_of_fuel_async_yield(1_000, 10);
let module = Module::new(
&engine,
@@ -338,8 +406,8 @@ fn iloop_with_fuel() {
#[test]
fn fuel_eventually_finishes() {
let engine = Engine::new(Config::new().consume_fuel(true));
let store = Store::new_async(&engine);
let engine = Engine::new(Config::new().async_support(true).consume_fuel(true)).unwrap();
let store = Store::new(&engine);
store.out_of_fuel_async_yield(u32::max_value(), 10);
let module = Module::new(
&engine,
@@ -368,33 +436,60 @@ fn fuel_eventually_finishes() {
#[test]
fn async_with_pooling_stacks() {
let mut config = Config::new();
config
.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
table_elements: 0,
..Default::default()
},
instance_limits: InstanceLimits {
count: 1,
memory_reservation_size: 1,
},
})
.expect("pooling allocator created");
config.async_support(true);
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
table_elements: 0,
..Default::default()
},
instance_limits: InstanceLimits {
count: 1,
memory_reservation_size: 1,
},
});
let engine = Engine::new(&config);
let store = Store::new_async(&engine);
let engine = Engine::new(&config).unwrap();
let store = Store::new(&engine);
let func = Func::new_async(
&store,
FuncType::new(None, None),
(),
move |_caller, _state, _params, _results| Box::new(async { Ok(()) }),
);
run(func.call_async(&[])).unwrap();
run(func.call_async(&[])).unwrap();
let future1 = func.call_async(&[]);
let future2 = func.call_async(&[]);
run(future2).unwrap();
run(future1).unwrap();
run_smoke_test(&func);
run_smoke_get0_test(&func);
}
#[test]
fn async_host_func_with_pooling_stacks() {
let mut config = Config::new();
config.async_support(true);
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
table_elements: 0,
..Default::default()
},
instance_limits: InstanceLimits {
count: 1,
memory_reservation_size: 1,
},
});
config.define_host_func_async(
"",
"",
FuncType::new(None, None),
move |_caller, _params, _results| Box::new(async { Ok(()) }),
);
let store = Store::new(&Engine::new(&config).unwrap());
let func = store.get_host_func("", "").expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
}

View File

@@ -112,7 +112,7 @@ mod tests {
// hostcall can be handled.
#[test]
fn test_custom_signal_handler_single_instance_hostcall() -> Result<()> {
let engine = Engine::new(&Config::default());
let engine = Engine::new(&Config::default())?;
let store = Store::new(&engine);
let module = Module::new(&engine, WAT1)?;
@@ -132,7 +132,7 @@ mod tests {
#[test]
fn test_custom_signal_handler_single_instance() -> Result<()> {
let engine = Engine::new(&Config::default());
let engine = Engine::new(&Config::default())?;
let store = Store::new(&engine);
let module = Module::new(&engine, WAT1)?;
@@ -193,7 +193,7 @@ mod tests {
#[test]
fn test_custom_signal_handler_multiple_instances() -> Result<()> {
let engine = Engine::new(&Config::default());
let engine = Engine::new(&Config::default())?;
let store = Store::new(&engine);
let module = Module::new(&engine, WAT1)?;
@@ -286,7 +286,7 @@ mod tests {
#[test]
fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
let engine = Engine::new(&Config::default());
let engine = Engine::new(&Config::default())?;
let store = Store::new(&engine);
// instance1 which defines 'read'

View File

@@ -57,7 +57,7 @@ fn bad_tables() {
fn cross_store() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store1 = Store::new(&engine);
let store2 = Store::new(&engine);
@@ -127,7 +127,7 @@ fn cross_store() -> anyhow::Result<()> {
fn get_set_externref_globals_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
// Initialize with a null externref.
@@ -162,7 +162,7 @@ fn get_set_externref_globals_via_api() -> anyhow::Result<()> {
fn get_set_funcref_globals_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
let f = Func::wrap(&store, || {});
@@ -197,7 +197,7 @@ fn get_set_funcref_globals_via_api() -> anyhow::Result<()> {
fn create_get_set_funcref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
let table_ty = TableType::new(ValType::FuncRef, Limits::at_least(10));
@@ -218,7 +218,7 @@ fn create_get_set_funcref_tables_via_api() -> anyhow::Result<()> {
fn fill_funcref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
let table_ty = TableType::new(ValType::FuncRef, Limits::at_least(10));
@@ -244,7 +244,7 @@ fn fill_funcref_tables_via_api() -> anyhow::Result<()> {
fn grow_funcref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
let table_ty = TableType::new(ValType::FuncRef, Limits::at_least(10));
@@ -261,7 +261,7 @@ fn grow_funcref_tables_via_api() -> anyhow::Result<()> {
fn create_get_set_externref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
let table_ty = TableType::new(ValType::ExternRef, Limits::at_least(10));
@@ -292,7 +292,7 @@ fn create_get_set_externref_tables_via_api() -> anyhow::Result<()> {
fn fill_externref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
let table_ty = TableType::new(ValType::ExternRef, Limits::at_least(10));
@@ -328,7 +328,7 @@ fn fill_externref_tables_via_api() -> anyhow::Result<()> {
fn grow_externref_tables_via_api() -> anyhow::Result<()> {
let mut cfg = Config::new();
cfg.wasm_reference_types(true);
let engine = Engine::new(&cfg);
let engine = Engine::new(&cfg)?;
let store = Store::new(&engine);
let table_ty = TableType::new(ValType::ExternRef, Limits::at_least(10));
@@ -344,7 +344,7 @@ fn grow_externref_tables_via_api() -> anyhow::Result<()> {
#[test]
fn read_write_memory_via_api() {
let cfg = Config::new();
let store = Store::new(&Engine::new(&cfg));
let store = Store::new(&Engine::new(&cfg).unwrap());
let ty = MemoryType::new(Limits::new(1, None));
let mem = Memory::new(&store, ty);
mem.grow(1).unwrap();

View File

@@ -48,7 +48,7 @@ fn run() -> Result<()> {
fn fuel_consumed(wasm: &[u8]) -> u64 {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config);
let engine = Engine::new(&config).unwrap();
let module = Module::new(&engine, wasm).unwrap();
let store = Store::new(&engine);
store.add_fuel(u64::max_value()).unwrap();
@@ -110,7 +110,7 @@ fn iloop() {
fn iloop_aborts(wat: &str) {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config);
let engine = Engine::new(&config).unwrap();
let module = Module::new(&engine, wat).unwrap();
let store = Store::new(&engine);
store.add_fuel(10_000).unwrap();

View File

@@ -156,7 +156,7 @@ fn import_works() -> Result<()> {
)?;
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let module = Module::new(&engine, &wasm)?;
let instance = Instance::new(
@@ -459,7 +459,7 @@ fn return_cross_store_value() -> anyhow::Result<()> {
)?;
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;
let store1 = Store::new(&engine);
@@ -485,7 +485,7 @@ fn return_cross_store_value() -> anyhow::Result<()> {
fn pass_cross_store_arg() -> anyhow::Result<()> {
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let store1 = Store::new(&engine);
let store2 = Store::new(&engine);

638
tests/all/host_funcs.rs Normal file
View File

@@ -0,0 +1,638 @@
use anyhow::Result;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasi_cap_std_sync::WasiCtxBuilder;
use wasmtime::*;
use wasmtime_wasi::Wasi;
#[test]
fn async_required() {
let mut config = Config::default();
config.define_host_func_async(
"",
"",
FuncType::new(None, None),
move |_caller, _params, _results| Box::new(async { Ok(()) }),
);
assert_eq!(
Engine::new(&config)
.map_err(|e| e.to_string())
.err()
.unwrap(),
"an async host function cannot be defined without async support enabled in the config"
);
}
#[test]
fn wrap_func() {
let mut config = Config::default();
config.wrap_host_func("", "", || {});
config.wrap_host_func("m", "f", |_: i32| {});
config.wrap_host_func("m", "f2", |_: i32, _: i64| {});
config.wrap_host_func("m2", "f", |_: f32, _: f64| {});
config.wrap_host_func("m2", "f2", || -> i32 { 0 });
config.wrap_host_func("", "", || -> i64 { 0 });
config.wrap_host_func("m", "f", || -> f32 { 0.0 });
config.wrap_host_func("m2", "f", || -> f64 { 0.0 });
config.wrap_host_func("m3", "", || -> Option<ExternRef> { None });
config.wrap_host_func("m3", "f", || -> Option<Func> { None });
config.wrap_host_func("", "f1", || -> Result<(), Trap> { loop {} });
config.wrap_host_func("", "f2", || -> Result<i32, Trap> { loop {} });
config.wrap_host_func("", "f3", || -> Result<i64, Trap> { loop {} });
config.wrap_host_func("", "f4", || -> Result<f32, Trap> { loop {} });
config.wrap_host_func("", "f5", || -> Result<f64, Trap> { loop {} });
config.wrap_host_func("", "f6", || -> Result<Option<ExternRef>, Trap> { loop {} });
config.wrap_host_func("", "f7", || -> Result<Option<Func>, Trap> { loop {} });
}
#[test]
fn drop_func() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
struct A;
impl Drop for A {
fn drop(&mut self) {
HITS.fetch_add(1, SeqCst);
}
}
let mut config = Config::default();
let a = A;
config.wrap_host_func("", "", move || {
drop(&a);
});
assert_eq!(HITS.load(SeqCst), 0);
// Define the function again to ensure redefined functions are dropped
let a = A;
config.wrap_host_func("", "", move || {
drop(&a);
});
assert_eq!(HITS.load(SeqCst), 1);
drop(config);
assert_eq!(HITS.load(SeqCst), 2);
Ok(())
}
#[test]
fn drop_delayed() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
struct A;
impl Drop for A {
fn drop(&mut self) {
HITS.fetch_add(1, SeqCst);
}
}
let mut config = Config::default();
let a = A;
config.wrap_host_func("", "", move || drop(&a));
assert_eq!(HITS.load(SeqCst), 0);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wat::parse_str(r#"(import "" "" (func))"#)?)?;
let store = Store::new(&engine);
let instance = Instance::new(
&store,
&module,
&[store
.get_host_func("", "")
.expect("function should be defined")
.into()],
)?;
drop((instance, store));
assert_eq!(HITS.load(SeqCst), 0);
let store = Store::new(&engine);
let instance = Instance::new(
&store,
&module,
&[store
.get_host_func("", "")
.expect("function should be defined")
.into()],
)?;
drop((instance, store, engine, module));
assert_eq!(HITS.load(SeqCst), 0);
drop(config);
assert_eq!(HITS.load(SeqCst), 1);
Ok(())
}
#[test]
fn signatures_match() -> Result<()> {
let mut config = Config::default();
config.wrap_host_func("", "f1", || {});
config.wrap_host_func("", "f2", || -> i32 { loop {} });
config.wrap_host_func("", "f3", || -> i64 { loop {} });
config.wrap_host_func("", "f4", || -> f32 { loop {} });
config.wrap_host_func("", "f5", || -> f64 { loop {} });
config.wrap_host_func(
"",
"f6",
|_: f32, _: f64, _: i32, _: i64, _: i32, _: Option<ExternRef>, _: Option<Func>| -> f64 {
loop {}
},
);
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let f = store
.get_host_func("", "f1")
.expect("func should be defined");
assert_eq!(f.ty().params().collect::<Vec<_>>(), &[]);
assert_eq!(f.param_arity(), 0);
assert_eq!(f.ty().results().collect::<Vec<_>>(), &[]);
assert_eq!(f.result_arity(), 0);
let f = store
.get_host_func("", "f2")
.expect("func should be defined");
assert_eq!(f.ty().params().collect::<Vec<_>>(), &[]);
assert_eq!(f.ty().results().collect::<Vec<_>>(), &[ValType::I32]);
let f = store
.get_host_func("", "f3")
.expect("func should be defined");
assert_eq!(f.ty().params().collect::<Vec<_>>(), &[]);
assert_eq!(f.ty().results().collect::<Vec<_>>(), &[ValType::I64]);
let f = store
.get_host_func("", "f4")
.expect("func should be defined");
assert_eq!(f.ty().params().collect::<Vec<_>>(), &[]);
assert_eq!(f.ty().results().collect::<Vec<_>>(), &[ValType::F32]);
let f = store
.get_host_func("", "f5")
.expect("func should be defined");
assert_eq!(f.ty().params().collect::<Vec<_>>(), &[]);
assert_eq!(f.ty().results().collect::<Vec<_>>(), &[ValType::F64]);
let f = store
.get_host_func("", "f6")
.expect("func should be defined");
assert_eq!(
f.ty().params().collect::<Vec<_>>(),
&[
ValType::F32,
ValType::F64,
ValType::I32,
ValType::I64,
ValType::I32,
ValType::ExternRef,
ValType::FuncRef,
]
);
assert_eq!(f.ty().results().collect::<Vec<_>>(), &[ValType::F64]);
Ok(())
}
#[test]
// Note: Cranelift only supports refrerence types (used in the wasm in this
// test) on x64.
#[cfg(target_arch = "x86_64")]
fn import_works() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
let wasm = wat::parse_str(
r#"
(import "" "f1" (func))
(import "" "f2" (func (param i32) (result i32)))
(import "" "f3" (func (param i32) (param i64)))
(import "" "f4" (func (param i32 i64 i32 f32 f64 externref funcref)))
(func (export "run") (param externref funcref)
call 0
i32.const 0
call 1
i32.const 1
i32.add
i64.const 3
call 2
i32.const 100
i64.const 200
i32.const 300
f32.const 400
f64.const 500
local.get 0
local.get 1
call 3
)
"#,
)?;
let mut config = Config::new();
config.wasm_reference_types(true);
config.wrap_host_func("", "f1", || {
assert_eq!(HITS.fetch_add(1, SeqCst), 0);
});
config.wrap_host_func("", "f2", |x: i32| -> i32 {
assert_eq!(x, 0);
assert_eq!(HITS.fetch_add(1, SeqCst), 1);
1
});
config.wrap_host_func("", "f3", |x: i32, y: i64| {
assert_eq!(x, 2);
assert_eq!(y, 3);
assert_eq!(HITS.fetch_add(1, SeqCst), 2);
});
config.wrap_host_func(
"",
"f4",
|a: i32, b: i64, c: i32, d: f32, e: f64, f: Option<ExternRef>, g: Option<Func>| {
assert_eq!(a, 100);
assert_eq!(b, 200);
assert_eq!(c, 300);
assert_eq!(d, 400.0);
assert_eq!(e, 500.0);
assert_eq!(
f.as_ref().unwrap().data().downcast_ref::<String>().unwrap(),
"hello"
);
assert_eq!(g.as_ref().unwrap().call(&[]).unwrap()[0].unwrap_i32(), 42);
assert_eq!(HITS.fetch_add(1, SeqCst), 3);
},
);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;
let store = Store::new(&engine);
let instance = Instance::new(
&store,
&module,
&[
store
.get_host_func("", "f1")
.expect("should be defined")
.into(),
store
.get_host_func("", "f2")
.expect("should be defined")
.into(),
store
.get_host_func("", "f3")
.expect("should be defined")
.into(),
store
.get_host_func("", "f4")
.expect("should be defined")
.into(),
],
)?;
let run = instance.get_func("run").unwrap();
run.call(&[
Val::ExternRef(Some(ExternRef::new("hello".to_string()))),
Val::FuncRef(Some(Func::wrap(&store, || -> i32 { 42 }))),
])?;
assert_eq!(HITS.load(SeqCst), 4);
Ok(())
}
#[test]
fn trap_smoke() -> Result<()> {
let mut config = Config::default();
config.wrap_host_func("", "", || -> Result<(), Trap> { Err(Trap::new("test")) });
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let f = store.get_host_func("", "").expect("should be defined");
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert!(err.to_string().contains("test"));
assert!(err.i32_exit_status().is_none());
Ok(())
}
#[test]
fn trap_import() -> Result<()> {
let wasm = wat::parse_str(
r#"
(import "" "" (func))
(start 0)
"#,
)?;
let mut config = Config::default();
config.wrap_host_func("", "", || -> Result<(), Trap> { Err(Trap::new("foo")) });
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;
let store = Store::new(&engine);
let trap = Instance::new(
&store,
&module,
&[store.get_host_func("", "").expect("defined").into()],
)
.err()
.unwrap()
.downcast::<Trap>()?;
assert!(trap.to_string().contains("foo"));
Ok(())
}
#[test]
fn new_from_signature() -> Result<()> {
let mut config = Config::default();
let ty = FuncType::new(None, None);
config.define_host_func("", "f1", ty, |_, _, _| panic!());
let ty = FuncType::new(Some(ValType::I32), Some(ValType::F64));
config.define_host_func("", "f2", ty, |_, _, _| panic!());
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let f = store.get_host_func("", "f1").expect("func defined");
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
let f = store.get_host_func("", "f2").expect("func defined");
assert!(f.get0::<()>().is_err());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, f64>().is_ok());
Ok(())
}
#[test]
fn call_wrapped_func() -> Result<()> {
let mut config = Config::default();
config.wrap_host_func("", "f1", |a: i32, b: i64, c: f32, d: f64| {
assert_eq!(a, 1);
assert_eq!(b, 2);
assert_eq!(c, 3.0);
assert_eq!(d, 4.0);
});
config.wrap_host_func("", "f2", || 1i32);
config.wrap_host_func("", "f3", || 2i64);
config.wrap_host_func("", "f4", || 3.0f32);
config.wrap_host_func("", "f5", || 4.0f64);
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let f = store.get_host_func("", "f1").expect("func defined");
f.call(&[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()])?;
f.get4::<i32, i64, f32, f64, ()>()?(1, 2, 3.0, 4.0)?;
let f = store.get_host_func("", "f2").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 1);
assert_eq!(f.get0::<i32>()?()?, 1);
let f = store.get_host_func("", "f3").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2);
assert_eq!(f.get0::<i64>()?()?, 2);
let f = store.get_host_func("", "f4").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f32(), 3.0);
assert_eq!(f.get0::<f32>()?()?, 3.0);
let f = store.get_host_func("", "f5").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f64(), 4.0);
assert_eq!(f.get0::<f64>()?()?, 4.0);
Ok(())
}
#[test]
fn func_return_nothing() -> Result<()> {
let mut config = Config::default();
let ty = FuncType::new(None, Some(ValType::I32));
config.define_host_func("", "", ty, |_, _, _| Ok(()));
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let f = store.get_host_func("", "").expect("func defined");
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert!(err
.to_string()
.contains("function attempted to return an incompatible value"));
Ok(())
}
#[test]
fn call_via_funcref() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
struct A;
impl Drop for A {
fn drop(&mut self) {
HITS.fetch_add(1, SeqCst);
}
}
let wasm = wat::parse_str(
r#"
(table $t 1 funcref)
(type $add (func (param i32 i32) (result i32)))
(func (export "call") (param funcref) (result i32 funcref)
(table.set $t (i32.const 0) (local.get 0))
(call_indirect (type $add) (i32.const 3) (i32.const 4) (i32.const 0))
(local.get 0)
)
"#,
)?;
let mut config = Config::default();
let a = A;
config.wrap_host_func("", "", move |x: i32, y: i32| {
drop(&a);
x + y
});
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;
let store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?;
let results = instance
.get_func("call")
.unwrap()
.call(&[store.get_host_func("", "").expect("func defined").into()])?;
assert_eq!(results.len(), 2);
assert_eq!(results[0].unwrap_i32(), 7);
{
let f = results[1].unwrap_funcref().unwrap();
let results = f.call(&[1.into(), 2.into()])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 3);
}
assert_eq!(HITS.load(SeqCst), 0);
drop((results, instance, store, module, engine));
assert_eq!(HITS.load(SeqCst), 0);
drop(config);
assert_eq!(HITS.load(SeqCst), 1);
Ok(())
}
#[test]
fn store_with_context() -> Result<()> {
struct Ctx {
called: std::cell::Cell<bool>,
}
let mut config = Config::default();
config.wrap_host_func("", "", |caller: Caller| {
let ctx = caller
.store()
.get::<Ctx>()
.expect("store should have context");
ctx.called.set(true);
});
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
assert!(store.get::<Ctx>().is_none());
assert!(store
.set(Ctx {
called: std::cell::Cell::new(false)
})
.is_ok());
assert!(store
.set(Ctx {
called: std::cell::Cell::new(false)
})
.is_err());
assert!(!store.get::<Ctx>().unwrap().called.get());
let f = store.get_host_func("", "").expect("func defined");
f.call(&[])?;
assert!(store.get::<Ctx>().unwrap().called.get());
Ok(())
}
#[test]
fn wasi_imports_missing_context() -> Result<()> {
let mut config = Config::default();
Wasi::add_to_config(&mut config);
let wasm = wat::parse_str(
r#"
(import "wasi_snapshot_preview1" "proc_exit" (func $__wasi_proc_exit (param i32)))
(memory (export "memory") 0)
(func (export "_start")
(call $__wasi_proc_exit (i32.const 123))
)
"#,
)?;
let engine = Engine::new(&config)?;
let module = Module::new(&engine, wasm)?;
let store = Store::new(&engine);
let linker = Linker::new(&store);
let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap().get0::<()>()?;
let trap = start().unwrap_err();
assert!(trap.to_string().contains("context is missing in the store"));
assert!(trap.i32_exit_status().is_none());
Ok(())
}
#[test]
fn wasi_imports() -> Result<()> {
let mut config = Config::default();
Wasi::add_to_config(&mut config);
let wasm = wat::parse_str(
r#"
(import "wasi_snapshot_preview1" "proc_exit" (func $__wasi_proc_exit (param i32)))
(memory (export "memory") 0)
(func (export "_start")
(call $__wasi_proc_exit (i32.const 123))
)
"#,
)?;
let engine = Engine::new(&config)?;
let module = Module::new(&engine, wasm)?;
let store = Store::new(&engine);
assert!(Wasi::set_context(&store, WasiCtxBuilder::new().build()?).is_ok());
let linker = Linker::new(&store);
let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap().get0::<()>()?;
let trap = start().unwrap_err();
assert_eq!(trap.i32_exit_status(), Some(123));
Ok(())
}

View File

@@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasmtime::*;
fn interruptable_store() -> Store {
let engine = Engine::new(Config::new().interruptable(true));
let engine = Engine::new(Config::new().interruptable(true)).unwrap();
Store::new(&engine)
}

View File

@@ -223,3 +223,40 @@ fn no_leak_with_imports() -> Result<()> {
assert!(flag.get(), "store was leaked");
Ok(())
}
#[test]
fn get_host_function() -> Result<()> {
let mut config = Config::default();
config.wrap_host_func("mod", "f1", || {});
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module (import "mod" "f1" (func)))"#)?;
let store = Store::new(&engine);
let linker = Linker::new(&store);
assert!(linker.get(&module.imports().nth(0).unwrap()).is_some());
Ok(())
}
#[test]
fn shadowing_host_function() -> Result<()> {
let mut config = Config::default();
config.wrap_host_func("mod", "f1", || {});
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let mut linker = Linker::new(&store);
assert!(linker
.define("mod", "f1", Func::wrap(&store, || {}))
.is_err());
linker.define("mod", "f2", Func::wrap(&store, || {}))?;
let mut linker = Linker::new(&store);
linker.allow_shadowing(true);
linker.define("mod", "f1", Func::wrap(&store, || {}))?;
linker.define("mod", "f2", Func::wrap(&store, || {}))?;
Ok(())
}

View File

@@ -7,6 +7,7 @@ mod fuel;
mod func;
mod fuzzing;
mod globals;
mod host_funcs;
mod iloop;
mod import_calling_export;
mod import_indexes;
@@ -43,7 +44,7 @@ pub(crate) fn ref_types_module(
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let module = Module::new(&engine, source)?;

View File

@@ -136,7 +136,7 @@ mod not_for_windows {
.with_host_memory(mem_creator.clone())
.static_memory_maximum_size(0)
.dynamic_memory_guard_size(0);
(Store::new(&Engine::new(&config)), mem_creator)
(Store::new(&Engine::new(&config).unwrap()), mem_creator)
}
#[test]

View File

@@ -5,13 +5,13 @@ fn caches_across_engines() {
let mut c = Config::new();
c.cranelift_clear_cpu_flags();
let bytes = Module::new(&Engine::new(&c), "(module)")
let bytes = Module::new(&Engine::new(&c).unwrap(), "(module)")
.unwrap()
.serialize()
.unwrap();
let res = Module::deserialize(
&Engine::new(&Config::new().cranelift_clear_cpu_flags()),
&Engine::new(&Config::new().cranelift_clear_cpu_flags()).unwrap(),
&bytes,
);
assert!(res.is_ok());
@@ -22,7 +22,8 @@ fn caches_across_engines() {
&Config::new()
.cranelift_clear_cpu_flags()
.cranelift_nan_canonicalization(true),
),
)
.unwrap(),
&bytes,
);
assert!(res.is_err());
@@ -33,7 +34,8 @@ fn caches_across_engines() {
&Config::new()
.cranelift_clear_cpu_flags()
.cranelift_opt_level(OptLevel::None),
),
)
.unwrap(),
&bytes,
);
assert!(res.is_err());
@@ -46,7 +48,8 @@ fn caches_across_engines() {
.cranelift_clear_cpu_flags()
.cranelift_other_flag("has_sse3", "true")
.unwrap()
}),
})
.unwrap(),
&bytes,
);
assert!(res.is_err());

View File

@@ -4,7 +4,7 @@ use wasmtime::*;
fn engine() -> Engine {
let mut config = Config::new();
config.wasm_module_linking(true);
Engine::new(&config)
Engine::new(&config).unwrap()
}
#[test]
@@ -191,7 +191,7 @@ fn limit_instances() -> Result<()> {
let mut config = Config::new();
config.wasm_module_linking(true);
config.max_instances(10);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
@@ -232,7 +232,7 @@ fn limit_memories() -> Result<()> {
config.wasm_module_linking(true);
config.wasm_multi_memory(true);
config.max_memories(10);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"
@@ -267,7 +267,7 @@ fn limit_tables() -> Result<()> {
let mut config = Config::new();
config.wasm_module_linking(true);
config.max_tables(10);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
r#"

View File

@@ -39,7 +39,7 @@ fn test_module_serialize_fail() -> Result<()> {
let mut config = Config::new();
config.cranelift_opt_level(OptLevel::None);
let store = Store::new(&Engine::new(&config));
let store = Store::new(&Engine::new(&config)?);
match deserialize_and_instantiate(&store, &buffer) {
Ok(_) => bail!("expected failure at deserialization"),
Err(_) => (),

View File

@@ -4,7 +4,7 @@ use wasmtime::*;
#[test]
fn successful_instantiation() -> Result<()> {
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
@@ -15,9 +15,9 @@ fn successful_instantiation() -> Result<()> {
count: 1,
memory_reservation_size: 1,
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module (memory 1) (table 10 funcref))"#)?;
// Module should instantiate
@@ -30,7 +30,7 @@ fn successful_instantiation() -> Result<()> {
#[test]
fn memory_limit() -> Result<()> {
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 3,
@@ -41,9 +41,9 @@ fn memory_limit() -> Result<()> {
count: 1,
memory_reservation_size: 196608,
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
// Module should fail to validate because the minimum is greater than the configured limit
match Module::new(&engine, r#"(module (memory 4))"#) {
@@ -92,7 +92,7 @@ fn memory_limit() -> Result<()> {
#[test]
fn memory_init() -> Result<()> {
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 2,
@@ -103,9 +103,9 @@ fn memory_init() -> Result<()> {
count: 1,
..Default::default()
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
@@ -130,7 +130,7 @@ fn memory_init() -> Result<()> {
#[test]
fn memory_guard_page_trap() -> Result<()> {
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 2,
@@ -141,9 +141,9 @@ fn memory_guard_page_trap() -> Result<()> {
count: 1,
..Default::default()
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
@@ -185,7 +185,7 @@ fn memory_guard_page_trap() -> Result<()> {
#[cfg_attr(target_arch = "aarch64", ignore)] // https://github.com/bytecodealliance/wasmtime/pull/2518#issuecomment-747280133
fn memory_zeroed() -> Result<()> {
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
@@ -196,9 +196,9 @@ fn memory_zeroed() -> Result<()> {
count: 1,
memory_reservation_size: 1,
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module (memory (export "m") 1))"#)?;
@@ -228,7 +228,7 @@ fn memory_zeroed() -> Result<()> {
fn table_limit() -> Result<()> {
const TABLE_ELEMENTS: u32 = 10;
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
@@ -239,9 +239,9 @@ fn table_limit() -> Result<()> {
count: 1,
memory_reservation_size: 1,
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
// Module should fail to validate because the minimum is greater than the configured limit
match Module::new(&engine, r#"(module (table 31 funcref))"#) {
@@ -296,7 +296,7 @@ fn table_limit() -> Result<()> {
#[test]
fn table_init() -> Result<()> {
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 0,
@@ -307,9 +307,9 @@ fn table_init() -> Result<()> {
count: 1,
..Default::default()
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(
&engine,
@@ -346,7 +346,7 @@ fn table_init() -> Result<()> {
#[cfg_attr(target_arch = "aarch64", ignore)] // https://github.com/bytecodealliance/wasmtime/pull/2518#issuecomment-747280133
fn table_zeroed() -> Result<()> {
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
@@ -357,9 +357,9 @@ fn table_zeroed() -> Result<()> {
count: 1,
memory_reservation_size: 1,
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module (table (export "t") 10 funcref))"#)?;
@@ -388,7 +388,7 @@ fn table_zeroed() -> Result<()> {
fn instantiation_limit() -> Result<()> {
const INSTANCE_LIMIT: u32 = 10;
let mut config = Config::new();
config.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
config.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
memory_pages: 1,
@@ -399,9 +399,9 @@ fn instantiation_limit() -> Result<()> {
count: INSTANCE_LIMIT,
memory_reservation_size: 1,
},
})?;
});
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, r#"(module)"#)?;
// Instantiate to the limit

View File

@@ -526,7 +526,7 @@ fn parse_dwarf_info() -> Result<()> {
);
let mut config = Config::new();
config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let module = Module::new(&engine, &wasm)?;
let mut linker = Linker::new(&store);
@@ -561,7 +561,7 @@ fn parse_dwarf_info() -> Result<()> {
fn no_hint_even_with_dwarf_info() -> Result<()> {
let mut config = Config::new();
config.wasm_backtrace_details(WasmBacktraceDetails::Disable);
let engine = Engine::new(&config);
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
let module = Module::new(
&engine,

View File

@@ -52,7 +52,7 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()>
// However, these limits may become insufficient in the future as the wast tests change.
// If a wast test fails because of a limit being "exceeded" or if memory/table
// fails to grow, the values here will need to be adjusted.
cfg.with_allocation_strategy(InstanceAllocationStrategy::Pooling {
cfg.allocation_strategy(InstanceAllocationStrategy::Pooling {
strategy: PoolingAllocationStrategy::NextAvailable,
module_limits: ModuleLimits {
imported_memories: 2,
@@ -68,10 +68,10 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()>
count: 450,
..Default::default()
},
})?;
});
}
let store = Store::new(&Engine::new(&cfg));
let store = Store::new(&Engine::new(&cfg)?);
let mut wast_context = WastContext::new(store);
wast_context.register_spectest()?;
wast_context.run_file(wast)?;