Skip to content

Commit

Permalink
Add a first-class way of accessing caller's exports
Browse files Browse the repository at this point in the history
This commit is a continuation of #1237 and updates the API of `Func` to
allow defining host functions which have easy access to a caller's
memory in particular. The new APIs look like so:

* The `Func::wrap*` family of functions was condensed into one
  `Func::wrap` function.
* The ABI layer of conversions in `WasmTy` were removed
* An optional `Caller<'_>` argument can be at the front of all
  host-defined functions now.

The old way the wasi bindings looked up memory has been removed and is
now replaced with the `Caller` type. The `Caller` type has a
`get_export` method on it which allows looking up a caller's export by
name, allowing you to get access to the caller's memory easily, and even
during instantiation.
  • Loading branch information
alexcrichton committed Mar 12, 2020
1 parent 6e55c54 commit bb021dd
Show file tree
Hide file tree
Showing 10 changed files with 503 additions and 465 deletions.
724 changes: 376 additions & 348 deletions crates/api/src/func.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ mod values;
pub use crate::callable::Callable;
pub use crate::externals::*;
pub use crate::frame_info::FrameInfo;
pub use crate::func::{Func, WasmRet, WasmTy};
pub use crate::func::*;
pub use crate::instance::Instance;
pub use crate::module::Module;
pub use crate::r#ref::{AnyRef, HostInfo, HostRef};
Expand Down
6 changes: 3 additions & 3 deletions crates/api/tests/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn cross_store() -> anyhow::Result<()> {

// ============ Cross-store instantiation ==============

let func = Func::wrap0(&store2, || {});
let func = Func::wrap(&store2, || {});
let ty = GlobalType::new(ValType::I32, Mutability::Const);
let global = Global::new(&store2, ty, Val::I32(0))?;
let ty = MemoryType::new(Limits::new(1, None));
Expand All @@ -84,8 +84,8 @@ fn cross_store() -> anyhow::Result<()> {

// ============ Cross-store globals ==============

let store1val = Val::FuncRef(Func::wrap0(&store1, || {}));
let store2val = Val::FuncRef(Func::wrap0(&store2, || {}));
let store1val = Val::FuncRef(Func::wrap(&store1, || {}));
let store2val = Val::FuncRef(Func::wrap(&store2, || {}));

let ty = GlobalType::new(ValType::FuncRef, Mutability::Var);
assert!(Global::new(&store2, ty.clone(), store1val.clone()).is_err());
Expand Down
139 changes: 102 additions & 37 deletions crates/api/tests/func.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
use anyhow::Result;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasmtime::{Callable, Func, FuncType, Instance, Module, Store, Trap, Val, ValType};
use wasmtime::*;

#[test]
fn func_constructors() {
let store = Store::default();
Func::wrap0(&store, || {});
Func::wrap1(&store, |_: i32| {});
Func::wrap2(&store, |_: i32, _: i64| {});
Func::wrap2(&store, |_: f32, _: f64| {});
Func::wrap0(&store, || -> i32 { 0 });
Func::wrap0(&store, || -> i64 { 0 });
Func::wrap0(&store, || -> f32 { 0.0 });
Func::wrap0(&store, || -> f64 { 0.0 });

Func::wrap0(&store, || -> Result<(), Trap> { loop {} });
Func::wrap0(&store, || -> Result<i32, Trap> { loop {} });
Func::wrap0(&store, || -> Result<i64, Trap> { loop {} });
Func::wrap0(&store, || -> Result<f32, Trap> { loop {} });
Func::wrap0(&store, || -> Result<f64, Trap> { loop {} });
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]
Expand All @@ -37,7 +37,7 @@ fn dtor_runs() {
let store = Store::default();
let a = A;
assert_eq!(HITS.load(SeqCst), 0);
Func::wrap0(&store, move || {
Func::wrap(&store, move || {
drop(&a);
});
assert_eq!(HITS.load(SeqCst), 1);
Expand All @@ -57,7 +57,7 @@ fn dtor_delayed() -> Result<()> {

let store = Store::default();
let a = A;
let func = Func::wrap0(&store, move || drop(&a));
let func = Func::wrap(&store, move || drop(&a));

assert_eq!(HITS.load(SeqCst), 0);
let wasm = wat::parse_str(r#"(import "" "" (func))"#)?;
Expand All @@ -73,27 +73,27 @@ fn dtor_delayed() -> Result<()> {
fn signatures_match() {
let store = Store::default();

let f = Func::wrap0(&store, || {});
let f = Func::wrap(&store, || {});
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[]);

let f = Func::wrap0(&store, || -> i32 { loop {} });
let f = Func::wrap(&store, || -> i32 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::I32]);

let f = Func::wrap0(&store, || -> i64 { loop {} });
let f = Func::wrap(&store, || -> i64 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::I64]);

let f = Func::wrap0(&store, || -> f32 { loop {} });
let f = Func::wrap(&store, || -> f32 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::F32]);

let f = Func::wrap0(&store, || -> f64 { loop {} });
let f = Func::wrap(&store, || -> f64 { loop {} });
assert_eq!(f.ty().params(), &[]);
assert_eq!(f.ty().results(), &[ValType::F64]);

let f = Func::wrap5(&store, |_: f32, _: f64, _: i32, _: i64, _: i32| -> f64 {
let f = Func::wrap(&store, |_: f32, _: f64, _: i32, _: i64, _: i32| -> f64 {
loop {}
});
assert_eq!(
Expand Down Expand Up @@ -144,23 +144,23 @@ fn import_works() -> Result<()> {
Instance::new(
&module,
&[
Func::wrap0(&store, || {
Func::wrap(&store, || {
assert_eq!(HITS.fetch_add(1, SeqCst), 0);
})
.into(),
Func::wrap1(&store, |x: i32| -> i32 {
Func::wrap(&store, |x: i32| -> i32 {
assert_eq!(x, 0);
assert_eq!(HITS.fetch_add(1, SeqCst), 1);
1
})
.into(),
Func::wrap2(&store, |x: i32, y: i64| {
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::wrap5(&store, |a: i32, b: i64, c: i32, d: f32, e: f64| {
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);
Expand All @@ -177,7 +177,7 @@ fn import_works() -> Result<()> {
#[test]
fn trap_smoke() {
let store = Store::default();
let f = Func::wrap0(&store, || -> Result<(), Trap> { Err(Trap::new("test")) });
let f = Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("test")) });
let err = f.call(&[]).unwrap_err();
assert_eq!(err.message(), "test");
}
Expand All @@ -194,7 +194,7 @@ fn trap_import() -> Result<()> {
let module = Module::new(&store, &wasm)?;
let trap = Instance::new(
&module,
&[Func::wrap0(&store, || -> Result<(), Trap> { Err(Trap::new("foo")) }).into()],
&[Func::wrap(&store, || -> Result<(), Trap> { Err(Trap::new("foo")) }).into()],
)
.err()
.unwrap()
Expand All @@ -206,7 +206,7 @@ fn trap_import() -> Result<()> {
#[test]
fn get_from_wrapper() {
let store = Store::default();
let f = Func::wrap0(&store, || {});
let f = Func::wrap(&store, || {});
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<(), ()>().is_ok());
Expand All @@ -216,23 +216,23 @@ fn get_from_wrapper() {
assert!(f.get2::<i32, i32, ()>().is_err());
assert!(f.get2::<i32, i32, i32>().is_err());

let f = Func::wrap0(&store, || -> i32 { loop {} });
let f = Func::wrap(&store, || -> i32 { loop {} });
assert!(f.get0::<i32>().is_ok());
let f = Func::wrap0(&store, || -> f32 { loop {} });
let f = Func::wrap(&store, || -> f32 { loop {} });
assert!(f.get0::<f32>().is_ok());
let f = Func::wrap0(&store, || -> f64 { loop {} });
let f = Func::wrap(&store, || -> f64 { loop {} });
assert!(f.get0::<f64>().is_ok());

let f = Func::wrap1(&store, |_: i32| {});
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::wrap1(&store, |_: i64| {});
let f = Func::wrap(&store, |_: i64| {});
assert!(f.get1::<i64, ()>().is_ok());
let f = Func::wrap1(&store, |_: f32| {});
let f = Func::wrap(&store, |_: f32| {});
assert!(f.get1::<f32, ()>().is_ok());
let f = Func::wrap1(&store, |_: f64| {});
let f = Func::wrap(&store, |_: f64| {});
assert!(f.get1::<f64, ()>().is_ok());
}

Expand Down Expand Up @@ -289,3 +289,68 @@ fn get_from_module() -> anyhow::Result<()> {
assert!(f2.get1::<i32, f32>().is_err());
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(())
}
4 changes: 2 additions & 2 deletions crates/api/tests/traps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ fn rust_panic_import() -> Result<()> {
&module,
&[
func.into(),
Func::wrap0(&store, || panic!("this is another panic")).into(),
Func::wrap(&store, || panic!("this is another panic")).into(),
],
)?;
let func = instance.exports()[0].func().unwrap().clone();
Expand Down Expand Up @@ -329,7 +329,7 @@ fn rust_panic_start_function() -> Result<()> {
.unwrap_err();
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));

let func = Func::wrap0(&store, || panic!("this is another panic"));
let func = Func::wrap(&store, || panic!("this is another panic"));
let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(Instance::new(&module, &[func.into()]));
}))
Expand Down
16 changes: 9 additions & 7 deletions crates/wasi-common/wig/src/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,24 +182,26 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
}

let format_str = format!("{}({})", name, formats.join(", "));
let wrap = format_ident!("wrap{}", shim_arg_decls.len() + 1);
ctor_externs.push(quote! {
let my_cx = cx.clone();
let #name_ident = wasmtime::Func::#wrap(
let #name_ident = wasmtime::Func::wrap(
store,
move |mem: crate::WasiCallerMemory #(,#shim_arg_decls)*| -> #ret_ty {
move |caller: wasmtime::Caller<'_> #(,#shim_arg_decls)*| -> #ret_ty {
log::trace!(
#format_str,
#(#format_args),*
);
unsafe {
let memory = match mem.get() {
Ok(e) => e,
Err(e) => #handle_early_error,
let memory = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
let e = wasi_common::wasi::__WASI_ERRNO_INVAL;
#handle_early_error
}
};
hostcalls::#name_ident(
&mut my_cx.borrow_mut(),
memory,
memory.data_unchecked_mut(),
#(#hostcall_args),*
) #cvt_ret
}
Expand Down
57 changes: 0 additions & 57 deletions crates/wasi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,60 +16,3 @@ pub fn is_wasi_module(name: &str) -> bool {
// trick.
name.starts_with("wasi")
}

/// This is an internal structure used to acquire a handle on the caller's
/// wasm memory buffer.
///
/// This exploits how we can implement `WasmTy` for ourselves locally even
/// though crates in general should not be doing that. This is a crate in
/// the wasmtime project, however, so we should be able to keep up with our own
/// changes.
///
/// In general this type is wildly unsafe. We need to update the wasi crates to
/// probably work with more `wasmtime`-like APIs to grip with the unsafety
/// around dealing with caller memory.
struct WasiCallerMemory {
base: *mut u8,
len: usize,
}

impl wasmtime::WasmTy for WasiCallerMemory {
type Abi = ();

fn push(_dst: &mut Vec<wasmtime::ValType>) {}

fn matches(_tys: impl Iterator<Item = wasmtime::ValType>) -> anyhow::Result<()> {
Ok(())
}

fn from_abi(vmctx: *mut wasmtime_runtime::VMContext, _abi: ()) -> Self {
unsafe {
match wasmtime_runtime::InstanceHandle::from_vmctx(vmctx).lookup("memory") {
Some(wasmtime_runtime::Export::Memory {
definition,
vmctx: _,
memory: _,
}) => WasiCallerMemory {
base: (*definition).base,
len: (*definition).current_length,
},
_ => WasiCallerMemory {
base: std::ptr::null_mut(),
len: 0,
},
}
}
}

fn into_abi(self) {}
}

impl WasiCallerMemory {
unsafe fn get(&self) -> Result<&mut [u8], wasi_common::wasi::__wasi_errno_t> {
if self.base.is_null() {
Err(wasi_common::wasi::__WASI_ERRNO_INVAL)
} else {
Ok(std::slice::from_raw_parts_mut(self.base, self.len))
}
}
}
Loading

0 comments on commit bb021dd

Please sign in to comment.