Skip to content

Commit

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

I've taken a "minimalist" approach to this, such that the host binding
generator just skips wildcards entirely, leaving it to 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.

This adds two new functions to the public API:

- `Component::names`: provides an iterator over the names of the
  functions imported by the specified instance.
- `ExportInstance::funcs`: provides an iterator over the names of the
  functions exported by this instance.

In both cases, I'm open to alternative API designs for getting that
information.

Note that 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 5, 2023
1 parent ad584f4 commit cc5e7b0
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 5 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 @@ -172,7 +172,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 @@ -255,3 +255,6 @@ harness = false
[[bench]]
name = "wasi"
harness = false

[patch.crates-io]
wit-parser = { git = "https://github.com/fermyon/wasm-tools", branch = "wit-templates" }
17 changes: 17 additions & 0 deletions crates/wasmtime/src/component/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,4 +527,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(String::as_str))
} else {
None
}
})
.flatten()
}
}
23 changes: 23 additions & 0 deletions crates/wasmtime/src/component/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,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.as_str(),
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
15 changes: 14 additions & 1 deletion crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ impl Wasmtime {
Import::Function { sig, add_to_linker }
}
WorldItem::Interface(id) => {
let iface = &resolve.interfaces[*id];
if iface.functions.len() == 1 && "*" == iface.functions.keys().next().unwrap() {
// Skip wildcard interfaces and let the application call `func_wrap` directly since we can't
// know the names of the imported functions at code generation time.
return;
}

gen.current_interface = Some(*id);
gen.types(*id);
gen.generate_trappable_error_types(TypeOwner::Interface(*id));
Expand Down Expand Up @@ -157,10 +164,16 @@ impl Wasmtime {
}
WorldItem::Type(_) => unreachable!(),
WorldItem::Interface(id) => {
let iface = &resolve.interfaces[*id];
if iface.functions.len() == 1 && "*" == iface.functions.keys().next().unwrap() {
// Skip wildcard interfaces and let the application call `typed_func` directly since we can't
// know the names of the exported functions at code generation time.
return;
}

gen.current_interface = Some(*id);
gen.types(*id);
gen.generate_trappable_error_types(TypeOwner::Interface(*id));
let iface = &resolve.interfaces[*id];

let camel = to_rust_upper_camel_case(name);
uwriteln!(gen.src, "pub struct {camel} {{");
Expand Down
118 changes: 118 additions & 0 deletions tests/all/component_model/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,121 @@ mod one_import {
Ok(())
}
}

mod wildcards {
use super::*;
use component_test_util::TypedFuncExt;

// We won't actually use any code this produces, but it's here to assert that the macro doesn't get confused by
// wildcards:
wasmtime::component::bindgen!({
inline: "
default world wildcards {
import imports: interface {
*: func() -> u32
}
export exports: interface {
*: func() -> u32
}
}
"
});

fn lambda(
v: u32,
) -> Box<dyn Fn(wasmtime::StoreContextMut<'_, ()>, ()) -> Result<(u32,)> + Send + Sync> {
Box::new(move |_, _| Ok((v,)))
}

#[test]
fn run() -> Result<()> {
let engine = engine();

let component = Component::new(
&engine,
r#"
(component
(import "imports" (instance $i
(export "a" (func (result u32)))
(export "b" (func (result u32)))
(export "c" (func (result u32)))
))
(core module $m
(import "" "a" (func (result i32)))
(import "" "b" (func (result i32)))
(import "" "c" (func (result i32)))
(export "x" (func 0))
(export "y" (func 1))
(export "z" (func 2))
)
(core func $a (canon lower (func $i "a")))
(core func $b (canon lower (func $i "b")))
(core func $c (canon lower (func $i "c")))
(core instance $j (instantiate $m
(with "" (instance
(export "a" (func $a))
(export "b" (func $b))
(export "c" (func $c))
))
))
(func $x (result u32) (canon lift (core func $j "x")))
(func $y (result u32) (canon lift (core func $j "y")))
(func $z (export "z") (result u32) (canon lift (core func $j "z")))
(instance $k
(export "x" (func $x))
(export "y" (func $y))
(export "z" (func $z))
)
(export "exports" (instance $k))
)
"#,
)?;

let mut linker = Linker::<()>::new(&engine);
let mut instance = linker.instance("imports")?;
// In this simple test case, we don't really need to use `Component::names` to discover the imported
// function names, but in the general case, we would, so we verify they're all present.
for name in component.names("imports") {
instance.func_wrap(
name,
match name {
"a" => lambda(42),
"b" => lambda(43),
"c" => lambda(44),
_ => unreachable!(),
},
)?;
}

let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?;
let (mut x, mut y, mut z) = (None, None, None);

{
let mut exports = instance.exports(&mut store);
let mut exports = exports.instance("exports").unwrap();
// In this simple test case, we don't really need to use `ExportInstance::funcs` to discover the
// exported function names, but in the general case, we would, so we verify they're all present.
for (name, func) in exports.funcs() {
match name {
"x" => x = Some(func),
"y" => y = Some(func),
"z" => z = Some(func),
_ => unreachable!(),
}
}
};

let (x, y, z) = (
x.unwrap().typed::<(), (u32,)>(&store)?,
y.unwrap().typed::<(), (u32,)>(&store)?,
z.unwrap().typed::<(), (u32,)>(&store)?,
);

assert_eq!(42, x.call_and_post_return(&mut store, ())?.0);
assert_eq!(43, y.call_and_post_return(&mut store, ())?.0);
assert_eq!(44, z.call_and_post_return(&mut store, ())?.0);

Ok(())
}
}

0 comments on commit cc5e7b0

Please sign in to comment.