Skip to content

Commit

Permalink
support worlds which import and/or export "wildcard" interfaces
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
dicej committed Mar 3, 2023
1 parent 3ff3994 commit 77204fc
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 48 deletions.
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ wasmprinter = "0.2.50"
wasm-encoder = "0.23.0"
wasm-smith = "0.12.1"
wasm-mutate = "0.2.17"
wit-parser = "0.6.1"
wit-parser = "0.6.2"
windows-sys = "0.45.0"
env_logger = "0.9"
rustix = "0.36.7"
Expand Down Expand Up @@ -253,3 +253,6 @@ harness = false
[[bench]]
name = "wasi"
harness = false

[patch.crates-io]
wit-parser = { git = "https://github.com/fermyon/wasm-tools", branch = "wit-templates" }
18 changes: 18 additions & 0 deletions crates/wasmtime/src/component/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use std::collections::{BTreeSet, HashMap};
use std::fs;
use std::mem;
use std::ops::Deref;
use std::path::Path;
use std::ptr::NonNull;
use std::sync::Arc;
Expand Down Expand Up @@ -527,4 +528,21 @@ impl Component {
pub fn serialize(&self) -> Result<Vec<u8>> {
Ok(self.code_object().code_memory().mmap().to_vec())
}

/// Get the names of all the imports from the specified instance.
pub fn names<'a>(&'a self, instance_name: &'a str) -> impl Iterator<Item = &str> + 'a {
let env_component = self.env_component();

env_component
.imports
.values()
.filter_map(move |(import, names)| {
if instance_name == &env_component.import_types[*import].0 {
Some(names.iter().map(Deref::deref))
} else {
None
}
})
.flatten()
}
}
24 changes: 24 additions & 0 deletions crates/wasmtime/src/component/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{AsContextMut, Module, StoreContextMut};
use anyhow::{anyhow, Context, Result};
use indexmap::IndexMap;
use std::marker;
use std::ops::Deref;
use std::sync::Arc;
use wasmtime_environ::component::{
AlwaysTrap, ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory,
Expand Down Expand Up @@ -660,6 +661,29 @@ impl<'a, 'store> ExportInstance<'a, 'store> {
})
}

/// Returns an iterator of all of the exported functions that this instance
/// contains.
//
// See FIXME in above `modules` method, which also applies here.
pub fn funcs(&mut self) -> impl Iterator<Item = (&'a str, Func)> + '_ {
self.exports
.iter()
.filter_map(|(name, export)| match export {
Export::LiftedFunction { ty, func, options } => Some((
name.deref(),
Func::from_lifted_func(
self.store,
self.instance,
self.data,
*ty,
func,
options,
),
)),
_ => None,
})
}

fn as_mut(&mut self) -> ExportInstance<'a, '_> {
ExportInstance {
exports: self.exports,
Expand Down
Loading

0 comments on commit 77204fc

Please sign in to comment.