Skip to content

Commit

Permalink
Reactor support.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sunfishcode committed May 15, 2020
1 parent 01f46d0 commit 1f37f74
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 141 deletions.
7 changes: 7 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Unreleased

### Added

* The [WASI commands and reactors ABI] is now supported. To use, create a
`Linker` instance with `wasmtime_wasi::wasi_linker`, and instantiate modules
with `Linker::instantiate_wasi_abi`. This will automatically run commands
and automatically initialize reactors.

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

### Changed

### Fixed
Expand Down
4 changes: 0 additions & 4 deletions crates/runtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,8 +1330,4 @@ pub enum InstantiationError {
/// A trap ocurred during instantiation, after linking.
#[error("Trap occurred during instantiation")]
Trap(Trap),

/// A trap occurred while running the wasm start function.
#[error("Trap occurred while invoking start function")]
StartTrap(Trap),
}
11 changes: 4 additions & 7 deletions crates/test-programs/tests/wasm_tests/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,14 @@ pub fn instantiate(
})
.collect::<Result<Vec<_>, _>>()?;

let instance = Instance::new(&module, &imports).context(format!(
let instance = Instance::new_wasi_abi(&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(&[])?;
if instance.is_some() {
bail!("expected module to be a command with a \"_start\" function")
}

Ok(())
}
Expand Down
2 changes: 2 additions & 0 deletions crates/wasi-common/wig/src/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ pub fn define_struct(args: TokenStream) -> TokenStream {
let memory = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
log::warn!("callee does not export a memory as \"memory\"");
let e = wasi_common::old::snapshot_0::wasi::__WASI_ERRNO_INVAL;
#handle_early_error
}
Expand Down Expand Up @@ -463,6 +464,7 @@ pub fn define_struct_for_wiggle(args: TokenStream) -> TokenStream {
let mem = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
log::warn!("callee does not export a memory as \"memory\"");
let e = wasi_common::wasi::Errno::Inval;
#handle_early_error
}
Expand Down
39 changes: 38 additions & 1 deletion crates/wasi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use wasmtime::Trap;
use anyhow::Result;
use std::fs::File;
use wasmtime::{Linker, Store, Trap};

pub mod old;

Expand Down Expand Up @@ -28,3 +30,38 @@ fn wasi_proc_exit(status: i32) -> Result<(), Trap> {
))
}
}

/// Creates a new [`Linker`], similar to `Linker::new`, and initializes it
/// with WASI exports.
pub fn wasi_linker(
store: &Store,
preopen_dirs: &[(String, File)],
argv: &[String],
vars: &[(String, String)],
) -> Result<Linker> {
let mut linker = Linker::new(store);

let mut cx = 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 = Wasi::new(linker.store(), cx);
wasi.add_to_linker(&mut linker)?;

let mut cx = 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 = old::snapshot_0::Wasi::new(linker.store(), cx);
wasi.add_to_linker(&mut linker)?;

Ok(linker)
}
56 changes: 53 additions & 3 deletions crates/wasmtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ 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(),
}
})?;
Expand Down Expand Up @@ -101,6 +99,54 @@ pub struct Instance {
}

impl Instance {
/// Creates a new [`Instance`] from the previously compiled [`Module`] and
/// list of `imports` specified, similar to `Instance::new`, and performs
/// [WASI ABI initialization]:
/// - If the module is a command, the `_start` function is run and `None`
/// is returned.
/// - If the module is a reactor, the `_initialize` function is run and
/// the initialized `Instance` is returned.
///
/// [WASI ABI initialization]: https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi
pub fn new_wasi_abi(module: &Module, imports: &[Extern]) -> Result<Option<Instance>, Error> {
let instance = Instance::new(module, imports)?;

// Invoke the WASI start function of the instance, if one is present.
let command_start = instance.get_export("_start");
let reactor_start = instance.get_export("_initialize");
match (command_start, reactor_start) {
(Some(command_start), None) => {
if let Some(func) = command_start.into_func() {
func.get0::<()>()?()?;

// For commands, we consume the instance after running the program.
Ok(None)
} else {
bail!("_start must be a function".to_owned())
}
}
(None, Some(reactor_start)) => {
if let Some(func) = reactor_start.into_func() {
func.get0::<()>()?()?;

// For reactors, we return the instance after running the initialization.
Ok(Some(instance))
} else {
bail!("_initialize must be a function".to_owned())
}
}
(None, None) => {
// Treat modules which don't declare themselves as commands or reactors as
// reactors which have no initialization to do.
Ok(Some(instance))
}
(Some(_), Some(_)) => {
// Module declares to be both a command and a reactor.
bail!("Program cannot be both a command and a reactor".to_owned())
}
}
}

/// Creates a new [`Instance`] from the previously compiled [`Module`] and
/// list of `imports` specified.
///
Expand All @@ -111,6 +157,10 @@ impl Instance {
/// automatically run (if provided) and then the [`Instance`] will be
/// returned.
///
/// Note that this function does not perform `WASI` ABI initialization
/// (eg. it does not run the `_start` or `_initialize` functions). To
/// perform them, use `new_wasi_abi` instead.
///
/// ## Providing Imports
///
/// The `imports` array here is a bit tricky. The entries in the list of
Expand Down
26 changes: 25 additions & 1 deletion crates/wasmtime/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ impl Linker {
/// linker will be connected with `store` and must come from the same
/// `store`.
///
/// To create a new [`Linker`] prepopulated with WASI APIs, see
/// [`wasi_linker`](wasmtime_wasi::wasi_linker).
///
/// # Examples
///
/// ```
Expand Down Expand Up @@ -338,6 +341,20 @@ impl Linker {
idx
}

/// Attempts to instantiate the `module` provided, similar to
/// `Linker::instantiate`, and performs [WASI ABI initialization]:
/// - If the module is a command, the `_start` function is run and `None`
/// is returned.
/// - If the module is a reactor, the `_initialize` function is run and
/// the initialized `Instance` is returned.
///
/// [WASI ABI initialization]: https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi
pub fn instantiate_wasi_abi(&self, module: &Module) -> Result<Option<Instance>> {
let imports = self.compute_imports(module)?;

Instance::new_wasi_abi(module, &imports)
}

/// Attempts to instantiate the `module` provided.
///
/// This method will attempt to assemble a list of imports that correspond
Expand Down Expand Up @@ -376,7 +393,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);
Expand Down Expand Up @@ -413,7 +437,7 @@ impl Linker {
)
}

Instance::new(module, &imports)
Ok(imports)
}

/// Returns the [`Store`] that this linker is connected to.
Expand Down
32 changes: 7 additions & 25 deletions examples/wasi/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,18 @@

use anyhow::Result;
use wasmtime::*;
use wasmtime_wasi::{Wasi, WasiCtx};
use wasmtime_wasi::wasi_linker;

fn main() -> Result<()> {
let store = Store::default();
let module = Module::from_file(&store, "target/wasm32-wasi/debug/wasi.wasm")?;

// 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()
);
}
// Create a new `Linker` with no preloaded directories, command-line arguments,
// or environment variables.
let mut linker = wasi_linker(&store, &[], &[], &[])?;

// Instanciate and run our module with the imports we've created.
let _instance = linker.instantiate_wasi_abi(&module)?;

// 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(())
}
Loading

0 comments on commit 1f37f74

Please sign in to comment.