From 6bd215a1043f149cb1dde1ba4a38b2f90c598d35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:46:29 +0000 Subject: [PATCH 1/3] Bump wit-bindgen from 0.62.0 to 0.63.0 Bumps [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) from 0.62.0 to 0.63.0. - [Release notes](https://github.com/bytecodealliance/wit-bindgen/releases) - [Commits](https://github.com/bytecodealliance/wit-bindgen/compare/v0.62.0...v0.63.0) --- updated-dependencies: - dependency-name: wit-bindgen dependency-version: 0.63.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 96 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b5b780..d140bb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2102,22 +2102,22 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.259.0" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d0246511d901aacf25d2dc9111f0054947de5e093fe662099e709ff7530dc3" +checksum = "2608e8bb6d67fd68f5a8d0eb1363d6e7bcbc1f8ded5a0bd3a1e382462b876b22" dependencies = [ "leb128fmt", - "wasmparser 0.259.0", + "wasmparser 0.261.0", ] [[package]] name = "wasm-encoder" -version = "0.261.0" +version = "0.262.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2608e8bb6d67fd68f5a8d0eb1363d6e7bcbc1f8ded5a0bd3a1e382462b876b22" +checksum = "4495a6f85dd94c411c749303efcbc5defade0b0cbdbc0541495241ab917a9b5a" dependencies = [ "leb128fmt", - "wasmparser 0.261.0", + "wasmparser 0.262.0", ] [[package]] @@ -2141,26 +2141,26 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.259.0" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7bf119eb01f4a246864c10bd87c8f26c566350abb25aa4d5273c415bbab338" +checksum = "b88de21560ecd49deb8d8f1616b9b695f66fc312c5eca503f43accdc5795339e" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.259.0", - "wasmparser 0.259.0", + "wasm-encoder 0.261.0", + "wasmparser 0.261.0", ] [[package]] name = "wasm-metadata" -version = "0.261.0" +version = "0.262.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88de21560ecd49deb8d8f1616b9b695f66fc312c5eca503f43accdc5795339e" +checksum = "c86ff6668190d9fb70372b306b4f9fe8930f0e2f57ce72e4922bf2f117900949" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.261.0", - "wasmparser 0.261.0", + "wasm-encoder 0.262.0", + "wasmparser 0.262.0", ] [[package]] @@ -2178,27 +2178,27 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.259.0" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7c12eac7bb587801590f6a67ff0bc84d0748513864b71108e4b311cc3df694" +checksum = "4f20f20e44f7e8aeb6744823ea9d869ede51e51be4fdaedede2852282e54d2d8" dependencies = [ "bitflags", "hashbrown 0.17.1", "indexmap", "semver", + "serde", ] [[package]] name = "wasmparser" -version = "0.261.0" +version = "0.262.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f20f20e44f7e8aeb6744823ea9d869ede51e51be4fdaedede2852282e54d2d8" +checksum = "974d643b125b272607e2fff8012ea14a8328acd7621e4bafa7afb32500052b1b" dependencies = [ "bitflags", "hashbrown 0.17.1", "indexmap", "semver", - "serde", ] [[package]] @@ -2632,9 +2632,9 @@ checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] name = "wit-bindgen" -version = "0.62.0" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cb4b5556c3a791e86838ea287782bdafa704d55b0e68b5b81a3a16b9ea5f4b" +checksum = "b85a987ab61d365382d25c7d47e26edd48941b3bf6e3fbf76eff918bc71dab06" dependencies = [ "bitflags", "futures", @@ -2643,36 +2643,36 @@ dependencies = [ [[package]] name = "wit-bindgen-core" -version = "0.62.0" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ad22a37ecbc0e1fdca34d2b670ba41368cb0920735643e00fdf39a16ee5431" +checksum = "775e122e390603abfacd548027016cd0113a6e2bf2f8ce6bdd3658b013456ea8" dependencies = [ "anyhow", "heck", - "wit-parser 0.259.0", + "wit-parser 0.262.0", ] [[package]] name = "wit-bindgen-rust" -version = "0.62.0" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ba5213d4332c260a0d4e69e34b0f7e4b85ddae10b6ebfbac00d9c185ba628a" +checksum = "84e26e13b9d972026778d2ffb7985736de116026fdc8d9464f966e12df2eb240" dependencies = [ "anyhow", "heck", "indexmap", "prettyplease", "syn 3.0.6", - "wasm-metadata 0.259.0", + "wasm-metadata 0.262.0", "wit-bindgen-core", - "wit-component 0.259.0", + "wit-component 0.262.0", ] [[package]] name = "wit-bindgen-rust-macro" -version = "0.62.0" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f19d63da8e478a370ef8192d90ccb517965b7747d6b46cd2d831dd0ef4d2541" +checksum = "16a008a0d6b4840677326beb4edbeb210a2f1b7d0183fb7cc33f466b90fd7fb6" dependencies = [ "anyhow", "macro-string", @@ -2686,9 +2686,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.259.0" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092f783dee3253265fcde131475055f8c275ac0176a9c0396db1dc7b5544bfb4" +checksum = "2818766842bd2321074c348fb5c15d8fdb105a9eb78374e6d77c7d4127e1f2f9" dependencies = [ "anyhow", "bitflags", @@ -2697,17 +2697,17 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.259.0", - "wasm-metadata 0.259.0", - "wasmparser 0.259.0", - "wit-parser 0.259.0", + "wasm-encoder 0.261.0", + "wasm-metadata 0.261.0", + "wasmparser 0.261.0", + "wit-parser 0.261.0", ] [[package]] name = "wit-component" -version = "0.261.0" +version = "0.262.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2818766842bd2321074c348fb5c15d8fdb105a9eb78374e6d77c7d4127e1f2f9" +checksum = "078def49fdb49cab405c3006e05793b3fdaf7069126ba6b9c97d01ec8c98fe4c" dependencies = [ "anyhow", "bitflags", @@ -2716,17 +2716,17 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.261.0", - "wasm-metadata 0.261.0", - "wasmparser 0.261.0", - "wit-parser 0.261.0", + "wasm-encoder 0.262.0", + "wasm-metadata 0.262.0", + "wasmparser 0.262.0", + "wit-parser 0.262.0", ] [[package]] name = "wit-parser" -version = "0.259.0" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6390f4f02493ce678d509c859cf1698e38929fb75c8012a6ee2f7d6b6cb66cd7" +checksum = "962e43c4db2da607b50839a531e5b63473268b40efdf8028d5c54b572ca4f6f6" dependencies = [ "anyhow", "hashbrown 0.17.1", @@ -2738,14 +2738,14 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser 0.259.0", + "wasmparser 0.261.0", ] [[package]] name = "wit-parser" -version = "0.261.0" +version = "0.262.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962e43c4db2da607b50839a531e5b63473268b40efdf8028d5c54b572ca4f6f6" +checksum = "00f0dc051466ffe46787721fca3d088536f158ae400be044f1b1c386ba9397ad" dependencies = [ "anyhow", "hashbrown 0.17.1", @@ -2757,7 +2757,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser 0.261.0", + "wasmparser 0.262.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 41e3559..9631e75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ wac-graph = "0.12" wasmtime = { version = "50", features = ["component-model-async"] } wasmtime-wasi = { version = "50", features = ["p3"] } wasmtime-wasi-http = { version = "50", default-features = false, features = ["p3", "component-model-async"] } -wit-bindgen = "0.62.0" +wit-bindgen = "0.63.0" [profile.release] opt-level = "s" From 741e9e2e1066d01e6d4ef58694743ff09ca123e8 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Tue, 27 Oct 2026 16:27:11 -0400 Subject: [PATCH 2/3] Use component model threads wasm32-wasip3 adds native support for component model threads in rust 1.100. This side-steps our hacky wit_bindgen::spawn_local usage, which breaks in 0.63. In general, this change is riding the bleeding edge. But we were already doing that with spawn_local. At the moment, there's no other option while implementing the wasi 0.3 APIs. Rust 1.100 is currently beta and should be released next month. We also need to use a custom wasi-sdk and wasi-libc to fully pickup the threading support. These will hopefully converge into stable releases. wasm32-wasip3 by default adds a number of additional wasi imports. We switch to no_std to avoid most of those imports. Other imports are side-stepped by overriding the exported methods. This is likely fragile and may break with bumps to wasi-sdk and/or wasi-libc. Signed-off-by: Scott Andrews --- Cargo.lock | 10 +- Cargo.toml | 7 +- Makefile | 8 ++ components/client/Cargo.toml | 10 +- components/client/src/lib.rs | 33 +++-- components/trace-client/Cargo.toml | 8 +- components/trace-handler/Cargo.toml | 8 +- components/trace-types/Cargo.toml | 8 +- components/trace-types/tests/types.rs | 42 +++++- components/trace/Cargo.toml | 8 +- crates/http-utils/src/lib.rs | 33 +++-- crates/test-harness/src/lib.rs | 51 +++++++- crates/trace/src/client.rs | 2 +- crates/trace/src/handler.rs | 2 +- crates/trace/src/lib.rs | 18 ++- crates/trace/src/types.rs | 175 ++++++++++++++++++------- crates/wasip3-rt/Cargo.toml | 6 + crates/wasip3-rt/src/lib.rs | 181 ++++++++++++++++++++++++++ rust-toolchain.toml | 4 +- tools/Cargo.toml | 2 + 20 files changed, 522 insertions(+), 94 deletions(-) create mode 100644 crates/wasip3-rt/Cargo.toml create mode 100644 crates/wasip3-rt/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d140bb0..bf2629e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,7 @@ dependencies = [ "test-harness", "tokio", "url", + "wasip3-rt", "wasmtime", "wit-bindgen", ] @@ -1845,6 +1846,7 @@ dependencies = [ "http-utils", "test-harness", "tokio", + "wasip3-rt", "wasmtime", "wit-bindgen", ] @@ -1856,6 +1858,7 @@ dependencies = [ "http-utils", "test-harness", "tokio", + "wasip3-rt", "wasmtime", "wit-bindgen", ] @@ -1877,6 +1880,7 @@ dependencies = [ "http-utils", "test-harness", "tokio", + "wasip3-rt", "wasmtime", "wit-bindgen", ] @@ -1888,6 +1892,7 @@ dependencies = [ "http-utils", "test-harness", "tokio", + "wasip3-rt", "wasmtime", "wit-bindgen", ] @@ -2029,6 +2034,10 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip3-rt" +version = "0.1.0" + [[package]] name = "wasm-bindgen" version = "0.2.129" @@ -2637,7 +2646,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b85a987ab61d365382d25c7d47e26edd48941b3bf6e3fbf76eff918bc71dab06" dependencies = [ "bitflags", - "futures", "wit-bindgen-rust-macro", ] diff --git a/Cargo.toml b/Cargo.toml index 9631e75..46eaba2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,11 +29,9 @@ members = [ "crates/http-latch-n", "crates/http-utils", "crates/test-harness", + "crates/wasip3-rt", ] -[workspace.package] -version = "0.1.1-dev" - [workspace.dependencies] bytes = "1" http = "1" @@ -41,9 +39,8 @@ http-body-util = "0.1" http-latch = { path = "./crates/http-latch" } http-latch-n = { path = "./crates/http-latch-n" } http-utils = { path = "./crates/http-utils" } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" test-harness = { path = "./crates/test-harness" } +wasip3-rt = { path = "./crates/wasip3-rt" } tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } wac-graph = "0.12" wasmtime = { version = "50", features = ["component-model-async"] } diff --git a/Makefile b/Makefile index ba04349..bd51001 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,12 @@ # This repository builds with its own build, its components and cli exist to smoke test it, see # README.md +# wasi-libc main for WebAssembly/wasi-libc#884, threads that outlive the sync export that spawned them +# corrupt the heap with wasi-sdk 34's sysroot +WASI_LIBC_VERSION := f6e9006376e6f8e8f91ab84fc956be4e345588bc +# the WASI version of the components' WIT, wasi-libc imports 0.3.2 +WASI_LIBC_WASI_VERSION := 0.3.0 + include build/build.mk + +test: check-components validate-components diff --git a/components/client/Cargo.toml b/components/client/Cargo.toml index 59beb36..acdc5cf 100644 --- a/components/client/Cargo.toml +++ b/components/client/Cargo.toml @@ -8,10 +8,16 @@ license = "Apache-2.0" crate-type = ["cdylib"] [dependencies] -url = "2.5" -wit-bindgen = { workspace = true, features = ["async-spawn"] } +url = { version = "2.5", default-features = false } +# without std, see crates/wasip3-rt +wasip3-rt = { workspace = true } +wit-bindgen = { version = "0.63.0", default-features = false, features = ["macros", "realloc", "async"] } [dev-dependencies] test-harness = { workspace = true } tokio = { workspace = true } wasmtime = { workspace = true } + +[package.metadata.componentized] +# built for wasm32-wasip3, see build/components.mk +target = "wasm32-wasip3" diff --git a/components/client/src/lib.rs b/components/client/src/lib.rs index fd84c39..0d3c920 100644 --- a/components/client/src/lib.rs +++ b/components/client/src/lib.rs @@ -1,4 +1,14 @@ +//! Without std, see crates/wasip3-rt. + +#![no_std] + +extern crate alloc; + +use alloc::format; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; use url::Url; +use wasip3_rt as _; wit_bindgen::generate!({ path: "../wit", @@ -305,15 +315,17 @@ fn map_trailers( wasi: FutureReader, WasiErrorCode>>, ) -> FutureReader, TrailersErrorCode>> { let (tx, rx) = wit_future::new(|| Ok(Vec::new())); - wit_bindgen::rt::async_support::spawn_local(async move { - let resolved = match wasi.await { - Ok(Some(t)) => Ok(read_fields(&t)), - Ok(None) => Ok(Vec::new()), - Err(e) => Err(TrailersErrorCode::Other(Some(format!( - "wasi:http error: {e:?}" - )))), - }; - tx.write(resolved); + wasip3_rt::spawn(move || { + wit_bindgen::block_on(async move { + let resolved = match wasi.await { + Ok(Some(t)) => Ok(read_fields(&t)), + Ok(None) => Ok(Vec::new()), + Err(e) => Err(TrailersErrorCode::Other(Some(format!( + "wasi:http error: {e:?}" + )))), + }; + tx.write(resolved); + }) }); rx } @@ -446,6 +458,7 @@ export!(HttpClient); #[cfg(test)] mod tests { use super::*; + use alloc::vec; fn location(value: &str) -> Vec<(String, String)> { vec![("Location".to_string(), value.to_string())] @@ -456,7 +469,7 @@ mod tests { } /// Compare by debug output, the generated `Method` does not implement `PartialEq`. - fn assert_same(actual: impl std::fmt::Debug, expected: impl std::fmt::Debug, context: &str) { + fn assert_same(actual: impl core::fmt::Debug, expected: impl core::fmt::Debug, context: &str) { assert_eq!(format!("{actual:?}"), format!("{expected:?}"), "{context}"); } diff --git a/components/trace-client/Cargo.toml b/components/trace-client/Cargo.toml index 5321dce..5778058 100644 --- a/components/trace-client/Cargo.toml +++ b/components/trace-client/Cargo.toml @@ -16,9 +16,15 @@ handler = [] [dependencies] http-utils = { workspace = true } -wit-bindgen = { workspace = true, features = ["async-spawn"] } +# without std, see crates/wasip3-rt +wasip3-rt = { workspace = true } +wit-bindgen = { version = "0.63.0", default-features = false, features = ["macros", "realloc", "async"] } [dev-dependencies] test-harness = { workspace = true } tokio = { workspace = true } wasmtime = { workspace = true } + +[package.metadata.componentized] +# built for wasm32-wasip3, see build/components.mk +target = "wasm32-wasip3" diff --git a/components/trace-handler/Cargo.toml b/components/trace-handler/Cargo.toml index 4d42738..51706be 100644 --- a/components/trace-handler/Cargo.toml +++ b/components/trace-handler/Cargo.toml @@ -16,9 +16,15 @@ handler = [] [dependencies] http-utils = { workspace = true } -wit-bindgen = { workspace = true, features = ["async-spawn"] } +# without std, see crates/wasip3-rt +wasip3-rt = { workspace = true } +wit-bindgen = { version = "0.63.0", default-features = false, features = ["macros", "realloc", "async"] } [dev-dependencies] test-harness = { workspace = true } tokio = { workspace = true } wasmtime = { workspace = true } + +[package.metadata.componentized] +# built for wasm32-wasip3, see build/components.mk +target = "wasm32-wasip3" diff --git a/components/trace-types/Cargo.toml b/components/trace-types/Cargo.toml index 1adbd03..06eadbd 100644 --- a/components/trace-types/Cargo.toml +++ b/components/trace-types/Cargo.toml @@ -16,9 +16,15 @@ handler = [] [dependencies] http-utils = { workspace = true } -wit-bindgen = { workspace = true, features = ["async-spawn"] } +# without std, see crates/wasip3-rt +wasip3-rt = { workspace = true } +wit-bindgen = { version = "0.63.0", default-features = false, features = ["macros", "realloc", "async"] } [dev-dependencies] test-harness = { workspace = true } tokio = { workspace = true } wasmtime = { workspace = true } + +[package.metadata.componentized] +# built for wasm32-wasip3, see build/components.mk +target = "wasm32-wasip3" diff --git a/components/trace-types/tests/types.rs b/components/trace-types/tests/types.rs index 44a07dd..2c2aa73 100644 --- a/components/trace-types/tests/types.rs +++ b/components/trace-types/tests/types.rs @@ -1,7 +1,7 @@ //! Tests for the `wasi:http/types` operations traced by every trace component, run against //! `trace-types`. -use test_harness::{ErrorCode, Harness, LogEntry, Method, Scheme, ready}; +use test_harness::{ErrorCode, Harness, LogEntry, Method, Scheme, ready, resolve}; fn traced(message: &str) -> LogEntry { LogEntry::trace("componentized-trace", message) @@ -337,3 +337,43 @@ async fn response_traced() -> wasmtime::Result<()> { ); Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn trailers_passed_through_consume_body() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + let (request_trailers, response_trailers) = trace + .run(async |accessor, trace| { + let types = trace.wasi_http_types(); + let headers = types.fields().call_constructor(accessor).await?; + // an error, the trailers of a dropped writer are `ok(none)` + let trailers = ready(accessor, Err(ErrorCode::HttpProtocolError))?; + let (request, _transmitted) = types + .request() + .call_new(accessor, headers, None, trailers, None) + .await?; + let (_contents, trailers) = trace.consume_body(accessor, "request", request).await?; + let request_trailers = resolve(accessor, trailers).await?; + + let headers = types.fields().call_constructor(accessor).await?; + let trailers = ready(accessor, Err(ErrorCode::HttpProtocolError))?; + let (response, _transmitted) = types + .response() + .call_new(accessor, headers, None, trailers) + .await?; + let (_contents, trailers) = trace.consume_body(accessor, "response", response).await?; + let response_trailers = resolve(accessor, trailers).await?; + Ok((request_trailers, response_trailers)) + }) + .await?; + // the trailers `new` was called with, a request or response created by the component reads + // them back from it + assert!( + matches!(request_trailers, Err(ErrorCode::HttpProtocolError)), + "{request_trailers:?}" + ); + assert!( + matches!(response_trailers, Err(ErrorCode::HttpProtocolError)), + "{response_trailers:?}" + ); + Ok(()) +} diff --git a/components/trace/Cargo.toml b/components/trace/Cargo.toml index 78b7f9b..c7451c0 100644 --- a/components/trace/Cargo.toml +++ b/components/trace/Cargo.toml @@ -16,9 +16,15 @@ handler = [] [dependencies] http-utils = { workspace = true } -wit-bindgen = { workspace = true, features = ["async-spawn"] } +# without std, see crates/wasip3-rt +wasip3-rt = { workspace = true } +wit-bindgen = { version = "0.63.0", default-features = false, features = ["macros", "realloc", "async"] } [dev-dependencies] test-harness = { workspace = true } tokio = { workspace = true } wasmtime = { workspace = true } + +[package.metadata.componentized] +# built for wasm32-wasip3, see build/components.mk +target = "wasm32-wasip3" diff --git a/crates/http-utils/src/lib.rs b/crates/http-utils/src/lib.rs index fc68479..8d745a4 100644 --- a/crates/http-utils/src/lib.rs +++ b/crates/http-utils/src/lib.rs @@ -3,9 +3,22 @@ //! Each component generates its own bindings for `wasi:http/types`, so the helpers for its types //! are macros, expanded at the call site for the component's type, e.g. //! `format_http_method!(types::Method, &method)`. +//! +//! Without std, a component built for wasm32-wasip3 doesn't link the parts of wasi-libc std uses, and +//! the imports they need, e.g. `wasi:cli/stdin`. + +#![no_std] + +extern crate alloc; +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; + +#[doc(hidden)] +pub use alloc::borrow::Cow; #[doc(hidden)] -pub use std::borrow::Cow; +pub use alloc::string::ToString; /// Format a wasi:http `error-code` for logs, as a `Cow<'static, str>`. The name of the variant as /// written in the WIT, in lower case, e.g. `http-request-denied`. A payload is appended with the @@ -31,7 +44,7 @@ macro_rules! format_http_error_code { "dns-error", &[ ("rcode", payload.rcode.clone()), - ("info-code", payload.info_code.map(|code| code.to_string())), + ("info-code", payload.info_code.map(|code| $crate::ToString::to_string(&code))), ], ), $($error_code)::+::DestinationNotFound => $crate::Cow::Borrowed("destination-not-found"), @@ -57,7 +70,7 @@ macro_rules! format_http_error_code { $($error_code)::+::TlsAlertReceived(payload) => $crate::with_fields( "tls-alert-received", &[ - ("alert-id", payload.alert_id.map(|id| id.to_string())), + ("alert-id", payload.alert_id.map(|id| $crate::ToString::to_string(&id))), ("alert-message", payload.alert_message.clone()), ], ), @@ -66,7 +79,7 @@ macro_rules! format_http_error_code { $crate::Cow::Borrowed("http-request-length-required") } $($error_code)::+::HttpRequestBodySize(size) => { - $crate::with_value("http-request-body-size", size.map(|size| size.to_string())) + $crate::with_value("http-request-body-size", size.map(|size| $crate::ToString::to_string(&size))) } $($error_code)::+::HttpRequestMethodInvalid => { $crate::Cow::Borrowed("http-request-method-invalid") @@ -77,7 +90,7 @@ macro_rules! format_http_error_code { } $($error_code)::+::HttpRequestHeaderSectionSize(size) => $crate::with_value( "http-request-header-section-size", - size.map(|size| size.to_string()), + size.map(|size| $crate::ToString::to_string(&size)), ), $($error_code)::+::HttpRequestHeaderSize(None) => { $crate::Cow::Borrowed("http-request-header-size") @@ -87,7 +100,7 @@ macro_rules! format_http_error_code { } $($error_code)::+::HttpRequestTrailerSectionSize(size) => $crate::with_value( "http-request-trailer-section-size", - size.map(|size| size.to_string()), + size.map(|size| $crate::ToString::to_string(&size)), ), $($error_code)::+::HttpRequestTrailerSize(payload) => { $crate::with_field_size!("http-request-trailer-size", payload) @@ -97,17 +110,17 @@ macro_rules! format_http_error_code { } $($error_code)::+::HttpResponseHeaderSectionSize(size) => $crate::with_value( "http-response-header-section-size", - size.map(|size| size.to_string()), + size.map(|size| $crate::ToString::to_string(&size)), ), $($error_code)::+::HttpResponseHeaderSize(payload) => { $crate::with_field_size!("http-response-header-size", payload) } $($error_code)::+::HttpResponseBodySize(size) => { - $crate::with_value("http-response-body-size", size.map(|size| size.to_string())) + $crate::with_value("http-response-body-size", size.map(|size| $crate::ToString::to_string(&size))) } $($error_code)::+::HttpResponseTrailerSectionSize(size) => $crate::with_value( "http-response-trailer-section-size", - size.map(|size| size.to_string()), + size.map(|size| $crate::ToString::to_string(&size)), ), $($error_code)::+::HttpResponseTrailerSize(payload) => { $crate::with_field_size!("http-response-trailer-size", payload) @@ -260,7 +273,7 @@ macro_rules! with_field_size { ("field-name", $payload.field_name.clone()), ( "field-size", - $payload.field_size.map(|size| size.to_string()), + $payload.field_size.map(|size| $crate::ToString::to_string(&size)), ), ], ) diff --git a/crates/test-harness/src/lib.rs b/crates/test-harness/src/lib.rs index 43dca35..b517981 100644 --- a/crates/test-harness/src/lib.rs +++ b/crates/test-harness/src/lib.rs @@ -16,8 +16,8 @@ use bytes::Bytes; use http_body_util::{BodyExt, Empty}; use tokio::sync::{mpsc, oneshot}; use wasmtime::component::{ - Accessor, Component, FutureConsumer, FutureReader, HasData, HasSelf, Lift, Linker, Lower, - Resource, ResourceTable, Source, StreamConsumer, StreamReader, StreamResult, + Accessor, Component, FutureAny, FutureConsumer, FutureReader, HasData, HasSelf, Instance, Lift, + Linker, Lower, Resource, ResourceTable, Source, StreamConsumer, StreamReader, StreamResult, }; use wasmtime::error::Context as _; use wasmtime::{Config, Engine, Result, Store, StoreContextMut, bail, format_err}; @@ -888,6 +888,8 @@ impl Harness { pub async fn build(self) -> Result { let mut config = Config::new(); config.wasm_component_model_async(true); + // std threads on wasm32-wasip3 are component model cooperative threads + config.wasm_component_model_threading(true); // named imports, e.g. `latch0` of a `latch-n` component composed with a latch config.wasm_component_model_implements(true); let engine = Engine::new(&config)?; @@ -929,6 +931,7 @@ impl Harness { // exported by a gate takes the host's requests, a trace component takes its own let exports = Exports { name: self.subject, + instance, types: types::GuestIndices::new(&instance_pre) .ok() .and_then(|indices| indices.load(&mut store, &instance).ok()), @@ -962,6 +965,7 @@ impl Harness { /// Accessing an interface the component does not export panics. pub struct Exports { name: String, + instance: Instance, types: Option, client: Option, handler: Option, @@ -978,6 +982,49 @@ impl Exports { .unwrap_or_else(|| panic!("{} does not export wasi:http/types", self.name)) } + /// `consume-body` of a `request` or `response` of the exported `wasi:http/types`, with a `res` + /// that resolves to `ok`. + /// + /// The test subject passes `res` through to the host's `consume-body`, which reads it. A future + /// transferred host to host requires the same Rust type on both ends, so unlike [`ready`], `res` + /// is created with wasmtime-wasi-http's error code, and passed untyped. + pub async fn consume_body( + &self, + accessor: &Accessor, + resource: &str, + this: ResourceAny, + ) -> Result<( + StreamReader, + FutureReader, ErrorCode>>, + )> { + type HttpErrorCode = wasmtime_wasi_http::p3::bindings::http::types::ErrorCode; + type Params = (ResourceAny, FutureAny); + type Results = (( + StreamReader, + FutureReader, ErrorCode>>, + ),); + let (func, res) = accessor.with(|mut store| -> Result<_> { + let interface = self + .instance + .get_export_index(&mut store, None, "wasi:http/types@0.3.0") + .ok_or_else(|| format_err!("{} does not export wasi:http/types", self.name))?; + let name = format!("[static]{resource}.consume-body"); + let func = self + .instance + .get_export_index(&mut store, Some(&interface), &name) + .ok_or_else(|| format_err!("{} does not export {name}", self.name))?; + let func = self + .instance + .get_typed_func::(&mut store, &func)?; + let res = FutureReader::new(&mut store, async { + Ok::<_, wasmtime::Error>(Ok::<(), HttpErrorCode>(())) + })?; + Ok((func, res.try_into_future_any(&mut store)?)) + })?; + let (results,) = func.call_concurrent(accessor, (this, res)).await?; + Ok(results) + } + /// The exported `wasi:http/client` interface. pub fn wasi_http_client(&self) -> &client::Guest { self.client diff --git a/crates/trace/src/client.rs b/crates/trace/src/client.rs index d8038d3..246c0da 100644 --- a/crates/trace/src/client.rs +++ b/crates/trace/src/client.rs @@ -15,7 +15,7 @@ impl Guest for Trace { async fn send(request: Request) -> Result { let request = request.into_inner::(); trace!("OPERATION=wasi:http/client#send REQUEST={request}"); - let response = client::send(request.request).await?; + let response = client::send(request.into_host()).await?; Ok(Response::new(TraceResponse::new(response))) } } diff --git a/crates/trace/src/handler.rs b/crates/trace/src/handler.rs index 5e85f30..49df603 100644 --- a/crates/trace/src/handler.rs +++ b/crates/trace/src/handler.rs @@ -15,7 +15,7 @@ impl Guest for Trace { async fn handle(request: Request) -> Result { let request = request.into_inner::(); trace!("OPERATION=wasi:http/handler#handle REQUEST={request}"); - let response = handler::handle(request.request).await?; + let response = handler::handle(request.into_host()).await?; Ok(Response::new(TraceResponse::new(response))) } } diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index db3b3b5..56ebd79 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -6,8 +6,16 @@ //! //! - `client`: exports `wasi:http/client` //! - `handler`: exports `wasi:http/handler` +//! +//! Without std, see crates/wasip3-rt. + +#![no_std] + +extern crate alloc; + +use core::fmt::Display; -use std::fmt::Display; +use wasip3_rt as _; mod types; @@ -23,14 +31,14 @@ macro_rules! trace { $crate::wasi::logging::logging::log( $crate::wasi::logging::logging::Level::Trace, "componentized-trace", - &format!($dst, $($arg)*), + &::alloc::format!($dst, $($arg)*), ); }; ($dst:expr) => { $crate::wasi::logging::logging::log( $crate::wasi::logging::logging::Level::Trace, "componentized-trace", - &format!($dst), + &::alloc::format!($dst), ); }; } @@ -41,7 +49,7 @@ const HANDLE_MODULUS: u32 = 16u32.pow(HANDLE_DIGITS); /// Write the trailing hex digits of a resource handle, padded with zeros and prefixed with `&`, /// e.g. `&002a`. The prefix marks the value as a handle, a reference to a resource. -fn fmt_handle(f: &mut std::fmt::Formatter<'_>, handle: u32) -> std::fmt::Result { +fn fmt_handle(f: &mut core::fmt::Formatter<'_>, handle: u32) -> core::fmt::Result { write!( f, "&{:0width$x}", @@ -53,7 +61,7 @@ fn fmt_handle(f: &mut std::fmt::Formatter<'_>, handle: u32) -> std::fmt::Result struct DisplayOption(Option); impl Display for DisplayOption { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self(Some(value)) => write!(f, "some<{value}>"), Self(None) => f.write_str("none"), diff --git a/crates/trace/src/types.rs b/crates/trace/src/types.rs index 988aa3b..146a929 100644 --- a/crates/trace/src/types.rs +++ b/crates/trace/src/types.rs @@ -1,4 +1,6 @@ -use std::fmt::Display; +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt::Display; use crate::{ DisplayOption, Trace, @@ -29,6 +31,68 @@ impl TraceFields { } } +/// The trailers a request or response was created with by `new`, adapted to the trailers of the +/// host's request or response once the host needs them. +/// +/// The host's request or response is created with trailers this component writes. `consume-body` of +/// a request or response created here gets those trailers back from the host. Reading them would +/// read a future this component also writes, which the component model doesn't allow, so the guest's +/// own trailers are returned instead. +pub(crate) struct GuestTrailers { + /// The guest's trailers and the writer of the host's, until one of them is taken. + pending: Option<(GuestTrailersReader, HostTrailersWriter)>, +} + +type GuestTrailersReader = wit_bindgen::FutureReader, ErrorCode>>; +type HostTrailersReader = wit_bindgen::FutureReader, ErrorCode>>; +type HostTrailersWriter = wit_bindgen::FutureWriter, ErrorCode>>; + +impl GuestTrailers { + /// Defer adapting `trailers`, returns the trailers for the host's request or response. + fn new(trailers: GuestTrailersReader) -> (Self, HostTrailersReader) { + let (host_tx, host_rx) = wit_future::new(|| Ok(None)); + let pending = Some((trailers, host_tx)); + (Self { pending }, host_rx) + } + + /// The guest's trailers, in place of the host's `trailers` returned by `consume-body`, which are + /// dropped unread. + fn into_guest(mut self, trailers: HostTrailersReader) -> GuestTrailersReader { + let (guest, host_tx) = self + .pending + .take() + .expect("trailers are pending until taken"); + drop(trailers); + // dropping a writer writes its default, which needs a task, a sync export has none + wasip3_rt::spawn(move || { + wit_bindgen::block_on(async move { + let _ = host_tx.write(Ok(None)).await; + }) + }); + guest + } +} + +impl Drop for GuestTrailers { + /// Forward the guest's trailers to the host's, e.g. the request is sent or dropped. + fn drop(&mut self) { + let Some((guest, host_tx)) = self.pending.take() else { + return; + }; + // a sync export has no task to spawn into, so the future runs on its own thread + wasip3_rt::spawn(move || { + wit_bindgen::block_on(async move { + let trailers = match guest.await { + Ok(None) => Ok(None), + Ok(Some(trailers)) => Ok(Some(trailers.into_inner::().fields)), + Err(err) => Err(err), + }; + let _ = host_tx.write(trailers).await; + }) + }); + } +} + impl GuestFields for TraceFields { #[doc = " Construct an empty HTTP Fields."] #[doc = ""] @@ -165,7 +229,7 @@ impl GuestFields for TraceFields { } impl Display for TraceFields { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fmt_handle(f, self.fields.handle()) } } @@ -173,19 +237,24 @@ impl Display for TraceFields { struct DisplayFieldValue(FieldValue); impl Display for DisplayFieldValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let Self(value) = self; f.write_str(&String::from_utf8_lossy(value)) } } pub(crate) struct TraceRequest { - pub(crate) request: types::Request, + request: types::Request, + /// The trailers of a request created with `request.new`. + trailers: Option, } impl TraceRequest { - pub(crate) fn new(request: types::Request) -> Self { - Self { request } + /// The host's request, to hand to the host, its trailers are forwarded. + pub(crate) fn into_host(self) -> types::Request { + let Self { request, trailers } = self; + drop(trailers); + request } } @@ -220,18 +289,11 @@ impl GuestRequest for TraceRequest { ) -> (Request, wit_bindgen::FutureReader>) { trace!("OPERATION=wasi:http/types#request.new"); let headers = headers.into_inner::().fields; - let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); - wit_bindgen::spawn_local(async move { - let trailers = match trailers.await { - Ok(None) => Ok(None), - Ok(Some(trailers)) => Ok(Some(trailers.into_inner::().fields)), - Err(err) => Err(err), - }; - let _ = trailers_tx.write(trailers).await; - }); + let (trailers, host_trailers) = GuestTrailers::new(trailers); let options = options.map(|options| options.into_inner::().options); - let (request, read) = types::Request::new(headers, contents, trailers_rx, options); - (Request::new(Self::new(request)), read) + let (request, read) = types::Request::new(headers, contents, host_trailers, options); + let trailers = Some(trailers); + (Request::new(Self { request, trailers }), read) } #[doc = " Get the Method for the Request."] @@ -359,35 +421,43 @@ impl GuestRequest for TraceRequest { ) { let this = this.into_inner::(); trace!("OPERATION=wasi:http/types#request.consume-body THIS={this}"); - let request = this.request; + let TraceRequest { + request, + trailers: guest_trailers, + } = this; let (contents, trailers) = types::Request::consume_body(request, res); + if let Some(guest_trailers) = guest_trailers { + return (contents, guest_trailers.into_guest(trailers)); + } let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); - wit_bindgen::spawn_local(async move { - let trailers = match trailers.await { - Ok(None) => Ok(None), - Ok(Some(trailers)) => Ok(Some(Trailers::new(TraceFields::new(trailers)))), - Err(err) => Err(err), - }; - let _ = trailers_tx.write(trailers).await; + wasip3_rt::spawn(move || { + wit_bindgen::block_on(async move { + let trailers = match trailers.await { + Ok(None) => Ok(None), + Ok(Some(trailers)) => Ok(Some(Trailers::new(TraceFields::new(trailers)))), + Err(err) => Err(err), + }; + let _ = trailers_tx.write(trailers).await; + }) }); (contents, trailers_rx) } } impl Display for TraceRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fmt_handle(f, self.request.handle()) } } impl Display for Method { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(&http_utils::format_http_method!(types::Method, self)) } } impl Display for Scheme { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(&http_utils::format_http_scheme!(types::Scheme, self)) } } @@ -484,18 +554,22 @@ impl GuestRequestOptions for TraceRequestOptions { } impl Display for TraceRequestOptions { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fmt_handle(f, self.options.handle()) } } pub(crate) struct TraceResponse { response: types::Response, + /// The trailers of a response created with `response.new`. + trailers: Option, } impl TraceResponse { + /// Trace a response from the host. pub(crate) fn new(response: types::Response) -> Self { - Self { response } + let trailers = None; + Self { response, trailers } } } @@ -521,17 +595,10 @@ impl GuestResponse for TraceResponse { ) -> (Response, wit_bindgen::FutureReader>) { trace!("OPERATION=wasi:http/types#response.new"); let headers = headers.into_inner::().fields; - let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); - wit_bindgen::spawn_local(async move { - let trailers = match trailers.await { - Ok(None) => Ok(None), - Ok(Some(trailers)) => Ok(Some(trailers.into_inner::().fields)), - Err(err) => Err(err), - }; - let _ = trailers_tx.write(trailers).await; - }); - let (response, read) = types::Response::new(headers, contents, trailers_rx); - (Response::new(Self::new(response)), read) + let (trailers, host_trailers) = GuestTrailers::new(trailers); + let (response, read) = types::Response::new(headers, contents, host_trailers); + let trailers = Some(trailers); + (Response::new(Self { response, trailers }), read) } #[doc = " Get the HTTP Status Code for the Response."] @@ -583,23 +650,31 @@ impl GuestResponse for TraceResponse { ) { let this = this.into_inner::(); trace!("OPERATION=wasi:http/types#response.consume-body THIS={this}"); - let response = this.response; + let TraceResponse { + response, + trailers: guest_trailers, + } = this; let (contents, trailers) = types::Response::consume_body(response, res); + if let Some(guest_trailers) = guest_trailers { + return (contents, guest_trailers.into_guest(trailers)); + } let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); - wit_bindgen::spawn_local(async move { - let trailers = match trailers.await { - Ok(None) => Ok(None), - Ok(Some(trailers)) => Ok(Some(Trailers::new(TraceFields::new(trailers)))), - Err(err) => Err(err), - }; - let _ = trailers_tx.write(trailers).await; + wasip3_rt::spawn(move || { + wit_bindgen::block_on(async move { + let trailers = match trailers.await { + Ok(None) => Ok(None), + Ok(Some(trailers)) => Ok(Some(Trailers::new(TraceFields::new(trailers)))), + Err(err) => Err(err), + }; + let _ = trailers_tx.write(trailers).await; + }) }); (contents, trailers_rx) } } impl Display for TraceResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fmt_handle(f, self.response.handle()) } } diff --git a/crates/wasip3-rt/Cargo.toml b/crates/wasip3-rt/Cargo.toml new file mode 100644 index 0000000..0bf1531 --- /dev/null +++ b/crates/wasip3-rt/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "wasip3-rt" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" +publish = false diff --git a/crates/wasip3-rt/src/lib.rs b/crates/wasip3-rt/src/lib.rs new file mode 100644 index 0000000..8d0aba1 --- /dev/null +++ b/crates/wasip3-rt/src/lib.rs @@ -0,0 +1,181 @@ +//! What std provides a component, for a `no_std` component built for wasm32-wasip3: an allocator, +//! a panic handler, and threads. +//! +//! Without std, a component doesn't link the parts of wasi-libc std uses, and the imports they need, +//! e.g. `wasi:cli/stdin` and `wasi:cli/environment`. The allocator and threads are wasi-libc's, +//! the same std uses. Built for another target, e.g. the host for unit tests, std provides the +//! allocator and panic handler, and threads aren't supported. +//! +//! A component depends on this crate and links it with `use wasip3_rt as _;`, otherwise the +//! allocator and panic handler are missing. + +#![no_std] + +extern crate alloc; + +#[cfg(target_env = "p3")] +use alloc::boxed::Box; + +/// Run `f` on a new thread, detached. +/// +/// On wasm32-wasip3 a thread is a component model cooperative thread, it runs when the threads +/// before it block or yield, e.g. a future that runs while a sync export, which has no task to spawn +/// it into, returns. +pub fn spawn(f: F) { + #[cfg(target_env = "p3")] + { + extern "C" fn run(arg: *mut u8) -> *mut u8 { + let f = unsafe { Box::from_raw(arg.cast::>()) }; + f(); + core::ptr::null_mut() + } + let f: Box> = Box::new(Box::new(f)); + let arg = Box::into_raw(f).cast(); + // created detached, `pthread_detach` of a thread that already exited joins it, a wait with a + // timeout, which imports `wasi:clocks/monotonic-clock` and `wasi:clocks/system-clock`. + // `pthread_attr_init` waits for a lock with a timeout too, the attributes are the defaults + // `pthread_create` uses without attributes, read without a lock, as it does. + let mut attr = libc::Attr::default(); + let mut thread = 0; + let created = unsafe { + libc::pthread_attr_setstacksize(&mut attr, libc::__default_stacksize as usize); + libc::pthread_attr_setdetachstate(&mut attr, libc::PTHREAD_CREATE_DETACHED); + libc::pthread_create(&mut thread, &attr, run, arg) + }; + if created != 0 { + drop(unsafe { Box::from_raw(arg.cast::>()) }); + panic!("failed to spawn thread"); + } + } + #[cfg(not(target_env = "p3"))] + { + drop(f); + unimplemented!("threads need wasm32-wasip3"); + } +} + +/// A trap, without std there is nowhere to write the message. +#[cfg(target_env = "p3")] +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + core::arch::wasm32::unreachable() +} + +/// A trap, in place of wasi-libc's `exit`, which imports `wasi:cli/exit`. +/// +/// A component isn't a process, there is nothing to exit, an instance that exits can't be used +/// again, like one that traps. wasi-libc calls it when the last thread exits, which doesn't happen, +/// the main thread is never on the thread list. +#[cfg(target_env = "p3")] +#[unsafe(no_mangle)] +extern "C" fn exit(_code: i32) -> ! { + core::arch::wasm32::unreachable() +} + +/// The lock of wasi-libc's thread list, a no-op, like wasi-libc's own. +/// +/// wasi-libc's cooperative threads only define it with the thread-specific data of +/// `pthread_key_create`, linking it links them, and their destructors, which wait for a lock with a +/// timeout, importing `wasi:clocks/monotonic-clock` and `wasi:clocks/system-clock`. Nothing uses +/// thread-specific data, the release build optimizes them away, a debug build keeps the imports. +#[cfg(target_env = "p3")] +#[unsafe(no_mangle)] +extern "C" fn __tl_lock() {} + +#[cfg(target_env = "p3")] +#[unsafe(no_mangle)] +extern "C" fn __tl_unlock() {} + +#[cfg(target_env = "p3")] +#[global_allocator] +static ALLOCATOR: libc::Malloc = libc::Malloc; + +#[cfg(target_env = "p3")] +mod libc { + use core::alloc::{GlobalAlloc, Layout}; + + /// pthread_t, a pointer + pub type Thread = usize; + + /// pthread_attr_t, 36 bytes on wasm32, zeroed, with no guard, the only guard size WASI allows + #[repr(C, align(8))] + pub struct Attr([u8; 64]); + + impl Default for Attr { + fn default() -> Self { + Self([0; 64]) + } + } + + pub const PTHREAD_CREATE_DETACHED: i32 = 1; + + #[link(name = "c")] + unsafe extern "C" { + fn malloc(size: usize) -> *mut u8; + fn calloc(count: usize, size: usize) -> *mut u8; + fn realloc(ptr: *mut u8, size: usize) -> *mut u8; + fn aligned_alloc(align: usize, size: usize) -> *mut u8; + fn free(ptr: *mut u8); + /// the stack size of a thread created without attributes + pub static __default_stacksize: u32; + pub fn pthread_attr_setstacksize(attr: *mut Attr, size: usize) -> i32; + pub fn pthread_attr_setdetachstate(attr: *mut Attr, state: i32) -> i32; + pub fn pthread_create( + thread: *mut Thread, + attr: *const Attr, + start: extern "C" fn(*mut u8) -> *mut u8, + arg: *mut u8, + ) -> i32; + } + + /// The alignment malloc guarantees, like std's allocator for wasm32 + const MIN_ALIGN: usize = 8; + + /// wasi-libc's allocator, the one std uses + pub struct Malloc; + + unsafe impl GlobalAlloc for Malloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { + if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { + malloc(layout.size()) + } else { + aligned_alloc(layout.align(), layout.size()) + } + } + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + unsafe { + if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { + calloc(layout.size(), 1) + } else { + let ptr = self.alloc(layout); + if !ptr.is_null() { + ptr.write_bytes(0, layout.size()); + } + ptr + } + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + unsafe { free(ptr) } + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + unsafe { + if layout.align() <= MIN_ALIGN && layout.align() <= new_size { + realloc(ptr, new_size) + } else { + let new = self.alloc(Layout::from_size_align_unchecked(new_size, layout.align())); + if !new.is_null() { + new.copy_from_nonoverlapping(ptr, layout.size().min(new_size)); + free(ptr); + } + new + } + } + } + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5459225..f637be2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.99" -targets = [ "wasm32-unknown-unknown" ] +channel = "1.100.0-beta.7" +targets = [ "wasm32-unknown-unknown", "wasm32-wasip3" ] diff --git a/tools/Cargo.toml b/tools/Cargo.toml index f0f17a2..9c13bbd 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -18,6 +18,7 @@ path = "lib.rs" componentized-constants-cli = ["constants"] componentized-static-config-cli = ["static-config"] wac-cli = ["wac"] +wasm-opt = ["wasm-opt"] wasm-tools = ["wasm-tools"] wasmtime-cli = ["wasmtime"] wkg = ["wkg"] @@ -26,6 +27,7 @@ wkg = ["wkg"] componentized-constants-cli = "=0.2.0" componentized-static-config-cli = "=0.3.0" wac-cli = "=0.12.0" +wasm-opt = "=0.116.1" wasm-tools = "=1.262.0" wasmtime-cli = "=50.0.0" wkg = "=0.16.1" From 735609c777fde5d64ad612b70983254ea61440e8 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Tue, 27 Oct 2026 16:50:32 -0400 Subject: [PATCH 3/3] Suppress warnings False dead code warnings because of how the trace components are built. Signed-off-by: Scott Andrews --- crates/trace/src/types.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/trace/src/types.rs b/crates/trace/src/types.rs index 146a929..0e580ba 100644 --- a/crates/trace/src/types.rs +++ b/crates/trace/src/types.rs @@ -251,6 +251,7 @@ pub(crate) struct TraceRequest { impl TraceRequest { /// The host's request, to hand to the host, its trailers are forwarded. + #[allow(dead_code)] pub(crate) fn into_host(self) -> types::Request { let Self { request, trailers } = self; drop(trailers); @@ -567,6 +568,7 @@ pub(crate) struct TraceResponse { impl TraceResponse { /// Trace a response from the host. + #[allow(dead_code)] pub(crate) fn new(response: types::Response) -> Self { let trailers = None; Self { response, trailers }