Skip to content

Commit

Permalink
Reactor support. (#1565)
Browse files Browse the repository at this point in the history
* Reactor support.

This implements the new WASI ABI described here:

https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md

It adds APIs to `Instance` and `Linker` with support for running
WASI programs, and also simplifies the process of instantiating
WASI API modules.

This currently only includes Rust API support.

* Add comments and fix a typo in a comment.

* Fix a rustdoc warning.

* Tidy an unneeded `mut`.

* Factor out instance initialization with `NewInstance`.

This also separates instantiation from initialization in a manner
similar to bytecodealliance/lucet#506.

* Update fuzzing oracles for the API changes.

* Remove `wasi_linker` and clarify that Commands/Reactors aren't connected to WASI.

* Move Command/Reactor semantics into the Linker.

* C API support.

* Fix fuzzer build.

* Update usage syntax from "::" to "=".

* Remove `NewInstance` and `start()`.

* Elaborate on Commands and Reactors and add a spec link.

* Add more comments.

* Fix wat syntax.

* Fix wat.

* Use the `Debug` formatter to format an anyhow::Error.

* Fix wat.
  • Loading branch information
sunfishcode authored May 26, 2020
1 parent c619136 commit 3715e19
Show file tree
Hide file tree
Showing 19 changed files with 720 additions and 241 deletions.
5 changes: 4 additions & 1 deletion RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ Unreleased

### Added

### Changed
* The [Commands and Reactors ABI] is now supported in the Rust API. `Linker::module`
loads a module and automatically handles Commands and Reactors semantics.

[Commands and Reactors ABI]: https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi

### Fixed

Expand Down
12 changes: 12 additions & 0 deletions crates/c-api/include/wasmtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ WASM_API_EXTERN own wasmtime_error_t* wasmtime_linker_instantiate(
own wasm_trap_t **trap
);

WASM_API_EXTERN own wasmtime_error_t* wasmtime_linker_module(
const wasmtime_linker_t *linker,
const wasm_name_t *name,
const wasm_module_t *module
);

WASM_API_EXTERN own wasmtime_error_t* wasmtime_linker_get_default(
const wasmtime_linker_t *linker,
const wasm_name_t *name,
own wasm_func_t **func
);

///////////////////////////////////////////////////////////////////////////////
//
// wasmtime_caller_t extension, binding the `Caller` type in the Rust API
Expand Down
34 changes: 32 additions & 2 deletions crates/c-api/src/linker.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{bad_utf8, handle_result, wasmtime_error_t};
use crate::{wasm_extern_t, wasm_store_t, ExternHost};
use crate::{wasm_instance_t, wasm_module_t, wasm_name_t, wasm_trap_t};
use crate::{wasm_func_t, wasm_instance_t, wasm_module_t, wasm_name_t, wasm_trap_t};
use std::str;
use wasmtime::{Extern, Linker};
use wasmtime::{Extern, HostRef, Linker};

#[repr(C)]
pub struct wasmtime_linker_t {
Expand Down Expand Up @@ -89,3 +89,33 @@ pub unsafe extern "C" fn wasmtime_linker_instantiate(
let result = linker.linker.instantiate(&module.module.borrow());
super::instance::handle_instantiate(result, instance_ptr, trap_ptr)
}

#[no_mangle]
pub unsafe extern "C" fn wasmtime_linker_module(
linker: &mut wasmtime_linker_t,
name: &wasm_name_t,
module: &wasm_module_t,
) -> Option<Box<wasmtime_error_t>> {
let linker = &mut linker.linker;
let name = match str::from_utf8(name.as_slice()) {
Ok(s) => s,
Err(_) => return bad_utf8(),
};
handle_result(linker.module(name, &module.module.borrow()), |_linker| ())
}

#[no_mangle]
pub unsafe extern "C" fn wasmtime_linker_get_default(
linker: &mut wasmtime_linker_t,
name: &wasm_name_t,
func: &mut *mut wasm_func_t,
) -> Option<Box<wasmtime_error_t>> {
let linker = &mut linker.linker;
let name = match str::from_utf8(name.as_slice()) {
Ok(s) => s,
Err(_) => return bad_utf8(),
};
handle_result(linker.get_default(name), |f| {
*func = Box::into_raw(Box::new(HostRef::new(f).into()))
})
}
4 changes: 0 additions & 4 deletions crates/runtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,8 +1330,4 @@ pub enum InstantiationError {
/// A trap ocurred during instantiation, after linking.
#[error("Trap occurred during instantiation")]
Trap(Trap),

/// A trap occurred while running the wasm start function.
#[error("Trap occurred while invoking start function")]
StartTrap(Trap),
}
41 changes: 12 additions & 29 deletions crates/test-programs/tests/wasm_tests/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use anyhow::{bail, Context};
use anyhow::Context;
use std::fs::File;
use std::path::Path;
use wasi_common::VirtualDirEntry;
use wasmtime::{Instance, Module, Store};
use wasmtime::{Linker, Module, Store};

#[derive(Clone, Copy, Debug)]
pub enum PreopenType {
Expand Down Expand Up @@ -48,36 +48,19 @@ pub fn instantiate(
let (reader, _writer) = os_pipe::pipe()?;
builder.stdin(reader_to_file(reader));
let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let module = Module::new(&store, &data).context("failed to create wasm module")?;
let imports = module
.imports()
.map(|i| {
let field_name = i.name();
if let Some(export) = snapshot1.get_export(field_name) {
Ok(export.clone().into())
} else {
bail!(
"import {} was not found in module {}",
field_name,
i.module()
)
}
})
.collect::<Result<Vec<_>, _>>()?;

let instance = Instance::new(&module, &imports).context(format!(
"error while instantiating Wasm module '{}'",
bin_name,
))?;
let mut linker = Linker::new(&store);

instance
.get_export("_start")
.context("expected a _start export")?
.into_func()
.context("expected export to be a func")?
.call(&[])?;
snapshot1.add_to_linker(&mut linker)?;

let module = Module::new(&store, &data).context("failed to create wasm module")?;

Ok(())
linker
.module("", &module)
.and_then(|m| m.get_default(""))
.and_then(|f| f.get0::<()>())
.and_then(|f| f().map_err(Into::into))
.context(format!("error while testing Wasm module '{}'", bin_name,))
}

#[cfg(unix)]
Expand Down
2 changes: 2 additions & 0 deletions crates/wasi-common/wig/src/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
let memory = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
log::warn!("callee does not export a memory as \"memory\"");
let e = wasi_common::old::snapshot_0::wasi::__WASI_ERRNO_INVAL;
#handle_early_error
}
Expand Down Expand Up @@ -463,6 +464,7 @@ pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream {
let mem = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
log::warn!("callee does not export a memory as \"memory\"");
let e = wasi_common::wasi::Errno::Inval;
#handle_early_error
}
Expand Down
83 changes: 49 additions & 34 deletions crates/wasmtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,25 @@ fn instantiate(
sig_registry: &SignatureRegistry,
host: Box<dyn Any>,
) -> Result<StoreInstanceHandle, Error> {
// For now we have a restriction that the `Store` that we're working
// with is the same for everything involved here.
for import in imports {
if !import.comes_from_same_store(store) {
bail!("cross-`Store` instantiation is not currently supported");
}
}

if imports.len() != compiled_module.module().imports.len() {
bail!(
"wrong number of imports provided, {} != {}",
imports.len(),
compiled_module.module().imports.len()
);
}

let mut resolver = SimpleResolver { imports };
unsafe {
let config = store.engine().config();
let config = store.engine().config();
let instance = unsafe {
let instance = compiled_module.instantiate(
&mut resolver,
sig_registry,
Expand All @@ -51,31 +67,38 @@ fn instantiate(
)
.map_err(|e| -> Error {
match e {
InstantiationError::StartTrap(trap) | InstantiationError::Trap(trap) => {
Trap::from_runtime(trap).into()
}
InstantiationError::Trap(trap) => Trap::from_runtime(trap).into(),
other => other.into(),
}
})?;

// If a start function is present, now that we've got our compiled
// instance we can invoke it. Make sure we use all the trap-handling
// configuration in `store` as well.
if let Some(start) = instance.module().start_func {
let f = match instance.lookup_by_declaration(&EntityIndex::Function(start)) {
wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(), // valid modules shouldn't hit this
};
super::func::catch_traps(instance.vmctx_ptr(), store, || {
instance
};

let start_func = instance.handle.module().start_func;

// If a start function is present, invoke it. Make sure we use all the
// trap-handling configuration in `store` as well.
if let Some(start) = start_func {
let f = match instance
.handle
.lookup_by_declaration(&EntityIndex::Function(start))
{
wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(), // valid modules shouldn't hit this
};
let vmctx_ptr = instance.handle.vmctx_ptr();
unsafe {
super::func::catch_traps(vmctx_ptr, store, || {
mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(*mut VMContext, *mut VMContext),
>(f.address)(f.vmctx, instance.vmctx_ptr())
>(f.address)(f.vmctx, vmctx_ptr)
})?;
}

Ok(instance)
}

Ok(instance)
}

/// An instantiated WebAssembly module.
Expand Down Expand Up @@ -111,6 +134,15 @@ impl Instance {
/// automatically run (if provided) and then the [`Instance`] will be
/// returned.
///
/// Per the WebAssembly spec, instantiation includes running the module's
/// start function, if it has one (not to be confused with the `_start`
/// function, which is not run).
///
/// Note that this is a low-level function that just performance an
/// instantiation. See the `Linker` struct for an API which provides a
/// convenient way to link imports and provides automatic Command and Reactor
/// behavior.
///
/// ## Providing Imports
///
/// The `imports` array here is a bit tricky. The entries in the list of
Expand Down Expand Up @@ -147,23 +179,6 @@ impl Instance {
/// [`ExternType`]: crate::ExternType
pub fn new(module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
let store = module.store();

// For now we have a restriction that the `Store` that we're working
// with is the same for everything involved here.
for import in imports {
if !import.comes_from_same_store(store) {
bail!("cross-`Store` instantiation is not currently supported");
}
}

if imports.len() != module.imports().len() {
bail!(
"wrong number of imports provided, {} != {}",
imports.len(),
module.imports().len()
);
}

let info = module.register_frame_info();
let handle = instantiate(
store,
Expand Down
Loading

0 comments on commit 3715e19

Please sign in to comment.