diff --git a/.github/actions/install-wasi-sdk/action.yml b/.github/actions/install-wasi-sdk/action.yml index 169f68c93..b5dc5a88b 100644 --- a/.github/actions/install-wasi-sdk/action.yml +++ b/.github/actions/install-wasi-sdk/action.yml @@ -38,4 +38,8 @@ runs: - name: Setup `wasmtime` uses: bytecodealliance/actions/wasmtime/setup@v1 with: - version: "44.0.0" + # Pinned to the rolling `dev` build (main, which will become wasmtime + # 47) because the component model `implements` clause used by the + # `implements` runtime test has landed on `main` but is not in a + # released wasmtime yet. + version: "dev" diff --git a/Cargo.lock b/Cargo.lock index 2e6530677..8d5a4d98f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,7 +1024,7 @@ name = "test-helpers" version = "0.0.0" dependencies = [ "codegen-macro", - "wasm-encoder 0.252.0", + "wasm-encoder", "wit-bindgen-core", "wit-component", "wit-parser", @@ -1161,9 +1161,8 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wac-graph" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1294c33de03c933cceedb23457c9317ca87b3df11580250ec35a6fadc8c229da" +version = "0.10.1" +source = "git+https://github.com/ricochet/wac?branch=implements#cffa858037e482602329b160e1d866f16d19cf6f" dependencies = [ "anyhow", "id-arena", @@ -1173,16 +1172,15 @@ dependencies = [ "semver", "thiserror", "wac-types", - "wasm-encoder 0.247.0", - "wasm-metadata 0.247.0", - "wasmparser 0.247.0", + "wasm-encoder", + "wasm-metadata", + "wasmparser", ] [[package]] name = "wac-parser" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc78e70c11d6727b9b4b38f96f12f12f115f2f66a88b405918f8c1b6a1be646" +version = "0.10.1" +source = "git+https://github.com/ricochet/wac?branch=implements#cffa858037e482602329b160e1d866f16d19cf6f" dependencies = [ "anyhow", "id-arena", @@ -1194,23 +1192,22 @@ dependencies = [ "serde", "thiserror", "wac-graph", - "wasm-encoder 0.247.0", - "wasm-metadata 0.247.0", - "wasmparser 0.247.0", + "wasm-encoder", + "wasm-metadata", + "wasmparser", ] [[package]] name = "wac-types" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d2a240ab5ee560f160b161886e53b7eded2d5a9949964c832edfa1636f283b" +version = "0.10.1" +source = "git+https://github.com/ricochet/wac?branch=implements#cffa858037e482602329b160e1d866f16d19cf6f" dependencies = [ "anyhow", "id-arena", "indexmap", "semver", - "wasm-encoder 0.247.0", - "wasmparser 0.247.0", + "wasm-encoder", + "wasmparser", ] [[package]] @@ -1234,21 +1231,11 @@ dependencies = [ "serde_derive", "serde_yaml2", "smallvec", - "wasm-encoder 0.252.0", - "wasmparser 0.252.0", + "wasm-encoder", + "wasmparser", "wat", ] -[[package]] -name = "wasm-encoder" -version = "0.247.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b6733b8b91d010a6ac5b0fb237dc46a19650bc4c67db66857e2e787d437204" -dependencies = [ - "leb128fmt", - "wasmparser 0.247.0", -] - [[package]] name = "wasm-encoder" version = "0.252.0" @@ -1256,14 +1243,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" dependencies = [ "leb128fmt", - "wasmparser 0.252.0", + "wasmparser", ] [[package]] name = "wasm-metadata" -version = "0.247.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "665fe59e56cc9b419ca6fcca56673e3421d1a5011e3b65caf6b726fd9e041d10" +checksum = "2b7e08e02a3cd55bf778009d4cd6faae50da011f293644daf78a531a32d6d142" dependencies = [ "anyhow", "auditable-serde", @@ -1274,33 +1261,8 @@ dependencies = [ "serde_json", "spdx", "url", - "wasm-encoder 0.247.0", - "wasmparser 0.247.0", -] - -[[package]] -name = "wasm-metadata" -version = "0.252.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7e08e02a3cd55bf778009d4cd6faae50da011f293644daf78a531a32d6d142" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.252.0", - "wasmparser 0.252.0", -] - -[[package]] -name = "wasmparser" -version = "0.247.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" -dependencies = [ - "bitflags", - "hashbrown 0.17.1", - "indexmap", - "semver", - "serde", + "wasm-encoder", + "wasmparser", ] [[package]] @@ -1326,7 +1288,7 @@ dependencies = [ "leb128fmt", "memchr", "unicode-width 0.2.2", - "wasm-encoder 0.252.0", + "wasm-encoder", ] [[package]] @@ -1378,8 +1340,8 @@ dependencies = [ "clap", "heck", "indexmap", - "wasm-encoder 0.252.0", - "wasm-metadata 0.252.0", + "wasm-encoder", + "wasm-metadata", "wit-bindgen-core", "wit-component", ] @@ -1391,7 +1353,7 @@ dependencies = [ "anyhow", "clap", "env_logger", - "wasm-encoder 0.252.0", + "wasm-encoder", "wit-bindgen-c", "wit-bindgen-core", "wit-bindgen-cpp", @@ -1424,8 +1386,8 @@ dependencies = [ "heck", "indexmap", "test-helpers", - "wasm-encoder 0.252.0", - "wasm-metadata 0.252.0", + "wasm-encoder", + "wasm-metadata", "wit-bindgen-c", "wit-bindgen-core", "wit-component", @@ -1441,7 +1403,7 @@ dependencies = [ "heck", "indexmap", "regex", - "wasm-metadata 0.252.0", + "wasm-metadata", "wit-bindgen-core", "wit-component", "wit-parser", @@ -1454,8 +1416,8 @@ dependencies = [ "anyhow", "clap", "heck", - "wasm-encoder 0.252.0", - "wasm-metadata 0.252.0", + "wasm-encoder", + "wasm-metadata", "wit-bindgen-core", "wit-component", ] @@ -1496,7 +1458,7 @@ dependencies = [ "serde_json", "syn", "test-helpers", - "wasm-metadata 0.252.0", + "wasm-metadata", "wit-bindgen", "wit-bindgen-core", "wit-component", @@ -1534,8 +1496,8 @@ dependencies = [ "wac-types", "wasi-preview1-component-adapter-provider", "wasm-compose", - "wasm-encoder 0.252.0", - "wasmparser 0.252.0", + "wasm-encoder", + "wasmparser", "wat", "wit-bindgen-csharp", "wit-component", @@ -1555,9 +1517,9 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.252.0", - "wasm-metadata 0.252.0", - "wasmparser 0.252.0", + "wasm-encoder", + "wasm-metadata", + "wasmparser", "wat", "wit-parser", ] @@ -1578,7 +1540,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser 0.252.0", + "wasmparser", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5bf23e8ae..ff1a24dcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,3 +120,11 @@ csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] async = [] + +# The component model `implements` clause requires composition support that is +# not yet in a released `wac`. Until that ships on crates.io, point the `wac` +# crates at the `implements` branch. +[patch.crates-io] +wac-parser = { git = "https://github.com/ricochet/wac", branch = "implements" } +wac-types = { git = "https://github.com/ricochet/wac", branch = "implements" } +wac-graph = { git = "https://github.com/ricochet/wac", branch = "implements" } diff --git a/tests/runtime/implements/compose.wac b/tests/runtime/implements/compose.wac new file mode 100644 index 000000000..9dc2876be --- /dev/null +++ b/tests/runtime/implements/compose.wac @@ -0,0 +1,16 @@ +package example:composition; + +// Instantiate the `test` component twice to back the two labeled imports of the +// runner. The component model `implements` clause lets the runner import the +// same `store` interface under two different plain names (`primary` and +// `backup`), each wired here to its own independent instance. +let primary = new test:test { ... }; +let backup = new test:test { ... }; + +let runner = new test:runner { + primary: primary.store, + backup: backup.store, + ... +}; + +export runner...; diff --git a/tests/runtime/implements/runner.go b/tests/runtime/implements/runner.go new file mode 100644 index 000000000..c07dc326b --- /dev/null +++ b/tests/runtime/implements/runner.go @@ -0,0 +1,35 @@ +//@ wasmtime-flags = '-Wcomponent-model-implements' + +package export_wit_world + +import ( + "wit_component/backup" + "wit_component/primary" + + . "go.bytecodealliance.org/pkg/wit/types" +) + +func Run() { + // Each labeled import is its own instance of `store`, so values set + // through `primary` are independent from those set through `backup`. + primary.Set("key", "from-primary") + backup.Set("key", "from-backup") + + assertSome(primary.Get("key"), "from-primary") + assertSome(backup.Get("key"), "from-backup") + + assertNone(primary.Get("missing")) + assertNone(backup.Get("missing")) +} + +func assertSome(opt Option[string], expected string) { + if opt.Tag() != OptionSome || opt.Some() != expected { + panic("unexpected value") + } +} + +func assertNone(opt Option[string]) { + if opt.Tag() != OptionNone { + panic("expected none") + } +} diff --git a/tests/runtime/implements/runner.rs b/tests/runtime/implements/runner.rs new file mode 100644 index 000000000..e57e96ee6 --- /dev/null +++ b/tests/runtime/implements/runner.rs @@ -0,0 +1,22 @@ +//@ wasmtime-flags = '-Wcomponent-model-implements' + +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +impl Guest for Component { + fn run() { + // Each labeled import is its own instance of `store`, so values set + // through `primary` are independent from those set through `backup`. + primary::set("key", "from-primary"); + backup::set("key", "from-backup"); + + assert_eq!(primary::get("key").as_deref(), Some("from-primary")); + assert_eq!(backup::get("key").as_deref(), Some("from-backup")); + + assert_eq!(primary::get("missing"), None); + assert_eq!(backup::get("missing"), None); + } +} diff --git a/tests/runtime/implements/runner.wat b/tests/runtime/implements/runner.wat new file mode 100644 index 000000000..21f8daa9a --- /dev/null +++ b/tests/runtime/implements/runner.wat @@ -0,0 +1,22 @@ +;;@ wasmtime-flags = '-Wcomponent-model-implements' + +;; A `runner` written directly in WebAssembly text. It imports the same `store` +;; interface twice under two different plain names (`primary` and `backup`) via +;; the component model `implements` clause, so the two imports appear as distinct +;; core wasm import modules. +(module + (import "primary" "set" (func $primary-set (param i32 i32 i32 i32))) + (import "backup" "set" (func $backup-set (param i32 i32 i32 i32))) + + (memory (export "memory") 1) + + ;; "key" @ 0 (len 3), "primary" @ 3 (len 7), "backup" @ 10 (len 6) + (data (i32.const 0) "keyprimarybackup") + + (func (export "run") + ;; primary::set("key", "primary") + (call $primary-set (i32.const 0) (i32.const 3) (i32.const 3) (i32.const 7)) + ;; backup::set("key", "backup") + (call $backup-set (i32.const 0) (i32.const 3) (i32.const 10) (i32.const 6)) + ) +) diff --git a/tests/runtime/implements/test.go b/tests/runtime/implements/test.go new file mode 100644 index 000000000..e40f69f7e --- /dev/null +++ b/tests/runtime/implements/test.go @@ -0,0 +1,18 @@ +package export_test_implements_store + +import ( + . "go.bytecodealliance.org/pkg/wit/types" +) + +var store = map[string]string{} + +func Get(key string) Option[string] { + if value, ok := store[key]; ok { + return Some[string](value) + } + return None[string]() +} + +func Set(key string, value string) { + store[key] = value +} diff --git a/tests/runtime/implements/test.rs b/tests/runtime/implements/test.rs new file mode 100644 index 000000000..59d55b8f6 --- /dev/null +++ b/tests/runtime/implements/test.rs @@ -0,0 +1,24 @@ +include!(env!("BINDINGS")); + +use std::cell::RefCell; +use std::collections::HashMap; + +export!(Test); + +struct Test; + +thread_local! { + static STORE: RefCell> = RefCell::new(HashMap::new()); +} + +impl exports::test::implements::store::Guest for Test { + fn get(key: String) -> Option { + STORE.with(|s| s.borrow().get(&key).cloned()) + } + + fn set(key: String, value: String) { + STORE.with(|s| { + s.borrow_mut().insert(key, value); + }); + } +} diff --git a/tests/runtime/implements/test.wit b/tests/runtime/implements/test.wit new file mode 100644 index 000000000..b71465b4a --- /dev/null +++ b/tests/runtime/implements/test.wit @@ -0,0 +1,23 @@ +//@ dependencies = ['test'] +//@ wac = 'compose.wac' + +package test:implements; + +interface store { + get: func(key: string) -> option; + set: func(key: string, value: string); +} + +// The `runner` imports the same `store` interface twice under two different +// plain names. This exercises the component model `implements` clause where +// `primary` and `backup` both resolve to `(implements "test:implements/store")`. +world runner { + import primary: store; + import backup: store; + + export run: func(); +} + +world test { + export store; +}