diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b358bb204696..67006ffd3422 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -653,6 +653,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 diff --git a/Cargo.lock b/Cargo.lock index 523b7a9211dd..579ae7ff4c0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4374,6 +4374,7 @@ dependencies = [ name = "wasmtime-jit-debug" version = "30.0.0" dependencies = [ + "cc", "object", "rustix", "wasmtime-versioned-export-macros", diff --git a/Cargo.toml b/Cargo.toml index 0966bf03d40e..41ec1c41c427 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,7 +152,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/crates/jit-debug/Cargo.toml b/crates/jit-debug/Cargo.toml index 9c55e2f8e7fd..a5c35cdebfd3 100644 --- a/crates/jit-debug/Cargo.toml +++ b/crates/jit-debug/Cargo.toml @@ -14,6 +14,10 @@ rust-version.workspace = true [lints] workspace = true +[build-dependencies] +cc = { workspace = true } +wasmtime-versioned-export-macros = { workspace = true } + [dependencies] object = { workspace = true, optional = true } wasmtime-versioned-export-macros = { workspace = true } diff --git a/crates/jit-debug/build.rs b/crates/jit-debug/build.rs new file mode 100644 index 000000000000..686a662581ae --- /dev/null +++ b/crates/jit-debug/build.rs @@ -0,0 +1,17 @@ +use wasmtime_versioned_export_macros::versioned_suffix; + +fn main() { + if !cfg!(feature = "gdb_jit_int") { + return; + } + + let mut build = cc::Build::new(); + build.warnings(true); + let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + build.define(&format!("CFG_TARGET_OS_{os}"), None); + build.define("VERSIONED_SUFFIX", Some(versioned_suffix!())); + + println!("cargo:rerun-if-changed=gdbjit.c"); + build.file("gdbjit.c"); + build.compile("gdbjit-helpers"); +} diff --git a/crates/jit-debug/gdbjit.c b/crates/jit-debug/gdbjit.c new file mode 100644 index 000000000000..b9f1d43e4468 --- /dev/null +++ b/crates/jit-debug/gdbjit.c @@ -0,0 +1,46 @@ +#include +#include + +#define CONCAT2(a, b) a##b +#define CONCAT(a, b) CONCAT2(a, b) +#define VERSIONED_SYMBOL(a) CONCAT(a, VERSIONED_SUFFIX) + +#ifdef CFG_TARGET_OS_windows +// export required for external access. +__declspec(dllexport) +#else +// Note the `weak` linkage here, though, which is intended to let other code +// override this symbol if it's defined elsewhere, since this definition doesn't +// matter. +// Just in case cross-language LTO is enabled we set the `noinline` attribute +// and also try to have some sort of side effect in this function with a dummy +// `asm` statement. +__attribute__((weak, noinline)) +#endif + void __jit_debug_register_code() { +#ifndef CFG_TARGET_OS_windows + __asm__(""); +#endif +} + +struct JITDescriptor { + uint32_t version_; + uint32_t action_flag_; + void *relevant_entry_; + void *first_entry_; +}; + +#ifdef CFG_TARGET_OS_windows +// export required for external access. +__declspec(dllexport) +#else +// Note the `weak` linkage here which is the same purpose as above. We want to +// let other runtimes be able to override this since our own definition isn't +// important. +__attribute__((weak)) +#endif +struct JITDescriptor __jit_debug_descriptor = {1, 0, NULL, NULL}; + +struct JITDescriptor *VERSIONED_SYMBOL(wasmtime_jit_debug_descriptor)() { + return &__jit_debug_descriptor; +} diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index 0057271f5cd5..09911e64f986 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -101,6 +101,10 @@ impl Engine { // handlers, etc. #[cfg(all(feature = "signals-based-traps", not(miri)))] crate::runtime::vm::init_traps(config.macos_use_mach_ports); + if !cfg!(miri) { + #[cfg(feature = "debug-builtins")] + crate::runtime::vm::debug_builtins::init(); + } } #[cfg(any(feature = "cranelift", feature = "winch"))] diff --git a/crates/wasmtime/src/runtime/vm/debug_builtins.rs b/crates/wasmtime/src/runtime/vm/debug_builtins.rs index 65c37e971949..cf5254423e6d 100644 --- a/crates/wasmtime/src/runtime/vm/debug_builtins.rs +++ b/crates/wasmtime/src/runtime/vm/debug_builtins.rs @@ -33,3 +33,18 @@ pub unsafe extern "C" fn set_vmctx_memory(vmctx_ptr: *mut VMContext) { // TODO multi-memory VMCTX_AND_MEMORY = (vmctx_ptr, 0); } + +/// A bit of a hack around various linkage things. The goal here is to force the +/// `wasmtime_*` symbols defined in `helpers.c` to actually get exported. That +/// means they need to be referenced for the linker to include them which is +/// what this function does with trickery in C. +pub fn init() { + extern "C" { + #[wasmtime_versioned_export_macros::versioned_link] + fn wasmtime_debug_builtins_init(); + } + + unsafe { + wasmtime_debug_builtins_init(); + } +} diff --git a/crates/wasmtime/src/runtime/vm/helpers.c b/crates/wasmtime/src/runtime/vm/helpers.c index 2ecaff40b35c..b35173405ca3 100644 --- a/crates/wasmtime/src/runtime/vm/helpers.c +++ b/crates/wasmtime/src/runtime/vm/helpers.c @@ -96,54 +96,18 @@ void VERSIONED_SYMBOL(set_vmctx_memory)(void *); DEBUG_BUILTIN_EXPORT void VERSIONED_SYMBOL(wasmtime_set_vmctx_memory)(void *p) { VERSIONED_SYMBOL(set_vmctx_memory)(p); } -#endif // FEATURE_DEBUG_BUILTINS -#ifdef CFG_TARGET_OS_windows -// export required for external access. -__declspec(dllexport) -#else -// Note the `weak` linkage here, though, which is intended to let other code -// override this symbol if it's defined elsewhere, since this definition doesn't -// matter. -// Just in case cross-language LTO is enabled we set the `noinline` attribute -// and also try to have some sort of side effect in this function with a dummy -// `asm` statement. -__attribute__((weak, noinline)) -#endif - void __jit_debug_register_code() { +// Helper symbol called from Rust to force the above two functions to not get +// stripped by the linker. +void VERSIONED_SYMBOL(wasmtime_debug_builtins_init)() { #ifndef CFG_TARGET_OS_windows - __asm__(""); -#ifdef FEATURE_DEBUG_BUILTINS - // Make sure these symbols do not get stripped by the compiler or linker. void *volatile p; p = (void *)&VERSIONED_SYMBOL(wasmtime_resolve_vmctx_memory_ptr); p = (void *)&VERSIONED_SYMBOL(wasmtime_set_vmctx_memory); (void)p; -#endif // FEATURE_DEBUG_BUILTINS -#endif -} - -struct JITDescriptor { - uint32_t version_; - uint32_t action_flag_; - void *relevant_entry_; - void *first_entry_; -}; - -#ifdef CFG_TARGET_OS_windows -// export required for external access. -__declspec(dllexport) -#else -// Note the `weak` linkage here which is the same purpose as above. We want to -// let other runtimes be able to override this since our own definition isn't -// important. -__attribute__((weak)) #endif -struct JITDescriptor __jit_debug_descriptor = {1, 0, NULL, NULL}; - -struct JITDescriptor *VERSIONED_SYMBOL(wasmtime_jit_debug_descriptor)() { - return &__jit_debug_descriptor; } +#endif // FEATURE_DEBUG_BUILTINS // For more information about this see `unix/unwind.rs` and the // `using_libunwind` function. The basic idea is that weak symbols aren't stable 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 660edf07532a..46df6db5eb43 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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) @@ -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) 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