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:
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user