Skip to content

Commit

Permalink
Remove wasi_linker and clarify that Commands/Reactors aren't connec…
Browse files Browse the repository at this point in the history
…ted to WASI.
  • Loading branch information
sunfishcode committed May 21, 2020
1 parent 1b387d3 commit 87b2592
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 113 deletions.
8 changes: 4 additions & 4 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ Unreleased

### Added

* The [WASI commands and reactors ABI] is now supported. `Instance::new` and
`Linker::instantiate` now return a `NewInstance`; to perform initialization
and obtain the `Instance`, call `.start`, `.run_command`, or
* The [Commands and Reactors ABI] is now supported in the Rust API. `Instance::new`
and `Linker::instantiate` now return a `NewInstance`; to perform initialization
and obtain the `Instance`, call `.activate`, `.run_command`, or
`.init_reactor` on it as needed.

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

### Changed

Expand Down
2 changes: 1 addition & 1 deletion crates/c-api/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ pub fn handle_instantiate(
}

// Run the wasm start function.
let instance = instance.and_then(NewInstance::minimal_init);
let instance = instance.and_then(NewInstance::start);

match instance {
Ok(instance) => {
Expand Down
41 changes: 1 addition & 40 deletions crates/wasi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use anyhow::Result;
use std::fs::File;
use wasmtime::{Linker, Store, Trap};
use wasmtime::Trap;

pub mod old;

Expand Down Expand Up @@ -30,40 +28,3 @@ 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);

// Add the current snapshot to the linker.
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)?;

// Repeat the above, but this time for snapshot 0.
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)
}
86 changes: 43 additions & 43 deletions crates/wasmtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ impl Instance {
/// returned.
///
/// This method returns a `NewInstance`, which is an instance which has
/// been created, however it has not yet been initialized -- wasm and WASI
/// initialization functions that it may have have not been run yet. Use
/// the methods on `NewInstance` to run the initialization and return the
/// actual `Instance`.
/// been created, however it has not yet been initialized -- initialization
/// functions that it may have have not been run yet. Use the methods on
/// `NewInstance` to run the initialization and return the actual
/// `Instance`.
///
/// ## Providing Imports
///
Expand Down Expand Up @@ -240,12 +240,12 @@ pub struct NewInstance {
}

impl NewInstance {
/// Run the instance's wasm start function (and not WASI ABI initialization).
/// Run the instance's wasm start function (and not Command/Reactor initialization).
///
/// This is public as it's used by the C API, which doesn't expose the `NewInstance`
/// type and needs a way to internally run initialization on a plain `Instance`.
#[doc(hidden)]
pub fn minimal_init(self) -> Result<Instance> {
pub fn start(self) -> Result<Instance> {
let start_func = self.instance.handle.module().start_func;
let instance = self.instance;
let store = instance.store();
Expand Down Expand Up @@ -274,80 +274,80 @@ impl NewInstance {
Ok(instance)
}

/// Run the instance's wasm start function and, if applicable, perform
/// [WASI ABI initialization]:
/// - If the module is a command, the `_start` function is run and `None`
/// Run the instance's wasm start function and:
/// - 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.
/// - If the module is a Reactor, if an `_initialize` function is present,
/// it is run and the initialized `Instance` is returned.
///
/// If you know you're expecting to run a command or a reactor specifically,
/// See [the documentation for Commands and Reactors] for details on the ABI.
///
/// If you know you're expecting to run a Command or a Reactor specifically,
/// you can use `run_command` or `init_reactor` instead, which offer a
/// more streamlined API.
///
/// For now, `params` must be an empty slice, and the results will always be empty.
/// In the future, this will be extended to support arguments and return values.
///
/// [WASI ABI initialization]: https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi
pub fn start(self, params: &[Val]) -> Result<Started> {
let instance = self.minimal_init()?;
/// [the documentation for Commands and Reactors]: https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi
pub fn activate(self, params: &[Val]) -> Result<Activated> {
let instance = self.start()?;

match wasi_abi_exec_model(instance)? {
ExecModel::Command(func) => Ok(Started::Command(run_command(func, params)?)),
ExecModel::Reactor((maybe_func, instance)) => Ok(Started::Reactor(init_reactor(
match exec_model(instance)? {
ExecModel::Command(func) => Ok(Activated::Command(run_command(func, params)?)),
ExecModel::Reactor((maybe_func, instance)) => Ok(Activated::Reactor(init_reactor(
maybe_func, params, instance,
)?)),
}
}

/// Given a command instance, run it. If the instance is not a command,
/// Given a Command instance, run it. If the instance is not a Command,
/// return an error.
///
/// A command is an instance which is expected to be called only once.
/// A Command is an instance which is expected to be called only once.
/// Accordingly, this function consumes the `Instance`.
pub fn run_command(self, params: &[Val]) -> Result<Box<[Val]>> {
let instance = self.minimal_init()?;
let instance = self.start()?;

if let ExecModel::Command(func) = wasi_abi_exec_model(instance)? {
if let ExecModel::Command(func) = exec_model(instance)? {
return run_command(func, params);
}

bail!("`run_command` called on module which is not a command");
}

/// Given a reactor instance, initialize it. If the instance is not a reactor,
/// Given a Reactor instance, initialize it. If the instance is not a Reactor,
/// return an error.
///
/// A reactor is an instance which is expected to be called any number of
/// A Reactor is an instance which is expected to be called any number of
/// times. Accordingly, this function returns the initialized `Instance`
/// so that its exports can be called.
pub fn init_reactor(self, params: &[Val]) -> Result<Instance> {
let instance = self.minimal_init()?;
let instance = self.start()?;

if let ExecModel::Reactor((maybe_func, instance)) = wasi_abi_exec_model(instance)? {
if let ExecModel::Reactor((maybe_func, instance)) = exec_model(instance)? {
return init_reactor(maybe_func, params, instance);
}

bail!("`init_reactor` called on module which is not a reactor");
}
}

/// Modules can be interpreted either as commands (instance lifetime ends
/// when the start function returns) or reactor (instance persists).
/// Modules can be interpreted either as Commands (instance lifetime ends
/// when the start function returns) or Reactor (instance persists).
enum ExecModel {
/// The instance is a command, and this is its start function. The
/// The instance is a Command, and this is its start function. The
/// instance should be consumed.
Command(Func),

/// The instance is a reactor, and this is its initialization function,
/// The instance is a Reactor, and this is its initialization function,
/// along with the instance itself, which should persist.
Reactor((Option<Func>, Instance)),
}

/// Classify the given instance as either a command or reactor and return
/// Classify the given instance as either a Command or Reactor and return
/// the information needed to initialize it.
fn wasi_abi_exec_model(instance: Instance) -> Result<ExecModel> {
// Invoke the WASI start function of the instance, if one is present.
fn exec_model(instance: Instance) -> Result<ExecModel> {
let command_start = instance.get_export("_start");
let reactor_start = instance.get_export("_initialize");
match (command_start, reactor_start) {
Expand All @@ -371,37 +371,37 @@ fn wasi_abi_exec_model(instance: Instance) -> Result<ExecModel> {
Ok(ExecModel::Reactor((None, instance)))
}
(Some(_), Some(_)) => {
// Module declares itself to be both a command and a reactor.
bail!("Program cannot be both a command and a reactor")
// Module declares itself to be both a Command and a Reactor.
bail!("Program cannot be both a Command and a Reactor")
}
}
}

/// The result of running WASI ABI initialization on a wasm module.
pub enum Started {
/// The module was a command; the instance was consumed and this `Started`
/// The result of a successful `Instance::activate`.
pub enum Activated {
/// The module was a Command; the instance was consumed and this `Activated`
/// holds the return values.
Command(Box<[Val]>),

/// The module was a reactor; this `Started` holds the instance.
/// The module was a Reactor; this `Activated` holds the instance.
Reactor(Instance),
}

/// Utility for running commands.
/// Utility for running Commands.
fn run_command(func: Func, params: &[Val]) -> Result<Box<[Val]>> {
if !params.is_empty() {
bail!("passing arguments to a WASI-ABI command is not supported yet");
bail!("passing arguments to a Command is not supported yet");
}

func.get0::<()>()?()?;

return Ok(Vec::new().into_boxed_slice());
}

/// Utility for initializing reactors.
/// Utility for initializing Reactors.
fn init_reactor(maybe_func: Option<Func>, params: &[Val], instance: Instance) -> Result<Instance> {
if !params.is_empty() {
bail!("passing arguments to a WASI-ABI reactor is not supported yet");
bail!("passing arguments to a Reactor's `init_reactor` is not supported yet");
}

if let Some(func) = maybe_func {
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ mod values;
pub use crate::externals::*;
pub use crate::frame_info::FrameInfo;
pub use crate::func::*;
pub use crate::instance::{Instance, NewInstance, Started};
pub use crate::instance::{Activated, Instance, NewInstance};
pub use crate::linker::*;
pub use crate::module::Module;
pub use crate::r#ref::{ExternRef, HostRef};
Expand Down
3 changes: 0 additions & 3 deletions crates/wasmtime/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ 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
/// `wasmtime_wasi::wasi_linker`.
///
/// # Examples
///
/// ```
Expand Down
27 changes: 21 additions & 6 deletions examples/wasi/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,33 @@

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

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

// Create a new `Linker` with no preloaded directories, command-line arguments,
// or environment variables.
let linker = wasi_linker(&store, &[], &[], &[])?;
// Create an instance of `Wasi` which contains a `WasiCtx`. Note that
// `WasiCtx` provides a number of ways to configure what the target program
// will have access to.
let wasi = Wasi::new(&store, WasiCtx::new(std::env::args())?);
let mut imports = Vec::new();
for import in module.imports() {
if import.module() == "wasi_snapshot_preview1" {
if let Some(export) = wasi.get_export(import.name()) {
imports.push(Extern::from(export.clone()));
continue;
}
}
panic!(
"couldn't find import for `{}::{}`",
import.module(),
import.name()
);
}

// Instantiate and run our module with the imports we've created.
let _instance = linker.instantiate(&module)?.run_command(&[])?;
// Instance our module with the imports we've created, and run it.
let _returns = Instance::new(&module, &imports)?.run_command(&[])?;

Ok(())
}
Loading

0 comments on commit 87b2592

Please sign in to comment.