Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp wasi example and related docs #9788

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ members = [
"crates/wasi-preview1-component-adapter",
"crates/wasi-preview1-component-adapter/verify",
"examples/fib-debug/wasm",
"examples/wasi/wasm",
"examples/wasm",
"examples/tokio/wasm",
"examples/component/wasm",
"examples/min-platform",
Expand Down
2 changes: 1 addition & 1 deletion docs/examples-c-wasi.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This example shows off how to instantiate a wasm module using WASI imports.
## Wasm Source code

```rust,ignore
{{#include ../examples/wasi/wasm/wasi.rs}}
{{#include ../examples/wasm/wasi.rs}}
```


Expand Down
90 changes: 25 additions & 65 deletions docs/examples-rust-wasi.md
Original file line number Diff line number Diff line change
@@ -1,101 +1,61 @@
# WASI
# WASIp2

You can also [browse this source code online][code] and clone the wasmtime
repository to run the example locally.

[code]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi/main.rs

This example shows how to use the [`wasi-common`] crate to define WASI
This example shows how to use the [`wasmtime-wasi`] crate to define WASI
functions within a [`Linker`] which can then be used to instantiate a
WebAssembly module.
WebAssembly component.

[`wasi-common`]: https://crates.io/crates/wasi-common
[`wasmtime-wasi`]: https://crates.io/crates/wasmtime-wasi
[`Linker`]: https://docs.rs/wasmtime/*/wasmtime/struct.Linker.html

### WebAssembly module source code
## WebAssembly Component Source Code

For this WASI example, this Hello World program is compiled to a WebAssembly module using the WASI Preview 1 API.
For this WASI example, this Hello World program is compiled to a WebAssembly component using the WASIp2 API.

`wasi.rs`
```rust
{{#include ../examples/wasi/wasm/wasi.rs}}
{{#include ../examples/wasm/wasi.rs}}
```

Building this program generates `target/wasm32-wasip1/debug/wasi.wasm`, used below.
> Building instructions:
> 1. Have Rust installed
> 2. Add WASIp2 target if you haven't already: `rustup target add wasm32-wasip2`
> 3. `cargo build --target wasm32-wasip2`

### Invoke the WASM module
Building this program generates `target/wasm32-wasip2/debug/wasi.wasm`, used below.

This example shows adding and configuring the WASI imports to invoke the above WASM module.
### Invoke the WASM component

This example shows adding and configuring the WASI imports to invoke the above WASM component.

`main.rs`
```rust,ignore
{{#include ../examples/wasi/main.rs}}
```

## WASI state with other custom host state

The [`add_to_linker`] takes a second argument which is a closure to access `&mut
WasiCtx` from within the `T` stored in the `Store<T>` itself. In the above
example this is trivial because the `T` in `Store<T>` is `WasiCtx` itself, but
you can also store other state in `Store` like so:

[`add_to_linker`]: https://docs.rs/wasi-common/*/wasi_common/sync/fn.add_to_linker.html
[`Store`]: https://docs.rs/wasmtime/*/wasmtime/struct.Store.html
[`BorrowMut<WasiCtx>`]: https://doc.rust-lang.org/stable/std/borrow/trait.BorrowMut.html
[`WasiCtx`]: https://docs.rs/wasi-common/*/wasi_common/struct.WasiCtx.html

```rust
# extern crate wasmtime;
# extern crate wasi_common;
# extern crate anyhow;
use anyhow::Result;
use std::borrow::{Borrow, BorrowMut};
use wasmtime::*;
use wasi_common::{WasiCtx, sync::WasiCtxBuilder};

struct MyState {
message: String,
wasi: WasiCtx,
}

fn main() -> Result<()> {
let engine = Engine::default();
let mut linker = Linker::new(&engine);
wasi_common::sync::add_to_linker(&mut linker, |state: &mut MyState| &mut state.wasi)?;

let wasi = WasiCtxBuilder::new()
.inherit_stdio()
.inherit_args()?
.build();
let mut store = Store::new(&engine, MyState {
message: format!("hello!"),
wasi,
});

// ...

# let _linker: Linker<MyState> = linker;
Ok(())
}
```

## WASI Preview 2

An experimental implementation of the WASI Preview 2 API is also available, along with an adapter layer for WASI Preview 1 WebAssembly modules. In future this `preview2` API will become the default. There are some features which are currently only accessible through the `preview2` API such as async support and overriding the clock and random implementations.

### Async example

This [async example code][code2] shows how to use the [wasmtime-wasi::preview2][`preview2`] module to
execute the same WASI Preview 1 WebAssembly module from the example above. This example requires the `wasmtime` crate `async` feature to be enabled.
This [async example code][code2] shows how to use the [wasmtime-wasi][`wasmtime-wasi`] crate to
execute the same WASIp2 component from the example above. This example requires the `wasmtime` crate `async` feature to be enabled.

This does not require any change to the WebAssembly module, it's just the WASI API host functions which are implemented to be async. See [wasmtime async support](https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.async_support).
This does not require any change to the WASIp2 component, it's just the WASIp2 API host functions which are implemented to be async. See [wasmtime async support](https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.async_support).

[code2]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi-async/main.rs
[`preview2`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/preview2/index.html
[`wasmtime-wasi`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/preview2/index.html

```rust,ignore
{{#include ../examples/wasi-async/main.rs}}
```

You can also [browse this source code online][code2] and clone the wasmtime
repository to run the example locally.

## Beyond Basics

Please see these references:
* The [book](https://component-model.bytecodealliance.org) for understanding the component model of WASIp2.
* [Bindgen Examples](https://docs.rs/wasmtime/latest/wasmtime/component/bindgen_examples/index.html) for implementing WASIp2 hosts and guests.
6 changes: 4 additions & 2 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ create_target(multi multi.c)
create_target(multimemory multimemory.c)
create_target(serialize serialize.c)
create_target(threads threads.c)
create_target(wasi wasi/main.c)
create_target(wasip1 wasip1/main.c)

# Add rust tests
create_rust_test(anyref)
create_rust_wasm(fib-debug wasm32-unknown-unknown)
create_rust_wasm(tokio wasm32-wasip1)
create_rust_wasm(wasi wasm32-wasip1)
create_rust_wasm(wasi wasm32-wasip2)
create_rust_wasm(component wasm32-unknown-unknown)
create_rust_test(epochs)
create_rust_test(externref)
Expand All @@ -78,6 +79,7 @@ create_rust_test(multi)
create_rust_test(multimemory)
create_rust_test(serialize)
create_rust_test(threads)
create_rust_test(wasi)
create_rust_test(wasip1)
create_rust_test(wasip2)
create_rust_test(tokio wasi-common/tokio)
create_rust_test(component)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/*
You can execute this example with:
cmake examples/
cargo run --example wasi-async
cargo run --example wasip1-async
*/

use anyhow::Result;
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion examples/wasi/main.rs → examples/wasip1/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
You can execute this example with:
cmake examples/
cargo run --example wasi
cargo run --example wasip1
*/

use wasi_common::sync::WasiCtxBuilder;
Expand Down
59 changes: 59 additions & 0 deletions examples/wasip2-async/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! Example of instantiating a wasm module which uses WASI preview1 imports
//! implemented through the async preview2 WASI implementation.

/*
You can execute this example with:
cmake examples/
cargo run --example wasip2-async
*/

use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::*;
use wasmtime_wasi::bindings::Command;
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};

pub struct ComponentRunStates {
// These two are required basically as a standard way to enable the impl of WasiView
// impl of WasiView is required by [`wasmtime_wasi::add_to_linker_sync`]
pub wasi_ctx: WasiCtx,
pub resource_table: ResourceTable,
// You can add other custom host states if needed
}

impl WasiView for ComponentRunStates {
fn table(&mut self) -> &mut ResourceTable {
&mut self.resource_table
}
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi_ctx
}
}

#[tokio::main]
async fn main() -> Result<()> {
// Construct the wasm engine with async support enabled.
let mut config = Config::new();
config.async_support(true);
let engine = Engine::new(&config)?;
let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker_async(&mut linker)?;

// Create a WASI context and put it in a Store; all instances in the store
// share this context. `WasiCtxBuilder` provides a number of ways to
// configure what the target program will have access to.
let wasi = WasiCtxBuilder::new().inherit_stdio().inherit_args().build();
let state = ComponentRunStates {
wasi_ctx: wasi,
resource_table: ResourceTable::new(),
};
let mut store = Store::new(&engine, state);

// Instantiate our component with the imports we've created, and run it.
let component = Component::from_file(&engine, "target/wasm32-wasip2/debug/wasi.wasm")?;
let command = Command::instantiate_async(&mut store, &component, &linker).await?;
let program_result = command.wasi_cli_run().call_run(&mut store).await?;
match program_result {
Ok(()) => Ok(()),
Err(()) => std::process::exit(1),
}
}
86 changes: 86 additions & 0 deletions examples/wasip2/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! Example of instantiating a wasm module which uses WASI imports.

/*
You can execute this example with:
cmake examples/
cargo run --example wasip2
*/

use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::*;
use wasmtime_wasi::bindings::sync::Command;
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};

pub struct ComponentRunStates {
// These two are required basically as a standard way to enable the impl of WasiView
// impl of WasiView is required by [`wasmtime_wasi::add_to_linker_sync`]
pub wasi_ctx: WasiCtx,
pub resource_table: ResourceTable,
// You can add other custom host states if needed
}

impl WasiView for ComponentRunStates {
fn table(&mut self) -> &mut ResourceTable {
&mut self.resource_table
}
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi_ctx
}
}

fn main() -> Result<()> {
// Define the WASI functions globally on the `Config`.
let engine = Engine::default();
let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker_sync(&mut linker)?;

// Create a WASI context and put it in a Store; all instances in the store
// share this context. `WasiCtxBuilder` provides a number of ways to
// configure what the target program will have access to.
let wasi = WasiCtxBuilder::new().inherit_stdio().inherit_args().build();
let state = ComponentRunStates {
wasi_ctx: wasi,
resource_table: ResourceTable::new(),
};
let mut store = Store::new(&engine, state);

// Instantiate our component with the imports we've created, and run it.
let component = Component::from_file(&engine, "target/wasm32-wasip2/debug/wasi.wasm")?;
let command = Command::instantiate(&mut store, &component, &linker)?;
let program_result = command.wasi_cli_run().call_run(&mut store)?;
if program_result.is_err() {
std::process::exit(1)
}

// Alternatively, instead of using `Command`, just instantiate it as a normal component
// New states
let wasi = WasiCtxBuilder::new().inherit_stdio().inherit_args().build();
let state = ComponentRunStates {
wasi_ctx: wasi,
resource_table: ResourceTable::new(),
};
let mut store = Store::new(&engine, state);
// Instantiate it as a normal component
let instance = linker.instantiate(&mut store, &component)?;
// Get the index for the exported interface
let interface_idx = instance
.get_export(&mut store, None, "wasi:cli/[email protected]")
.expect("Cannot get `wasi:cli/[email protected]` interface");
// Get the index for the exported function in the exported interface
let parent_export_idx = Some(&interface_idx);
let func_idx = instance
.get_export(&mut store, parent_export_idx, "run")
.expect("Cannot get `run` function in `wasi:cli/[email protected]` interface");
let func = instance
.get_func(&mut store, func_idx)
.expect("Unreachable since we've got func_idx");
// As the `run` function in `wasi:cli/[email protected]` takes no argument and return a WASI result that correspond to a `Result<(), ()>`
// Reference:
// * https://github.com/WebAssembly/wasi-cli/blob/main/wit/run.wit
// * Documentation for [Func::typed](https://docs.rs/wasmtime/latest/wasmtime/component/struct.Func.html#method.typed) and [ComponentNamedList](https://docs.rs/wasmtime/latest/wasmtime/component/trait.ComponentNamedList.html)
let typed = func.typed::<(), (Result<(), ()>,)>(&store)?;
let (result,) = typed.call(&mut store, ())?;
// Required, see documentation of TypedFunc::call
typed.post_return(&mut store)?;
result.map_err(|_| anyhow::anyhow!("error"))
}
File renamed without changes.
File renamed without changes.
Loading