diff --git a/Cargo.toml b/Cargo.toml index 588914a99b9d..356131651b38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/docs/examples-c-wasi.md b/docs/examples-c-wasi.md index 70dda92c5e2e..7367fb538bca 100644 --- a/docs/examples-c-wasi.md +++ b/docs/examples-c-wasi.md @@ -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}} ``` diff --git a/docs/examples-rust-wasi.md b/docs/examples-rust-wasi.md index ee5dfa43abaf..7dc223fc08e3 100644 --- a/docs/examples-rust-wasi.md +++ b/docs/examples-rust-wasi.md @@ -1,97 +1,51 @@ -# 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` itself. In the above -example this is trivial because the `T` in `Store` 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`]: 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 = 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}} @@ -99,3 +53,9 @@ This does not require any change to the WebAssembly module, it's just the WASI A 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. \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 84a02e46fb56..000bbdf661f5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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) @@ -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) diff --git a/examples/wasi-async/main.rs b/examples/wasip1-async/main.rs similarity index 97% rename from examples/wasi-async/main.rs rename to examples/wasip1-async/main.rs index b1c3a4d19b34..1c266752af88 100644 --- a/examples/wasi-async/main.rs +++ b/examples/wasip1-async/main.rs @@ -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; diff --git a/examples/wasi/main.c b/examples/wasip1/main.c similarity index 100% rename from examples/wasi/main.c rename to examples/wasip1/main.c diff --git a/examples/wasi/main.rs b/examples/wasip1/main.rs similarity index 97% rename from examples/wasi/main.rs rename to examples/wasip1/main.rs index c6a3592436fa..6bc01bca52c4 100644 --- a/examples/wasi/main.rs +++ b/examples/wasip1/main.rs @@ -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; diff --git a/examples/wasip2-async/main.rs b/examples/wasip2-async/main.rs new file mode 100644 index 000000000000..29877ac3c693 --- /dev/null +++ b/examples/wasip2-async/main.rs @@ -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), + } +} diff --git a/examples/wasip2/main.rs b/examples/wasip2/main.rs new file mode 100644 index 000000000000..cfcfcb543123 --- /dev/null +++ b/examples/wasip2/main.rs @@ -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/run@0.2.0") + .expect("Cannot get `wasi:cli/run@0.2.0` 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/run@0.2.0` 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/run@0.2.0` 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")) +} diff --git a/examples/wasi/wasm/Cargo.toml b/examples/wasm/Cargo.toml similarity index 100% rename from examples/wasi/wasm/Cargo.toml rename to examples/wasm/Cargo.toml diff --git a/examples/wasi/wasm/wasi.rs b/examples/wasm/wasi.rs similarity index 100% rename from examples/wasi/wasm/wasi.rs rename to examples/wasm/wasi.rs