Reactor support. (#1565)

* 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 https://github.com/bytecodealliance/lucet/pull/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.
This commit is contained in:
Dan Gohman
2020-05-26 08:39:40 -07:00
committed by GitHub
parent c619136752
commit 3715e19c67
19 changed files with 724 additions and 245 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 {
@@ -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()))
})
}

View File

@@ -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),
}

View File

@@ -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 {
@@ -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 mut linker = Linker::new(&store);
snapshot1.add_to_linker(&mut linker)?;
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,
))?;
instance
.get_export("_start")
.context("expected a _start export")?
.into_func()
.context("expected export to be a func")?
.call(&[])?;
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)]

View File

@@ -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
}
@@ -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
}

View File

@@ -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,
@@ -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.
@@ -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
@@ -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,

View File

@@ -1,7 +1,8 @@
use crate::{
Extern, ExternType, Func, FuncType, GlobalType, ImportType, Instance, IntoFunc, Module, Store,
Trap,
};
use anyhow::{anyhow, bail, Result};
use anyhow::{anyhow, bail, Context, Result};
use std::collections::hash_map::{Entry, HashMap};
use std::rc::Rc;
@@ -270,6 +271,187 @@ impl Linker {
Ok(self)
}
/// Define automatic instantiations of a [`Module`] in this linker.
///
/// This automatically handles [Commands and Reactors] instantiation and
/// initialization.
///
/// Exported functions of a Command module may be called directly, however
/// instead of having a single instance which is reused for each call,
/// each call creates a new instance, which lives for the duration of the
/// call. The imports of the Command are resolved once, and reused for
/// each instantiation, so all dependencies need to be present at the time
/// when `Linker::module` is called.
///
/// For Reactors, a single instance is created, and an initialization
/// function is called, and then its exports may be called.
///
/// Ordinary modules which don't declare themselves to be either Commands
/// or Reactors are treated as Reactors without any initialization calls.
///
/// [Commands and Reactors]: https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi
///
/// # Errors
///
/// Returns an error if the any item is redefined twice in this linker (for
/// example the same `module_name` was already defined) and shadowing is
/// disallowed, if `instance` comes from a different [`Store`] than this
/// [`Linker`] originally was created with, or if a Reactor initialization
/// function traps.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let mut linker = Linker::new(&store);
///
/// // Instantiate a small instance and inform the linker that the name of
/// // this instance is `instance1`. This defines the `instance1::run` name
/// // for our next module to use.
/// let wat = r#"(module (func (export "run") ))"#;
/// let module = Module::new(&store, wat)?;
/// linker.module("instance1", &module)?;
///
/// let wat = r#"
/// (module
/// (import "instance1" "run" (func $instance1_run))
/// (func (export "run")
/// call $instance1_run
/// )
/// )
/// "#;
/// let module = Module::new(&store, wat)?;
/// let instance = linker.instantiate(&module)?;
/// # Ok(())
/// # }
/// ```
///
/// For a Command, a new instance is created for each call.
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let mut linker = Linker::new(&store);
///
/// // Create a Command that attempts to count the number of times it is run, but is
/// // foiled by each call getting a new instance.
/// let wat = r#"
/// (module
/// (global $counter (mut i32) (i32.const 0))
/// (func (export "_start")
/// (global.set $counter (i32.add (global.get $counter) (i32.const 1)))
/// )
/// (func (export "read_counter") (result i32)
/// (global.get $counter)
/// )
/// )
/// "#;
/// let module = Module::new(&store, wat)?;
/// linker.module("commander", &module)?;
/// let run = linker.get_default("")?.get0::<()>()?;
/// run()?;
/// run()?;
/// run()?;
///
/// let wat = r#"
/// (module
/// (import "commander" "_start" (func $commander_start))
/// (import "commander" "read_counter" (func $commander_read_counter (result i32)))
/// (func (export "run") (result i32)
/// call $commander_start
/// call $commander_start
/// call $commander_start
/// call $commander_read_counter
/// )
/// )
/// "#;
/// let module = Module::new(&store, wat)?;
/// linker.module("", &module)?;
/// let count = linker.get_one_by_name("", "run")?.into_func().unwrap().get0::<i32>()?()?;
/// assert_eq!(count, 0, "a Command should get a fresh instance on each invocation");
///
/// # Ok(())
/// # }
/// ```
pub fn module(&mut self, module_name: &str, module: &Module) -> Result<&mut Self> {
match ModuleKind::categorize(module)? {
ModuleKind::Command => self.command(module_name, module),
ModuleKind::Reactor => {
let instance = self.instantiate(&module)?;
if let Some(export) = instance.get_export("_initialize") {
if let Extern::Func(func) = export {
func.get0::<()>()
.and_then(|f| f().map_err(Into::into))
.context("calling the Reactor initialization function")?;
}
}
self.instance(module_name, &instance)
}
}
}
fn command(&mut self, module_name: &str, module: &Module) -> Result<&mut Self> {
for export in module.exports() {
if let Some(func_ty) = export.ty().func() {
let imports = self.compute_imports(module)?;
let module = module.clone();
let export_name = export.name().to_owned();
let func = Func::new(&self.store, func_ty.clone(), move |_, params, results| {
// Create a new instance for this command execution.
let instance = Instance::new(&module, &imports).map_err(|error| match error
.downcast::<Trap>()
{
Ok(trap) => trap,
Err(error) => Trap::new(format!("{:?}", error)),
})?;
// `unwrap()` everything here because we know the instance contains a
// function export with the given name and signature because we're
// iterating over the module it was instantiated from.
let command_results = instance
.get_export(&export_name)
.unwrap()
.into_func()
.unwrap()
.call(params)
.map_err(|error| error.downcast::<Trap>().unwrap())?;
// Copy the return values into the output slice.
for (result, command_result) in
results.iter_mut().zip(command_results.into_vec())
{
*result = command_result;
}
Ok(())
});
self.insert(module_name, export.name(), Extern::Func(func))?;
} else if export.name() == "memory" && export.ty().memory().is_some() {
// Allow an exported "memory" memory for now.
} else if export.name() == "__indirect_function_table" && export.ty().table().is_some()
{
// Allow an exported "__indirect_function_table" table for now.
} else if export.name() == "__data_end" && export.ty().global().is_some() {
// Allow an exported "__data_end" memory for compatibility with toolchains
// which use --export-dynamic, which unfortunately doesn't work the way
// we want it to.
} else if export.name() == "__heap_base" && export.ty().global().is_some() {
// Allow an exported "__data_end" memory for compatibility with toolchains
// which use --export-dynamic, which unfortunately doesn't work the way
// we want it to.
} else {
bail!("command export '{}' is not a function", export.name());
}
}
Ok(self)
}
/// Aliases one module's name as another.
///
/// This method will alias all currently defined under `module` to also be
@@ -350,6 +532,10 @@ impl Linker {
/// incorrect signature or if it was not prevoiusly defined then an error
/// will be returned because the import can not be satisfied.
///
/// 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).
///
/// # Errors
///
/// This method can fail because an import may not be found, or because
@@ -376,7 +562,14 @@ impl Linker {
/// # }
/// ```
pub fn instantiate(&self, module: &Module) -> Result<Instance> {
let imports = self.compute_imports(module)?;
Instance::new(module, &imports)
}
fn compute_imports(&self, module: &Module) -> Result<Vec<Extern>> {
let mut imports = Vec::new();
for import in module.imports() {
if let Some(item) = self.get(&import) {
imports.push(item);
@@ -413,7 +606,7 @@ impl Linker {
)
}
Instance::new(module, &imports)
Ok(imports)
}
/// Returns the [`Store`] that this linker is connected to.
@@ -485,4 +678,85 @@ impl Linker {
}
Ok(ret.clone())
}
/// Returns the "default export" of a module.
///
/// An export with an empty string is considered to be a "default export".
/// "_start" is also recognized for compatibility.
pub fn get_default(&self, module: &str) -> Result<Func> {
let mut items = self.get_by_name(module, "");
if let Some(external) = items.next() {
if items.next().is_some() {
bail!("too many items named `` in `{}`", module);
}
if let Extern::Func(func) = external {
return Ok(func.clone());
}
bail!("default export in '{}' is not a function", module);
}
// For compatibility, also recognize "_start".
let mut items = self.get_by_name(module, "_start");
if let Some(external) = items.next() {
if items.next().is_some() {
bail!("too many items named `_start` in `{}`", module);
}
if let Extern::Func(func) = external {
return Ok(func.clone());
}
bail!("`_start` in '{}' is not a function", module);
}
// Otherwise return a no-op function.
Ok(Func::new(
&self.store,
FuncType::new(Vec::new().into_boxed_slice(), Vec::new().into_boxed_slice()),
move |_, _, _| Ok(()),
))
}
}
/// Modules can be interpreted either as Commands (instance lifetime ends
/// when the start function returns) or Reactor (instance persists).
enum ModuleKind {
/// The instance is a Command, and this is its start function. The
/// instance should be consumed.
Command,
/// The instance is a Reactor, and this is its initialization function,
/// along with the instance itself, which should persist.
Reactor,
}
impl ModuleKind {
/// Determine whether the given module is a Command or a Reactor.
fn categorize(module: &Module) -> Result<ModuleKind> {
let command_start = module.get_export("_start");
let reactor_start = module.get_export("_initialize");
match (command_start, reactor_start) {
(Some(command_start), None) => {
if let Some(_) = command_start.func() {
Ok(ModuleKind::Command)
} else {
bail!("`_start` must be a function")
}
}
(None, Some(reactor_start)) => {
if let Some(_) = reactor_start.func() {
Ok(ModuleKind::Reactor)
} else {
bail!("`_initialize` must be a function")
}
}
(None, None) => {
// Module declares neither of the recognized functions, so treat
// it as a reactor with no initialization function.
Ok(ModuleKind::Reactor)
}
(Some(_), Some(_)) => {
// Module declares itself to be both a Command and a Reactor.
bail!("Program cannot be both a Command and a Reactor")
}
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::frame_info::GlobalFrameInfoRegistration;
use crate::runtime::Store;
use crate::types::{EntityType, ExportType, ImportType};
use crate::types::{EntityType, ExportType, ExternType, ImportType};
use anyhow::{Error, Result};
use std::path::Path;
use std::sync::{Arc, Mutex};
@@ -478,6 +478,55 @@ impl Module {
})
}
/// Looks up an export in this [`Module`] by name.
///
/// This function will return the type of an export with the given name.
///
/// # Examples
///
/// There may be no export with that name:
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let module = Module::new(&store, "(module)")?;
/// assert!(module.get_export("foo").is_none());
/// # Ok(())
/// # }
/// ```
///
/// When there is an export with that name, it is returned:
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// # let store = Store::default();
/// let wat = r#"
/// (module
/// (func (export "foo"))
/// (memory (export "memory") 1)
/// )
/// "#;
/// let module = Module::new(&store, wat)?;
/// let foo = module.get_export("foo");
/// assert!(foo.is_some());
///
/// let foo = foo.unwrap();
/// match foo {
/// ExternType::Func(_) => { /* ... */ }
/// _ => panic!("unexpected export type!"),
/// }
///
/// # Ok(())
/// # }
/// ```
pub fn get_export<'module>(&'module self, name: &'module str) -> Option<ExternType> {
let module = self.inner.compiled.module_ref();
let entity_index = module.exports.get(name)?;
Some(EntityType::new(entity_index, module).extern_type())
}
/// Returns the [`Store`] that this [`Module`] was compiled into.
pub fn store(&self) -> &Store {
&self.inner.store

View File

@@ -419,7 +419,8 @@ impl<'module> EntityType<'module> {
}
}
fn extern_type(&self) -> ExternType {
/// Convert this `EntityType` to an `ExternType`.
pub(crate) fn extern_type(&self) -> ExternType {
match self {
EntityType::Function(sig) => FuncType::from_wasmtime_signature(sig)
.expect("core wasm function type should be supported")

View File

@@ -72,51 +72,30 @@ int main() {
if (wasi == NULL)
exit_with_error("failed to instantiate WASI", NULL, trap);
// Create import list for our module using wasi
wasm_importtype_vec_t import_types;
wasm_module_imports(module, &import_types);
const wasm_extern_t **imports = calloc(import_types.size, sizeof(void*));
assert(imports);
for (int i = 0; i < import_types.size; i++) {
const wasm_extern_t *binding = wasi_instance_bind_import(wasi, import_types.data[i]);
if (binding != NULL) {
imports[i] = binding;
} else {
printf("> Failed to satisfy import\n");
exit(1);
}
}
wasmtime_linker_t *linker = wasmtime_linker_new(store);
error = wasmtime_linker_define_wasi(linker, wasi);
if (error != NULL)
exit_with_error("failed to link wasi", error, NULL);
// Instantiate the module
wasm_name_t empty;
wasm_name_new_from_string(&empty, "");
wasm_instance_t *instance = NULL;
error = wasmtime_instance_new(module, imports, import_types.size, &instance, &trap);
if (instance == NULL)
exit_with_error("failed to instantiate", error, trap);
free(imports);
wasm_importtype_vec_delete(&import_types);
error = wasmtime_linker_module(linker, &empty, module);
if (error != NULL)
exit_with_error("failed to instantiate module", error, NULL);
// Lookup our `_start` export function
wasm_extern_vec_t externs;
wasm_instance_exports(instance, &externs);
wasm_exporttype_vec_t exports;
wasm_module_exports(module, &exports);
wasm_extern_t *start_extern = NULL;
for (int i = 0; i < exports.size; i++) {
const wasm_name_t *name = wasm_exporttype_name(exports.data[i]);
if (strncmp(name->data, "_start", name->size) == 0)
start_extern = externs.data[i];
}
assert(start_extern);
wasm_func_t *start = wasm_extern_as_func(start_extern);
assert(start != NULL);
error = wasmtime_func_call(start, NULL, 0, NULL, 0, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to call `_start`", error, trap);
// Run it.
wasm_func_t* func;
wasmtime_linker_get_default(linker, &empty, &func);
if (error != NULL)
exit_with_error("failed to locate default export for module", error, NULL);
error = wasmtime_func_call(func, NULL, 0, NULL, 0, &trap);
if (error != NULL)
exit_with_error("error calling default export", error, trap);
// Clean up after ourselves at this point
wasm_exporttype_vec_delete(&exports);
wasm_extern_vec_delete(&externs);
wasm_instance_delete(instance);
wasm_name_delete(&empty);
wasm_module_delete(module);
wasm_store_delete(store);
wasm_engine_delete(engine);

View File

@@ -9,32 +9,18 @@ use wasmtime_wasi::{Wasi, WasiCtx};
fn main() -> Result<()> {
let store = Store::default();
let module = Module::from_file(&store, "target/wasm32-wasi/debug/wasi.wasm")?;
let mut linker = Linker::new(&store);
// Create an instance of `Wasi` which contains a `WasiCtx`. Note that
// `WasiCtx` provides a number of ways to configure what the target program
// will have access to.
let wasi = Wasi::new(&store, WasiCtx::new(std::env::args())?);
let mut imports = Vec::new();
for import in module.imports() {
if import.module() == "wasi_snapshot_preview1" {
if let Some(export) = wasi.get_export(import.name()) {
imports.push(Extern::from(export.clone()));
continue;
}
}
panic!(
"couldn't find import for `{}::{}`",
import.module(),
import.name()
);
}
wasi.add_to_linker(&mut linker)?;
// Instantiate our module with the imports we've created, and run it.
let module = Module::from_file(&store, "target/wasm32-wasi/debug/wasi.wasm")?;
linker.module("", &module)?;
linker.get_default("")?.get0::<()>()?()?;
// Instance our module with the imports we've created, then we can run the
// standard wasi `_start` function.
let instance = Instance::new(&module, &imports)?;
let start = instance.get_func("_start").unwrap();
let start = start.get0::<()>()?;
start()?;
Ok(())
}

View File

@@ -7,13 +7,13 @@ use std::time::Duration;
use std::{
ffi::{OsStr, OsString},
fs::File,
path::{Component, Path, PathBuf},
path::{Component, PathBuf},
process,
};
use structopt::{clap::AppSettings, StructOpt};
use wasi_common::preopen_dir;
use wasmtime::{Engine, Instance, Module, Store, Trap, Val, ValType};
use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi};
use wasi_common::{preopen_dir, WasiCtxBuilder};
use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType};
use wasmtime_wasi::Wasi;
fn parse_module(s: &OsStr) -> Result<PathBuf, OsString> {
// Do not accept wasmtime subcommand names as the module name
@@ -51,6 +51,14 @@ fn parse_dur(s: &str) -> Result<Duration> {
Ok(dur)
}
fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
let parts: Vec<&str> = s.splitn(2, '=').collect();
if parts.len() != 2 {
bail!("must contain exactly one equals character ('=')");
}
Ok((parts[0].into(), parts[1].into()))
}
/// Runs a WebAssembly module
#[derive(StructOpt)]
#[structopt(name = "run", setting = AppSettings::TrailingVarArg)]
@@ -87,10 +95,10 @@ pub struct RunCommand {
#[structopt(
long = "preload",
number_of_values = 1,
value_name = "MODULE_PATH",
parse(from_os_str)
value_name = "NAME=MODULE_PATH",
parse(try_from_str = parse_preloads)
)]
preloads: Vec<PathBuf>,
preloads: Vec<(String, PathBuf)>,
/// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc)
#[structopt(
@@ -127,17 +135,25 @@ impl RunCommand {
let preopen_dirs = self.compute_preopen_dirs()?;
let argv = self.compute_argv();
let module_registry = ModuleRegistry::new(&store, &preopen_dirs, &argv, &self.vars)?;
let mut linker = Linker::new(&store);
populate_with_wasi(&mut linker, &preopen_dirs, &argv, &self.vars)?;
// Load the preload wasm modules.
for preload in self.preloads.iter() {
Self::instantiate_module(&store, &module_registry, preload)
.with_context(|| format!("failed to process preload at `{}`", preload.display()))?;
for (name, path) in self.preloads.iter() {
// Read the wasm module binary either as `*.wat` or a raw binary
let module = Module::from_file(linker.store(), path)?;
// Add the module's functions to the linker.
linker.module(name, &module).context(format!(
"failed to process preload `{}` at `{}`",
name,
path.display()
))?;
}
// Load the main wasm module.
match self
.handle_module(&store, &module_registry)
.load_main_module(&mut linker)
.with_context(|| format!("failed to run main module `{}`", self.module.display()))
{
Ok(()) => (),
@@ -220,79 +236,40 @@ impl RunCommand {
result
}
fn instantiate_module(
store: &Store,
module_registry: &ModuleRegistry,
path: &Path,
) -> Result<Instance> {
// Read the wasm module binary either as `*.wat` or a raw binary
let data = wat::parse_file(path)?;
let module = Module::new(store, &data)?;
// Resolve import using module_registry.
let imports = module
.imports()
.map(|i| {
let export = match i.module() {
"wasi_snapshot_preview1" => {
module_registry.wasi_snapshot_preview1.get_export(i.name())
}
"wasi_unstable" => module_registry.wasi_unstable.get_export(i.name()),
other => bail!("import module `{}` was not found", other),
};
match export {
Some(export) => Ok(export.clone().into()),
None => bail!(
"import `{}` was not found in module `{}`",
i.name(),
i.module()
),
}
})
.collect::<Result<Vec<_>, _>>()?;
let instance = Instance::new(&module, &imports)
.context(format!("failed to instantiate {:?}", path))?;
Ok(instance)
}
fn handle_module(&self, store: &Store, module_registry: &ModuleRegistry) -> Result<()> {
fn load_main_module(&self, linker: &mut Linker) -> Result<()> {
if let Some(timeout) = self.wasm_timeout {
let handle = store.interrupt_handle()?;
let handle = linker.store().interrupt_handle()?;
thread::spawn(move || {
thread::sleep(timeout);
handle.interrupt();
});
}
let instance = Self::instantiate_module(store, module_registry, &self.module)?;
// Read the wasm module binary either as `*.wat` or a raw binary.
// Use "" as a default module name.
let module = Module::from_file(linker.store(), &self.module)?;
linker
.module("", &module)
.context(format!("failed to instantiate {:?}", self.module))?;
// If a function to invoke was given, invoke it.
if let Some(name) = self.invoke.as_ref() {
self.invoke_export(instance, name)?;
} else if instance.exports().any(|export| export.name().is_empty()) {
// Launch the default command export.
self.invoke_export(instance, "")?;
self.invoke_export(linker, name)
} else {
// If the module doesn't have a default command export, launch the
// _start function if one is present, as a compatibility measure.
self.invoke_export(instance, "_start")?;
let func = linker.get_default("")?;
self.invoke_func(func, None)
}
Ok(())
}
fn invoke_export(&self, instance: Instance, name: &str) -> Result<()> {
let func = if let Some(export) = instance.get_export(name) {
if let Some(func) = export.into_func() {
func
} else {
bail!("export of `{}` wasn't a function", name)
}
} else {
bail!("failed to find export of `{}` in module", name)
fn invoke_export(&self, linker: &Linker, name: &str) -> Result<()> {
let func = match linker.get_one_by_name("", name)?.into_func() {
Some(func) => func,
None => bail!("export of `{}` wasn't a function", name),
};
self.invoke_func(func, Some(name))
}
fn invoke_func(&self, func: Func, name: Option<&str>) -> Result<()> {
let ty = func.ty();
if ty.params().len() > 0 {
eprintln!(
@@ -305,7 +282,13 @@ impl RunCommand {
for ty in ty.params() {
let val = match args.next() {
Some(s) => s,
None => bail!("not enough arguments for `{}`", name),
None => {
if let Some(name) = name {
bail!("not enough arguments for `{}`", name)
} else {
bail!("not enough arguments for command default")
}
}
};
values.push(match ty {
// TODO: integer parsing here should handle hexadecimal notation
@@ -321,9 +304,13 @@ impl RunCommand {
// Invoke the function and then afterwards print all the results that came
// out, if there are any.
let results = func
.call(&values)
.with_context(|| format!("failed to invoke `{}`", name))?;
let results = func.call(&values).with_context(|| {
if let Some(name) = name {
format!("failed to invoke `{}`", name)
} else {
format!("failed to invoke command default")
}
})?;
if !results.is_empty() {
eprintln!(
"warning: using `--invoke` with a function that returns values \
@@ -347,41 +334,36 @@ impl RunCommand {
}
}
struct ModuleRegistry {
wasi_snapshot_preview1: Wasi,
wasi_unstable: WasiSnapshot0,
}
/// Populates the given `Linker` with WASI APIs.
fn populate_with_wasi(
linker: &mut Linker,
preopen_dirs: &[(String, File)],
argv: &[String],
vars: &[(String, String)],
) -> Result<()> {
// Add the current snapshot to the linker.
let mut cx = WasiCtxBuilder::new();
cx.inherit_stdio().args(argv).envs(vars);
impl ModuleRegistry {
fn new(
store: &Store,
preopen_dirs: &[(String, File)],
argv: &[String],
vars: &[(String, String)],
) -> Result<ModuleRegistry> {
let mut cx1 = wasi_common::WasiCtxBuilder::new();
cx1.inherit_stdio().args(argv).envs(vars);
for (name, file) in preopen_dirs {
cx1.preopened_dir(file.try_clone()?, name);
}
let cx1 = cx1.build()?;
let mut cx2 = wasi_common::old::snapshot_0::WasiCtxBuilder::new();
cx2.inherit_stdio().args(argv).envs(vars);
for (name, file) in preopen_dirs {
cx2.preopened_dir(file.try_clone()?, name);
}
let cx2 = cx2.build()?;
Ok(ModuleRegistry {
wasi_snapshot_preview1: Wasi::new(store, cx1),
wasi_unstable: WasiSnapshot0::new(store, cx2),
})
for (name, file) in preopen_dirs {
cx.preopened_dir(file.try_clone()?, name);
}
let cx = cx.build()?;
let wasi = Wasi::new(linker.store(), cx);
wasi.add_to_linker(linker)?;
// Repeat the above, but this time for snapshot 0.
let mut cx = wasi_common::old::snapshot_0::WasiCtxBuilder::new();
cx.inherit_stdio().args(argv).envs(vars);
for (name, file) in preopen_dirs {
cx.preopened_dir(file.try_clone()?, name);
}
let cx = cx.build()?;
let wasi = wasmtime_wasi::old::snapshot_0::Wasi::new(linker.store(), cx);
wasi.add_to_linker(linker)?;
Ok(())
}

View File

@@ -256,3 +256,97 @@ fn exit126_wasi_snapshot1() -> Result<()> {
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
Ok(())
}
// Run a minimal command program.
#[test]
fn minimal_command() -> Result<()> {
let wasm = build_wasm("tests/wasm/minimal-command.wat")?;
let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
assert_eq!(stdout, "");
Ok(())
}
// Run a minimal reactor program.
#[test]
fn minimal_reactor() -> Result<()> {
let wasm = build_wasm("tests/wasm/minimal-reactor.wat")?;
let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?;
assert_eq!(stdout, "");
Ok(())
}
// Attempt to call invoke on a command.
#[test]
fn command_invoke() -> Result<()> {
let wasm = build_wasm("tests/wasm/minimal-command.wat")?;
run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--invoke",
"_start",
"--disable-cache",
])?;
Ok(())
}
// Attempt to call invoke on a command.
#[test]
fn reactor_invoke() -> Result<()> {
let wasm = build_wasm("tests/wasm/minimal-reactor.wat")?;
run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--invoke",
"_initialize",
"--disable-cache",
])?;
Ok(())
}
// Run the greeter test, which runs a preloaded reactor and a command.
#[test]
fn greeter() -> Result<()> {
let wasm = build_wasm("tests/wasm/greeter_command.wat")?;
let stdout = run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--disable-cache",
"--preload",
"reactor=tests/wasm/greeter_reactor.wat",
])?;
assert_eq!(
stdout,
"Hello _initialize\nHello _start\nHello greet\nHello done\n"
);
Ok(())
}
// Run the greeter test, but this time preload a command.
#[test]
fn greeter_preload_command() -> Result<()> {
let wasm = build_wasm("tests/wasm/greeter_reactor.wat")?;
let stdout = run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--disable-cache",
"--preload",
"reactor=tests/wasm/hello_wasi_snapshot1.wat",
])?;
assert_eq!(stdout, "Hello _initialize\n");
Ok(())
}
// Run the greeter test, which runs a preloaded reactor and a command.
#[test]
fn greeter_preload_callable_command() -> Result<()> {
let wasm = build_wasm("tests/wasm/greeter_command.wat")?;
let stdout = run_wasmtime(&[
"run",
wasm.path().to_str().unwrap(),
"--disable-cache",
"--preload",
"reactor=tests/wasm/greeter_callable_command.wat",
])?;
assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n");
Ok(())
}

View File

@@ -0,0 +1,23 @@
;; Like greeter_reactor, but exports "_start" instead of "_initialize".
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $__wasi_fd_write (param i32 i32 i32 i32) (result i32)))
(func (export "_start")
(call $print (i32.const 32) (i32.const 22))
)
(func (export "greet")
(call $print (i32.const 64) (i32.const 21))
)
(func $print (param $ptr i32) (param $len i32)
(i32.store (i32.const 8) (local.get $len))
(i32.store (i32.const 4) (local.get $ptr))
(drop (call $__wasi_fd_write
(i32.const 1)
(i32.const 4)
(i32.const 1)
(i32.const 0)))
)
(memory (export "memory") 1)
(data (i32.const 32) "Hello callable _start\0a")
(data (i32.const 64) "Hello callable greet\0a")
)

View File

@@ -0,0 +1,22 @@
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $__wasi_fd_write (param i32 i32 i32 i32) (result i32)))
(import "reactor" "greet" (func $greet))
(func (export "_start")
(call $print (i32.const 32) (i32.const 13))
(call $greet)
(call $print (i32.const 64) (i32.const 11))
)
(func $print (param $ptr i32) (param $len i32)
(i32.store (i32.const 8) (local.get $len))
(i32.store (i32.const 4) (local.get $ptr))
(drop (call $__wasi_fd_write
(i32.const 1)
(i32.const 4)
(i32.const 1)
(i32.const 0)))
)
(memory (export "memory") 1)
(data (i32.const 32) "Hello _start\0a")
(data (i32.const 64) "Hello done\0a")
)

View File

@@ -0,0 +1,22 @@
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $__wasi_fd_write (param i32 i32 i32 i32) (result i32)))
(func (export "_initialize")
(call $print (i32.const 32) (i32.const 18))
)
(func (export "greet")
(call $print (i32.const 64) (i32.const 12))
)
(func $print (param $ptr i32) (param $len i32)
(i32.store (i32.const 8) (local.get $len))
(i32.store (i32.const 4) (local.get $ptr))
(drop (call $__wasi_fd_write
(i32.const 1)
(i32.const 4)
(i32.const 1)
(i32.const 0)))
)
(memory (export "memory") 1)
(data (i32.const 32) "Hello _initialize\0a")
(data (i32.const 64) "Hello greet\0a")
)

View File

@@ -0,0 +1,3 @@
(module
(func (export "_start"))
)

View File

@@ -0,0 +1,3 @@
(module
(func (export "_initialize"))
)