Skip to content

Commit 6c09109

Browse files
committed
The implements clause lets a world import or export the same interface
multiple times under different plain names: world runner { import primary: store; import backup: store; } No bindings-generator changes needed. wit-parser surfaces each label as a plain-name interface import/export, `generate_nominal_type_ids` gives each its own type identity, and the existing plain-name interface code path emits label-named modules and `wasm_import_module`s. The test covers Rust, Go, and WAT runners with Rust and Go tests, composed via `compose.wac` so the two labels resolve to two independent `store` instances. Temporary, until releases catch up: - `wac` is pointed at the `implements` branch; composition support for the clause is not yet in a released wac. - CI's wasmtime is pinned to the `dev` build; the clause has landed on wasmtime `main` but is not yet released. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
1 parent cfd7b77 commit 6c09109

10 files changed

Lines changed: 211 additions & 77 deletions

File tree

.github/actions/install-wasi-sdk/action.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@ runs:
3838
- name: Setup `wasmtime`
3939
uses: bytecodealliance/actions/wasmtime/setup@v1
4040
with:
41-
version: "44.0.0"
41+
# Pinned to the rolling `dev` build (main, which will become wasmtime
42+
# 47) because the component model `implements` clause used by the
43+
# `implements` runtime test has landed on `main` but is not in a
44+
# released wasmtime yet.
45+
version: "dev"

Cargo.lock

Lines changed: 38 additions & 76 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,11 @@ csharp = ['dep:wit-bindgen-csharp']
120120
csharp-mono = ['csharp']
121121
moonbit = ['dep:wit-bindgen-moonbit']
122122
async = []
123+
124+
# The component model `implements` clause requires composition support that is
125+
# not yet in a released `wac`. Until that ships on crates.io, point the `wac`
126+
# crates at the `implements` branch.
127+
[patch.crates-io]
128+
wac-parser = { git = "https://github.com/ricochet/wac", branch = "implements" }
129+
wac-types = { git = "https://github.com/ricochet/wac", branch = "implements" }
130+
wac-graph = { git = "https://github.com/ricochet/wac", branch = "implements" }
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package example:composition;
2+
3+
// Instantiate the `test` component twice to back the two labeled imports of the
4+
// runner. The component model `implements` clause lets the runner import the
5+
// same `store` interface under two different plain names (`primary` and
6+
// `backup`), each wired here to its own independent instance.
7+
let primary = new test:test { ... };
8+
let backup = new test:test { ... };
9+
10+
let runner = new test:runner {
11+
primary: primary.store,
12+
backup: backup.store,
13+
...
14+
};
15+
16+
export runner...;

tests/runtime/implements/runner.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//@ wasmtime-flags = '-Wcomponent-model-implements'
2+
3+
package export_wit_world
4+
5+
import (
6+
"wit_component/backup"
7+
"wit_component/primary"
8+
9+
. "go.bytecodealliance.org/pkg/wit/types"
10+
)
11+
12+
func Run() {
13+
// Each labeled import is its own instance of `store`, so values set
14+
// through `primary` are independent from those set through `backup`.
15+
primary.Set("key", "from-primary")
16+
backup.Set("key", "from-backup")
17+
18+
assertSome(primary.Get("key"), "from-primary")
19+
assertSome(backup.Get("key"), "from-backup")
20+
21+
assertNone(primary.Get("missing"))
22+
assertNone(backup.Get("missing"))
23+
}
24+
25+
func assertSome(opt Option[string], expected string) {
26+
if opt.Tag() != OptionSome || opt.Some() != expected {
27+
panic("unexpected value")
28+
}
29+
}
30+
31+
func assertNone(opt Option[string]) {
32+
if opt.Tag() != OptionNone {
33+
panic("expected none")
34+
}
35+
}

tests/runtime/implements/runner.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ wasmtime-flags = '-Wcomponent-model-implements'
2+
3+
include!(env!("BINDINGS"));
4+
5+
struct Component;
6+
7+
export!(Component);
8+
9+
impl Guest for Component {
10+
fn run() {
11+
// Each labeled import is its own instance of `store`, so values set
12+
// through `primary` are independent from those set through `backup`.
13+
primary::set("key", "from-primary");
14+
backup::set("key", "from-backup");
15+
16+
assert_eq!(primary::get("key").as_deref(), Some("from-primary"));
17+
assert_eq!(backup::get("key").as_deref(), Some("from-backup"));
18+
19+
assert_eq!(primary::get("missing"), None);
20+
assert_eq!(backup::get("missing"), None);
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
;;@ wasmtime-flags = '-Wcomponent-model-implements'
2+
3+
;; A `runner` written directly in WebAssembly text. It imports the same `store`
4+
;; interface twice under two different plain names (`primary` and `backup`) via
5+
;; the component model `implements` clause, so the two imports appear as distinct
6+
;; core wasm import modules.
7+
(module
8+
(import "primary" "set" (func $primary-set (param i32 i32 i32 i32)))
9+
(import "backup" "set" (func $backup-set (param i32 i32 i32 i32)))
10+
11+
(memory (export "memory") 1)
12+
13+
;; "key" @ 0 (len 3), "primary" @ 3 (len 7), "backup" @ 10 (len 6)
14+
(data (i32.const 0) "keyprimarybackup")
15+
16+
(func (export "run")
17+
;; primary::set("key", "primary")
18+
(call $primary-set (i32.const 0) (i32.const 3) (i32.const 3) (i32.const 7))
19+
;; backup::set("key", "backup")
20+
(call $backup-set (i32.const 0) (i32.const 3) (i32.const 10) (i32.const 6))
21+
)
22+
)

tests/runtime/implements/test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package export_test_implements_store
2+
3+
import (
4+
. "go.bytecodealliance.org/pkg/wit/types"
5+
)
6+
7+
var store = map[string]string{}
8+
9+
func Get(key string) Option[string] {
10+
if value, ok := store[key]; ok {
11+
return Some[string](value)
12+
}
13+
return None[string]()
14+
}
15+
16+
func Set(key string, value string) {
17+
store[key] = value
18+
}

0 commit comments

Comments
 (0)