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:
@@ -8,7 +8,10 @@ Unreleased
|
|||||||
|
|
||||||
### Added
|
### 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
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ WASM_API_EXTERN own wasmtime_error_t* wasmtime_linker_instantiate(
|
|||||||
own wasm_trap_t **trap
|
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
|
// wasmtime_caller_t extension, binding the `Caller` type in the Rust API
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::{bad_utf8, handle_result, wasmtime_error_t};
|
use crate::{bad_utf8, handle_result, wasmtime_error_t};
|
||||||
use crate::{wasm_extern_t, wasm_store_t, ExternHost};
|
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 std::str;
|
||||||
use wasmtime::{Extern, Linker};
|
use wasmtime::{Extern, HostRef, Linker};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct wasmtime_linker_t {
|
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());
|
let result = linker.linker.instantiate(&module.module.borrow());
|
||||||
super::instance::handle_instantiate(result, instance_ptr, trap_ptr)
|
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()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1330,8 +1330,4 @@ pub enum InstantiationError {
|
|||||||
/// A trap ocurred during instantiation, after linking.
|
/// A trap ocurred during instantiation, after linking.
|
||||||
#[error("Trap occurred during instantiation")]
|
#[error("Trap occurred during instantiation")]
|
||||||
Trap(Trap),
|
Trap(Trap),
|
||||||
|
|
||||||
/// A trap occurred while running the wasm start function.
|
|
||||||
#[error("Trap occurred while invoking start function")]
|
|
||||||
StartTrap(Trap),
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use anyhow::{bail, Context};
|
use anyhow::Context;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use wasi_common::VirtualDirEntry;
|
use wasi_common::VirtualDirEntry;
|
||||||
use wasmtime::{Instance, Module, Store};
|
use wasmtime::{Linker, Module, Store};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum PreopenType {
|
pub enum PreopenType {
|
||||||
@@ -48,36 +48,19 @@ pub fn instantiate(
|
|||||||
let (reader, _writer) = os_pipe::pipe()?;
|
let (reader, _writer) = os_pipe::pipe()?;
|
||||||
builder.stdin(reader_to_file(reader));
|
builder.stdin(reader_to_file(reader));
|
||||||
let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?);
|
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 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!(
|
linker
|
||||||
"error while instantiating Wasm module '{}'",
|
.module("", &module)
|
||||||
bin_name,
|
.and_then(|m| m.get_default(""))
|
||||||
))?;
|
.and_then(|f| f.get0::<()>())
|
||||||
|
.and_then(|f| f().map_err(Into::into))
|
||||||
instance
|
.context(format!("error while testing Wasm module '{}'", bin_name,))
|
||||||
.get_export("_start")
|
|
||||||
.context("expected a _start export")?
|
|
||||||
.into_func()
|
|
||||||
.context("expected export to be a func")?
|
|
||||||
.call(&[])?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
|
|||||||
let memory = match caller.get_export("memory") {
|
let memory = match caller.get_export("memory") {
|
||||||
Some(wasmtime::Extern::Memory(m)) => m,
|
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;
|
let e = wasi_common::old::snapshot_0::wasi::__WASI_ERRNO_INVAL;
|
||||||
#handle_early_error
|
#handle_early_error
|
||||||
}
|
}
|
||||||
@@ -463,6 +464,7 @@ pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream {
|
|||||||
let mem = match caller.get_export("memory") {
|
let mem = match caller.get_export("memory") {
|
||||||
Some(wasmtime::Extern::Memory(m)) => m,
|
Some(wasmtime::Extern::Memory(m)) => m,
|
||||||
_ => {
|
_ => {
|
||||||
|
log::warn!("callee does not export a memory as \"memory\"");
|
||||||
let e = wasi_common::wasi::Errno::Inval;
|
let e = wasi_common::wasi::Errno::Inval;
|
||||||
#handle_early_error
|
#handle_early_error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,25 @@ fn instantiate(
|
|||||||
sig_registry: &SignatureRegistry,
|
sig_registry: &SignatureRegistry,
|
||||||
host: Box<dyn Any>,
|
host: Box<dyn Any>,
|
||||||
) -> Result<StoreInstanceHandle, Error> {
|
) -> 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 };
|
let mut resolver = SimpleResolver { imports };
|
||||||
unsafe {
|
let config = store.engine().config();
|
||||||
let config = store.engine().config();
|
let instance = unsafe {
|
||||||
let instance = compiled_module.instantiate(
|
let instance = compiled_module.instantiate(
|
||||||
&mut resolver,
|
&mut resolver,
|
||||||
sig_registry,
|
sig_registry,
|
||||||
@@ -51,31 +67,38 @@ fn instantiate(
|
|||||||
)
|
)
|
||||||
.map_err(|e| -> Error {
|
.map_err(|e| -> Error {
|
||||||
match e {
|
match e {
|
||||||
InstantiationError::StartTrap(trap) | InstantiationError::Trap(trap) => {
|
InstantiationError::Trap(trap) => Trap::from_runtime(trap).into(),
|
||||||
Trap::from_runtime(trap).into()
|
|
||||||
}
|
|
||||||
other => other.into(),
|
other => other.into(),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// If a start function is present, now that we've got our compiled
|
instance
|
||||||
// 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 start_func = instance.handle.module().start_func;
|
||||||
let f = match instance.lookup_by_declaration(&EntityIndex::Function(start)) {
|
|
||||||
wasmtime_runtime::Export::Function(f) => f,
|
// If a start function is present, invoke it. Make sure we use all the
|
||||||
_ => unreachable!(), // valid modules shouldn't hit this
|
// trap-handling configuration in `store` as well.
|
||||||
};
|
if let Some(start) = start_func {
|
||||||
super::func::catch_traps(instance.vmctx_ptr(), store, || {
|
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::<
|
mem::transmute::<
|
||||||
*const VMFunctionBody,
|
*const VMFunctionBody,
|
||||||
unsafe extern "C" fn(*mut VMContext, *mut VMContext),
|
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.
|
/// An instantiated WebAssembly module.
|
||||||
@@ -111,6 +134,15 @@ impl Instance {
|
|||||||
/// automatically run (if provided) and then the [`Instance`] will be
|
/// automatically run (if provided) and then the [`Instance`] will be
|
||||||
/// returned.
|
/// 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
|
/// ## Providing Imports
|
||||||
///
|
///
|
||||||
/// The `imports` array here is a bit tricky. The entries in the list of
|
/// The `imports` array here is a bit tricky. The entries in the list of
|
||||||
@@ -147,23 +179,6 @@ impl Instance {
|
|||||||
/// [`ExternType`]: crate::ExternType
|
/// [`ExternType`]: crate::ExternType
|
||||||
pub fn new(module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
|
pub fn new(module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
|
||||||
let store = module.store();
|
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 info = module.register_frame_info();
|
||||||
let handle = instantiate(
|
let handle = instantiate(
|
||||||
store,
|
store,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Extern, ExternType, Func, FuncType, GlobalType, ImportType, Instance, IntoFunc, Module, Store,
|
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::collections::hash_map::{Entry, HashMap};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -270,6 +271,187 @@ impl Linker {
|
|||||||
Ok(self)
|
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.
|
/// Aliases one module's name as another.
|
||||||
///
|
///
|
||||||
/// This method will alias all currently defined under `module` to also be
|
/// 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
|
/// incorrect signature or if it was not prevoiusly defined then an error
|
||||||
/// will be returned because the import can not be satisfied.
|
/// 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
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This method can fail because an import may not be found, or because
|
/// 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> {
|
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();
|
let mut imports = Vec::new();
|
||||||
|
|
||||||
for import in module.imports() {
|
for import in module.imports() {
|
||||||
if let Some(item) = self.get(&import) {
|
if let Some(item) = self.get(&import) {
|
||||||
imports.push(item);
|
imports.push(item);
|
||||||
@@ -413,7 +606,7 @@ impl Linker {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance::new(module, &imports)
|
Ok(imports)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`Store`] that this linker is connected to.
|
/// Returns the [`Store`] that this linker is connected to.
|
||||||
@@ -485,4 +678,85 @@ impl Linker {
|
|||||||
}
|
}
|
||||||
Ok(ret.clone())
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::frame_info::GlobalFrameInfoRegistration;
|
use crate::frame_info::GlobalFrameInfoRegistration;
|
||||||
use crate::runtime::Store;
|
use crate::runtime::Store;
|
||||||
use crate::types::{EntityType, ExportType, ImportType};
|
use crate::types::{EntityType, ExportType, ExternType, ImportType};
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Mutex};
|
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.
|
/// Returns the [`Store`] that this [`Module`] was compiled into.
|
||||||
pub fn store(&self) -> &Store {
|
pub fn store(&self) -> &Store {
|
||||||
&self.inner.store
|
&self.inner.store
|
||||||
|
|||||||
@@ -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 {
|
match self {
|
||||||
EntityType::Function(sig) => FuncType::from_wasmtime_signature(sig)
|
EntityType::Function(sig) => FuncType::from_wasmtime_signature(sig)
|
||||||
.expect("core wasm function type should be supported")
|
.expect("core wasm function type should be supported")
|
||||||
|
|||||||
@@ -72,51 +72,30 @@ int main() {
|
|||||||
if (wasi == NULL)
|
if (wasi == NULL)
|
||||||
exit_with_error("failed to instantiate WASI", NULL, trap);
|
exit_with_error("failed to instantiate WASI", NULL, trap);
|
||||||
|
|
||||||
// Create import list for our module using wasi
|
wasmtime_linker_t *linker = wasmtime_linker_new(store);
|
||||||
wasm_importtype_vec_t import_types;
|
error = wasmtime_linker_define_wasi(linker, wasi);
|
||||||
wasm_module_imports(module, &import_types);
|
if (error != NULL)
|
||||||
const wasm_extern_t **imports = calloc(import_types.size, sizeof(void*));
|
exit_with_error("failed to link wasi", error, NULL);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate the module
|
// Instantiate the module
|
||||||
|
wasm_name_t empty;
|
||||||
|
wasm_name_new_from_string(&empty, "");
|
||||||
wasm_instance_t *instance = NULL;
|
wasm_instance_t *instance = NULL;
|
||||||
error = wasmtime_instance_new(module, imports, import_types.size, &instance, &trap);
|
error = wasmtime_linker_module(linker, &empty, module);
|
||||||
if (instance == NULL)
|
if (error != NULL)
|
||||||
exit_with_error("failed to instantiate", error, trap);
|
exit_with_error("failed to instantiate module", error, NULL);
|
||||||
free(imports);
|
|
||||||
wasm_importtype_vec_delete(&import_types);
|
|
||||||
|
|
||||||
// Lookup our `_start` export function
|
// Run it.
|
||||||
wasm_extern_vec_t externs;
|
wasm_func_t* func;
|
||||||
wasm_instance_exports(instance, &externs);
|
wasmtime_linker_get_default(linker, &empty, &func);
|
||||||
wasm_exporttype_vec_t exports;
|
if (error != NULL)
|
||||||
wasm_module_exports(module, &exports);
|
exit_with_error("failed to locate default export for module", error, NULL);
|
||||||
wasm_extern_t *start_extern = NULL;
|
error = wasmtime_func_call(func, NULL, 0, NULL, 0, &trap);
|
||||||
for (int i = 0; i < exports.size; i++) {
|
if (error != NULL)
|
||||||
const wasm_name_t *name = wasm_exporttype_name(exports.data[i]);
|
exit_with_error("error calling default export", error, trap);
|
||||||
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);
|
|
||||||
|
|
||||||
// Clean up after ourselves at this point
|
// Clean up after ourselves at this point
|
||||||
wasm_exporttype_vec_delete(&exports);
|
wasm_name_delete(&empty);
|
||||||
wasm_extern_vec_delete(&externs);
|
|
||||||
wasm_instance_delete(instance);
|
|
||||||
wasm_module_delete(module);
|
wasm_module_delete(module);
|
||||||
wasm_store_delete(store);
|
wasm_store_delete(store);
|
||||||
wasm_engine_delete(engine);
|
wasm_engine_delete(engine);
|
||||||
|
|||||||
@@ -9,32 +9,18 @@ use wasmtime_wasi::{Wasi, WasiCtx};
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let store = Store::default();
|
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
|
// Create an instance of `Wasi` which contains a `WasiCtx`. Note that
|
||||||
// `WasiCtx` provides a number of ways to configure what the target program
|
// `WasiCtx` provides a number of ways to configure what the target program
|
||||||
// will have access to.
|
// will have access to.
|
||||||
let wasi = Wasi::new(&store, WasiCtx::new(std::env::args())?);
|
let wasi = Wasi::new(&store, WasiCtx::new(std::env::args())?);
|
||||||
let mut imports = Vec::new();
|
wasi.add_to_linker(&mut linker)?;
|
||||||
for import in module.imports() {
|
|
||||||
if import.module() == "wasi_snapshot_preview1" {
|
// Instantiate our module with the imports we've created, and run it.
|
||||||
if let Some(export) = wasi.get_export(import.name()) {
|
let module = Module::from_file(&store, "target/wasm32-wasi/debug/wasi.wasm")?;
|
||||||
imports.push(Extern::from(export.clone()));
|
linker.module("", &module)?;
|
||||||
continue;
|
linker.get_default("")?.get0::<()>()?()?;
|
||||||
}
|
|
||||||
}
|
|
||||||
panic!(
|
|
||||||
"couldn't find import for `{}::{}`",
|
|
||||||
import.module(),
|
|
||||||
import.name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ use std::time::Duration;
|
|||||||
use std::{
|
use std::{
|
||||||
ffi::{OsStr, OsString},
|
ffi::{OsStr, OsString},
|
||||||
fs::File,
|
fs::File,
|
||||||
path::{Component, Path, PathBuf},
|
path::{Component, PathBuf},
|
||||||
process,
|
process,
|
||||||
};
|
};
|
||||||
use structopt::{clap::AppSettings, StructOpt};
|
use structopt::{clap::AppSettings, StructOpt};
|
||||||
use wasi_common::preopen_dir;
|
use wasi_common::{preopen_dir, WasiCtxBuilder};
|
||||||
use wasmtime::{Engine, Instance, Module, Store, Trap, Val, ValType};
|
use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType};
|
||||||
use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi};
|
use wasmtime_wasi::Wasi;
|
||||||
|
|
||||||
fn parse_module(s: &OsStr) -> Result<PathBuf, OsString> {
|
fn parse_module(s: &OsStr) -> Result<PathBuf, OsString> {
|
||||||
// Do not accept wasmtime subcommand names as the module name
|
// Do not accept wasmtime subcommand names as the module name
|
||||||
@@ -51,6 +51,14 @@ fn parse_dur(s: &str) -> Result<Duration> {
|
|||||||
Ok(dur)
|
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
|
/// Runs a WebAssembly module
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
#[structopt(name = "run", setting = AppSettings::TrailingVarArg)]
|
#[structopt(name = "run", setting = AppSettings::TrailingVarArg)]
|
||||||
@@ -87,10 +95,10 @@ pub struct RunCommand {
|
|||||||
#[structopt(
|
#[structopt(
|
||||||
long = "preload",
|
long = "preload",
|
||||||
number_of_values = 1,
|
number_of_values = 1,
|
||||||
value_name = "MODULE_PATH",
|
value_name = "NAME=MODULE_PATH",
|
||||||
parse(from_os_str)
|
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)
|
/// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc)
|
||||||
#[structopt(
|
#[structopt(
|
||||||
@@ -127,17 +135,25 @@ impl RunCommand {
|
|||||||
let preopen_dirs = self.compute_preopen_dirs()?;
|
let preopen_dirs = self.compute_preopen_dirs()?;
|
||||||
let argv = self.compute_argv();
|
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.
|
// Load the preload wasm modules.
|
||||||
for preload in self.preloads.iter() {
|
for (name, path) in self.preloads.iter() {
|
||||||
Self::instantiate_module(&store, &module_registry, preload)
|
// Read the wasm module binary either as `*.wat` or a raw binary
|
||||||
.with_context(|| format!("failed to process preload at `{}`", preload.display()))?;
|
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.
|
// Load the main wasm module.
|
||||||
match self
|
match self
|
||||||
.handle_module(&store, &module_registry)
|
.load_main_module(&mut linker)
|
||||||
.with_context(|| format!("failed to run main module `{}`", self.module.display()))
|
.with_context(|| format!("failed to run main module `{}`", self.module.display()))
|
||||||
{
|
{
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
@@ -220,79 +236,40 @@ impl RunCommand {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instantiate_module(
|
fn load_main_module(&self, linker: &mut Linker) -> Result<()> {
|
||||||
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<()> {
|
|
||||||
if let Some(timeout) = self.wasm_timeout {
|
if let Some(timeout) = self.wasm_timeout {
|
||||||
let handle = store.interrupt_handle()?;
|
let handle = linker.store().interrupt_handle()?;
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
thread::sleep(timeout);
|
thread::sleep(timeout);
|
||||||
handle.interrupt();
|
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 a function to invoke was given, invoke it.
|
||||||
if let Some(name) = self.invoke.as_ref() {
|
if let Some(name) = self.invoke.as_ref() {
|
||||||
self.invoke_export(instance, name)?;
|
self.invoke_export(linker, name)
|
||||||
} else if instance.exports().any(|export| export.name().is_empty()) {
|
|
||||||
// Launch the default command export.
|
|
||||||
self.invoke_export(instance, "")?;
|
|
||||||
} else {
|
} else {
|
||||||
// If the module doesn't have a default command export, launch the
|
let func = linker.get_default("")?;
|
||||||
// _start function if one is present, as a compatibility measure.
|
self.invoke_func(func, None)
|
||||||
self.invoke_export(instance, "_start")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invoke_export(&self, instance: Instance, name: &str) -> Result<()> {
|
fn invoke_export(&self, linker: &Linker, name: &str) -> Result<()> {
|
||||||
let func = if let Some(export) = instance.get_export(name) {
|
let func = match linker.get_one_by_name("", name)?.into_func() {
|
||||||
if let Some(func) = export.into_func() {
|
Some(func) => func,
|
||||||
func
|
None => bail!("export of `{}` wasn't a function", name),
|
||||||
} else {
|
|
||||||
bail!("export of `{}` wasn't a function", name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!("failed to find export of `{}` in module", name)
|
|
||||||
};
|
};
|
||||||
|
self.invoke_func(func, Some(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke_func(&self, func: Func, name: Option<&str>) -> Result<()> {
|
||||||
let ty = func.ty();
|
let ty = func.ty();
|
||||||
if ty.params().len() > 0 {
|
if ty.params().len() > 0 {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
@@ -305,7 +282,13 @@ impl RunCommand {
|
|||||||
for ty in ty.params() {
|
for ty in ty.params() {
|
||||||
let val = match args.next() {
|
let val = match args.next() {
|
||||||
Some(s) => s,
|
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 {
|
values.push(match ty {
|
||||||
// TODO: integer parsing here should handle hexadecimal notation
|
// 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
|
// Invoke the function and then afterwards print all the results that came
|
||||||
// out, if there are any.
|
// out, if there are any.
|
||||||
let results = func
|
let results = func.call(&values).with_context(|| {
|
||||||
.call(&values)
|
if let Some(name) = name {
|
||||||
.with_context(|| format!("failed to invoke `{}`", name))?;
|
format!("failed to invoke `{}`", name)
|
||||||
|
} else {
|
||||||
|
format!("failed to invoke command default")
|
||||||
|
}
|
||||||
|
})?;
|
||||||
if !results.is_empty() {
|
if !results.is_empty() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"warning: using `--invoke` with a function that returns values \
|
"warning: using `--invoke` with a function that returns values \
|
||||||
@@ -347,41 +334,36 @@ impl RunCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ModuleRegistry {
|
/// Populates the given `Linker` with WASI APIs.
|
||||||
wasi_snapshot_preview1: Wasi,
|
fn populate_with_wasi(
|
||||||
wasi_unstable: WasiSnapshot0,
|
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 {
|
for (name, file) in preopen_dirs {
|
||||||
fn new(
|
cx.preopened_dir(file.try_clone()?, name);
|
||||||
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),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,3 +256,97 @@ fn exit126_wasi_snapshot1() -> Result<()> {
|
|||||||
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
|
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
23
tests/wasm/greeter_callable_command.wat
Normal file
23
tests/wasm/greeter_callable_command.wat
Normal 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")
|
||||||
|
)
|
||||||
22
tests/wasm/greeter_command.wat
Normal file
22
tests/wasm/greeter_command.wat
Normal 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")
|
||||||
|
)
|
||||||
22
tests/wasm/greeter_reactor.wat
Normal file
22
tests/wasm/greeter_reactor.wat
Normal 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")
|
||||||
|
)
|
||||||
3
tests/wasm/minimal-command.wat
Normal file
3
tests/wasm/minimal-command.wat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
(module
|
||||||
|
(func (export "_start"))
|
||||||
|
)
|
||||||
3
tests/wasm/minimal-reactor.wat
Normal file
3
tests/wasm/minimal-reactor.wat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
(module
|
||||||
|
(func (export "_initialize"))
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user