Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support worlds which import and/or export "wildcard" interfaces #5925

Closed
wants to merge 1 commit into from

Conversation

dicej
Copy link
Contributor

@dicej dicej commented Mar 3, 2023

Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export.

I've chosen to implement the bindings for host-implemented functions in such a way that the host may delay import resolution until the latest possible moment, i.e. when the guest is actually calling the function. This allows for fully dynamic resolution (e.g. using the function name as a key to be looked up in a remote key-value store) when desired. This does come at a small performance cost compared to doing resolution at e.g. link time instead.

In cases where the host wants to do resolution earlier (e.g. at deploy or instantiation time), that's certainly possible, e.g.:

let component = Component::new(&engine, wasm)?;
let funcs = component
    .names("imports")
    .map(|name| Ok((name.to_owned(), my_resolver(name)?)))
    .collect::<Result<HashMap<_, _>>>()?;

struct MyImports<F> {
   funcs: HashMap<String, F>
}

impl <F: Fn() -> Result<u32>> imports::Host for MyImports<F> {
    fn call(&mut self, name: &str) -> Result<u32> {
        (self.funcs.get(name).unwrap())()
    }
}

let mut store = Store::new(&engine, MyImports { funcs });
...

If we feel that early resolution is the more common case, we could consider adding a configuration option to the binding generator which indicates whether early or late resolution is desired, allowing the generator to optimize (ergonmically and performance-wise) accordingly.

Note that the generated add_to_linker functions now take a &Component parameter as well as a &mut Linker. This allows those functions to inspect the component in order to determine how many func_wrap{_async} calls to make, and with what names. I'm open to alternatives to this if there's a better way.

Finally, I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable.

@dicej
Copy link
Contributor Author

dicej commented Mar 3, 2023

Note that I tried to modify the generated code for exports to decouple the resolve step from the call step (e.g. so you could do (world.exports.get("x"))(&mut store)), and I had that working nicely in the non-async case, but the async case got really messy. I'll give it another shot next week.

@github-actions github-actions bot added the wasmtime:api Related to the API of the `wasmtime` crate itself label Mar 3, 2023
@github-actions
Copy link

github-actions bot commented Mar 3, 2023

Subscribe to Label Action

cc @peterhuene

This issue or pull request has been labeled: "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

  • peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

Per WebAssembly/component-model#172, this
implements "part 1" of WIT templates, allowing WIT files to define
interfaces which contain a single wildcard function, which worlds may
import or export.

I've chosen to implement the bindings for host-implemented functions
in such a way that the host may delay import resolution until the
latest possible moment, i.e. when the guest is actually calling the
function.  This allows for fully dynamic resolution (e.g. using the
function name as a key to be looked up in a remote key-value store)
when desired.  This does come at a small performance cost compared to
doing resolution at e.g. link time instead.

In cases where the host wants to do resolution earlier (e.g. at deploy
or instantiation time), that's certainly possible, e.g.:

```rust
let component = Component::new(&engine, wasm)?;
let funcs = component
    .names("imports")
    .map(|name| Ok((name.to_owned(), my_resolver(name)?)))
    .collect::<Result<HashMap<_, _>>>()?;

struct MyImports<F> {
   funcs: HashMap<String, F>
}

impl <F: Fn() -> Result<u32>> imports::Host for MyImports<F> {
    fn call(&mut self, name: &str) -> Result<u32> {
        (self.funcs.get(name).unwrap())()
    }
}

let mut store = Store::new(&engine, MyImports { funcs });
...
```

If we feel that early resolution is the more common case, we could
consider adding a configuration option to the binding generator which
indicates whether early or late resolution is desired, allowing the
generator to optimize (ergonmically and performance-wise) accordingly.

Note that the generated `add_to_linker` functions now take a
`&Component` parameter as well as a `&mut Linker`.  This allows those
functions to inspect the component in order to determine how many
`func_wrap{_async}` calls to make, and with what names.  I'm open to
alternatives to this if there's a better way.

Finally, I've added a temporary dependency override to Cargo.toml,
pointing to our fork of `wasm-tools`, which includes the necessary
`wit-parser` changes.  We're still iterating on that and will PR those
changes separately.  We also have a fork of `wit-bindgen` with a new
"wildcards" test to verify everything works end-to-end:
bytecodealliance/wit-bindgen@main...dicej:wit-templates.
I'll PR that last once everything else is stable.

Signed-off-by: Joel Dice <[email protected]>
@dicej
Copy link
Contributor Author

dicej commented Mar 5, 2023

After thinking about this some more, I realized that the "early vs. late resolution" tension I referred to above is not really a thing. Even if we force the host to do "early resolution" (i.e. prior to instantiating the component), it can just provide thunks which do asynchronous operations and/or trap at runtime if they need to. In other words, the host can always to "late resolution" (i.e. at the last possible moment, when called from the guest) inside the callback it provided at link time.

Anyway, I went ahead and rewrote this using an ultra-minimalist approach such that the binding generator just skips wildcards entirely, leaving the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. I'll close this and open a new PR.

@dicej dicej closed this Mar 5, 2023
@dicej
Copy link
Contributor Author

dicej commented Mar 5, 2023

See #5934 for the new PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wasmtime:api Related to the API of the `wasmtime` crate itself
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant