Files
wasmtime/tests/all/func.rs
Dan Gohman 9364eb1d98 Refactor (#1524)
* Compute instance exports on demand.

Instead having instances eagerly compute a Vec of Externs, and bumping
the refcount for each Extern, compute Externs on demand.

This also enables `Instance::get_export` to avoid doing a linear search.

This also means that the closure returned by `get0` and friends now
holds an `InstanceHandle` to dynamically hold the instance live rather
than being scoped to a lifetime.

* Compute module imports and exports on demand too.

And compute Extern::ty on demand too.

* Add a utility function for computing an ExternType.

* Add a utility function for looking up a function's signature.

* Add a utility function for computing the ValType of a Global.

* Rename wasmtime_environ::Export to EntityIndex.

This helps differentiate it from other Export types in the tree, and
describes what it is.

* Fix a typo in a comment.

* Simplify module imports and exports.

* Make `Instance::exports` return the export names.

This significantly simplifies the public API, as it's relatively common
to need the names, and this avoids the need to do a zip with
`Module::exports`.

This also changes `ImportType` and `ExportType` to have public members
instead of private members and accessors, as I find that simplifies the
usage particularly in cases where there are temporary instances.

* Remove `Instance::module`.

This doesn't quite remove `Instance`'s `module` member, it gets a step
closer.

* Use a InstanceHandle utility function.

* Don't consume self in the `Func::get*` methods.

Instead, just create a closure containing the instance handle and the
export for them to call.

* Use `ExactSizeIterator` to avoid needing separate `num_*` methods.

* Rename `Extern::func()` etc. to `into_func()` etc.

* Revise examples to avoid using `nth`.

* Add convenience methods to instance for getting specific extern types.

* Use the convenience functions in more tests and examples.

* Avoid cloning strings for `ImportType` and `ExportType`.

* Remove more obviated clone() calls.

* Simplify `Func`'s closure state.

* Make wasmtime::Export's fields private.

This makes them more consistent with ExportType.

* Fix compilation error.

* Make a lifetime parameter explicit, and use better lifetime names.

Instead of 'me, use 'instance and 'module to make it clear what the
lifetime is.

* More lifetime cleanups.
2020-04-20 15:55:33 -05:00

402 lines
11 KiB
Rust

use anyhow::Result;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasmtime::*;
#[test]
fn func_constructors() {
let store = Store::default();
Func::wrap(&store, || {});
Func::wrap(&store, |_: i32| {});
Func::wrap(&store, |_: i32, _: i64| {});
Func::wrap(&store, |_: f32, _: f64| {});
Func::wrap(&store, || -> i32 { 0 });
Func::wrap(&store, || -> i64 { 0 });
Func::wrap(&store, || -> f32 { 0.0 });
Func::wrap(&store, || -> f64 { 0.0 });
Func::wrap(&store, || -> Result<(), Trap> { loop {} });
Func::wrap(&store, || -> Result<i32, Trap> { loop {} });
Func::wrap(&store, || -> Result<i64, Trap> { loop {} });
Func::wrap(&store, || -> Result<f32, Trap> { loop {} });
Func::wrap(&store, || -> Result<f64, Trap> { loop {} });
}
#[test]
fn dtor_runs() {
static HITS: AtomicUsize = AtomicUsize::new(0);
struct A;
impl Drop for A {
fn drop(&mut self) {
HITS.fetch_add(1, SeqCst);
}
}
let store = Store::default();
let a = A;
assert_eq!(HITS.load(SeqCst), 0);
Func::wrap(&store, move || {
drop(&a);
});
assert_eq!(HITS.load(SeqCst), 1);
}
#[test]
fn dtor_delayed() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
struct A;
impl Drop for A {
fn drop(&mut self) {
HITS.fetch_add(1, SeqCst);
}
}
let store = Store::default();
let a = A;
let func = Func::wrap(&store, move || drop(&a));
assert_eq!(HITS.load(SeqCst), 0);
let wasm = wat::parse_str(r#"(import "" "" (func))"#)?;
let module = Module::new(&store, &wasm)?;
let instance = Instance::new(&module, &[func.into()])?;
assert_eq!(HITS.load(SeqCst), 0);
drop(instance);
assert_eq!(HITS.load(SeqCst), 1);
Ok(())
}
#[test]
fn signatures_match() {
let store = Store::default();
let f = Func::wrap(&store, || {});
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[]);
let f = Func::wrap(&store, || -> i32 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::I32]);
let f = Func::wrap(&store, || -> i64 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::I64]);
let f = Func::wrap(&store, || -> f32 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::F32]);
let f = Func::wrap(&store, || -> f64 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::F64]);
let f = Func::wrap(&store, |_: f32, _: f64, _: i32, _: i64, _: i32| -> f64 {
loop {}
});
assert_eq!(
f.ty().params(),
&[
ValType::F32,
ValType::F64,
ValType::I32,
ValType::I64,
ValType::I32
]
);
assert_eq!(f.ty().results(), &[ValType::F64]);
}
#[test]
fn import_works() -> Result<()> {
static HITS: AtomicUsize = AtomicUsize::new(0);
let wasm = wat::parse_str(
r#"
(import "" "" (func))
(import "" "" (func (param i32) (result i32)))
(import "" "" (func (param i32) (param i64)))
(import "" "" (func (param i32 i64 i32 f32 f64)))
(func $foo
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
call 3
)
(start $foo)
"#,
)?;
let store = Store::default();
let module = Module::new(&store, &wasm)?;
Instance::new(
&module,
&[
Func::wrap(&store, || {
assert_eq!(HITS.fetch_add(1, SeqCst), 0);
})
.into(),
Func::wrap(&store, |x: i32| -> i32 {
assert_eq!(x, 0);
assert_eq!(HITS.fetch_add(1, SeqCst), 1);
1
})
.into(),
Func::wrap(&store, |x: i32, y: i64| {
assert_eq!(x, 2);
assert_eq!(y, 3);
assert_eq!(HITS.fetch_add(1, SeqCst), 2);
})
.into(),
Func::wrap(&store, |a: i32, b: i64, c: i32, d: f32, e: f64| {
assert_eq!(a, 100);
assert_eq!(b, 200);
assert_eq!(c, 300);
assert_eq!(d, 400.0);
assert_eq!(e, 500.0);
assert_eq!(HITS.fetch_add(1, SeqCst), 3);
})
.into(),
],
)?;
Ok(())
}
#[test]
fn trap_smoke() -> Result<()> {
let store = Store::default();
let f = Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("test")) });
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert_eq!(err.message(), "test");
Ok(())
}
#[test]
fn trap_import() -> Result<()> {
let wasm = wat::parse_str(
r#"
(import "" "" (func))
(start 0)
"#,
)?;
let store = Store::default();
let module = Module::new(&store, &wasm)?;
let trap = Instance::new(
&module,
&[Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("foo")) }).into()],
)
.err()
.unwrap()
.downcast::<Trap>()?;
assert_eq!(trap.message(), "foo");
Ok(())
}
#[test]
fn get_from_wrapper() {
let store = Store::default();
let f = Func::wrap(&store, || {});
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<(), ()>().is_ok());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, i32>().is_err());
assert!(f.get2::<(), (), ()>().is_ok());
assert!(f.get2::<i32, i32, ()>().is_err());
assert!(f.get2::<i32, i32, i32>().is_err());
let f = Func::wrap(&store, || -> i32 { loop {} });
assert!(f.get0::<i32>().is_ok());
let f = Func::wrap(&store, || -> f32 { loop {} });
assert!(f.get0::<f32>().is_ok());
let f = Func::wrap(&store, || -> f64 { loop {} });
assert!(f.get0::<f64>().is_ok());
let f = Func::wrap(&store, |_: i32| {});
assert!(f.get1::<i32, ()>().is_ok());
assert!(f.get1::<i64, ()>().is_err());
assert!(f.get1::<f32, ()>().is_err());
assert!(f.get1::<f64, ()>().is_err());
let f = Func::wrap(&store, |_: i64| {});
assert!(f.get1::<i64, ()>().is_ok());
let f = Func::wrap(&store, |_: f32| {});
assert!(f.get1::<f32, ()>().is_ok());
let f = Func::wrap(&store, |_: f64| {});
assert!(f.get1::<f64, ()>().is_ok());
}
#[test]
fn get_from_signature() {
let store = Store::default();
let ty = FuncType::new(Box::new([]), Box::new([]));
let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
let ty = FuncType::new(Box::new([ValType::I32]), Box::new([ValType::F64]));
let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_err());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, f64>().is_ok());
}
#[test]
fn get_from_module() -> anyhow::Result<()> {
let store = Store::default();
let module = Module::new(
&store,
r#"
(module
(func (export "f0"))
(func (export "f1") (param i32))
(func (export "f2") (result i32)
i32.const 0)
)
"#,
)?;
let instance = Instance::new(&module, &[])?;
let f0 = instance.get_func("f0").unwrap();
assert!(f0.get0::<()>().is_ok());
assert!(f0.get0::<i32>().is_err());
let f1 = instance.get_func("f1").unwrap();
assert!(f1.get0::<()>().is_err());
assert!(f1.get1::<i32, ()>().is_ok());
assert!(f1.get1::<i32, f32>().is_err());
let f2 = instance.get_func("f2").unwrap();
assert!(f2.get0::<()>().is_err());
assert!(f2.get0::<i32>().is_ok());
assert!(f2.get1::<i32, ()>().is_err());
assert!(f2.get1::<i32, f32>().is_err());
Ok(())
}
#[test]
fn call_wrapped_func() -> Result<()> {
let store = Store::default();
let f = Func::wrap(&store, |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);
});
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 = Func::wrap(&store, || 1i32);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 1);
assert_eq!(f.get0::<i32>()?()?, 1);
let f = Func::wrap(&store, || 2i64);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2);
assert_eq!(f.get0::<i64>()?()?, 2);
let f = Func::wrap(&store, || 3.0f32);
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 = Func::wrap(&store, || 4.0f64);
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 caller_memory() -> anyhow::Result<()> {
let store = Store::default();
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("x").is_none());
assert!(c.get_export("y").is_none());
assert!(c.get_export("z").is_none());
});
f.call(&[])?;
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("x").is_none());
});
let module = Module::new(
&store,
r#"
(module
(import "" "" (func $f))
(start $f)
)
"#,
)?;
Instance::new(&module, &[f.into()])?;
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("memory").is_some());
});
let module = Module::new(
&store,
r#"
(module
(import "" "" (func $f))
(memory (export "memory") 1)
(start $f)
)
"#,
)?;
Instance::new(&module, &[f.into()])?;
let f = Func::wrap(&store, |c: Caller<'_>| {
assert!(c.get_export("m").is_some());
assert!(c.get_export("f").is_none());
assert!(c.get_export("g").is_none());
assert!(c.get_export("t").is_none());
});
let module = Module::new(
&store,
r#"
(module
(import "" "" (func $f))
(memory (export "m") 1)
(func (export "f"))
(global (export "g") i32 (i32.const 0))
(table (export "t") 1 funcref)
(start $f)
)
"#,
)?;
Instance::new(&module, &[f.into()])?;
Ok(())
}
#[test]
fn func_write_nothing() -> anyhow::Result<()> {
let store = Store::default();
let ty = FuncType::new(Box::new([]), Box::new([ValType::I32]));
let f = Func::new(&store, ty, |_, _, _| Ok(()));
let err = f.call(&[]).unwrap_err().downcast::<Trap>()?;
assert_eq!(
err.message(),
"function attempted to return an incompatible value"
);
Ok(())
}