Skip to content

Commit

Permalink
Revamp wasi example and related docs (bytecodealliance#9788)
Browse files Browse the repository at this point in the history
* update examples/wasi/main.rs and related doc with a runtime issue

* simplify building instruction

* fix runtime issue

* add a comment and cargo fmt

* revamped wasi-async

* remove the example of custom host states and add references

* add calling function dynamically

* cargo fmt

* add post_return

* update wording

* add expects

* change wording from "module" to "component"

* fix CMakeLists.txt

* compile wasi example with wasip1 to make the outdated WASIp1 C example happy

* rename wasi examples

* fix wording

* fix format

* add wasm32-wasip2 target

* Revert "add wasm32-wasip2 target"

This reverts commit 0aa610f.

* add wasm32-wasip2 target for test_capi job
  • Loading branch information
ifsheldon authored Jan 7, 2025
1 parent 115da98 commit 5030709
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 71 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ jobs:
# Build and test the C API with example C programs along with the example
# Rust programs. Note that this only executes if the `determine` step told
# us to test the capi which is off-by-default for PRs.
- run: rustup target add wasm32-wasip2 # wasip2 target needed by example programs
- run: cmake -Sexamples -Bexamples/build -DBUILD_SHARED_LIBS=OFF
- run: cmake --build examples/build --config Debug
- run: cmake -E env CTEST_OUTPUT_ON_FAILURE=1 cmake --build examples/build --config Debug --target RUN_TESTS
Expand Down
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 @@ -59,13 +59,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 @@ -80,6 +81,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.

0 comments on commit 5030709

Please sign in to comment.