Skip to content

Commit cc5e7b0

Browse files
committed
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 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]>
1 parent ad584f4 commit cc5e7b0

File tree

6 files changed

+178
-5
lines changed

6 files changed

+178
-5
lines changed

Cargo.lock

+2-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ wasmprinter = "0.2.50"
172172
wasm-encoder = "0.23.0"
173173
wasm-smith = "0.12.1"
174174
wasm-mutate = "0.2.17"
175-
wit-parser = "0.6.1"
175+
wit-parser = "0.6.2"
176176
windows-sys = "0.45.0"
177177
env_logger = "0.9"
178178
rustix = "0.36.7"
@@ -255,3 +255,6 @@ harness = false
255255
[[bench]]
256256
name = "wasi"
257257
harness = false
258+
259+
[patch.crates-io]
260+
wit-parser = { git = "https://github.com/fermyon/wasm-tools", branch = "wit-templates" }

crates/wasmtime/src/component/component.rs

+17
Original file line numberDiff line numberDiff line change
@@ -527,4 +527,21 @@ impl Component {
527527
pub fn serialize(&self) -> Result<Vec<u8>> {
528528
Ok(self.code_object().code_memory().mmap().to_vec())
529529
}
530+
531+
/// Get the names of all the imports from the specified instance.
532+
pub fn names<'a>(&'a self, instance_name: &'a str) -> impl Iterator<Item = &str> + 'a {
533+
let env_component = self.env_component();
534+
535+
env_component
536+
.imports
537+
.values()
538+
.filter_map(move |(import, names)| {
539+
if instance_name == &env_component.import_types[*import].0 {
540+
Some(names.iter().map(String::as_str))
541+
} else {
542+
None
543+
}
544+
})
545+
.flatten()
546+
}
530547
}

crates/wasmtime/src/component/instance.rs

+23
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,29 @@ impl<'a, 'store> ExportInstance<'a, 'store> {
660660
})
661661
}
662662

663+
/// Returns an iterator of all of the exported functions that this instance
664+
/// contains.
665+
//
666+
// See FIXME in above `modules` method, which also applies here.
667+
pub fn funcs(&mut self) -> impl Iterator<Item = (&'a str, Func)> + '_ {
668+
self.exports
669+
.iter()
670+
.filter_map(|(name, export)| match export {
671+
Export::LiftedFunction { ty, func, options } => Some((
672+
name.as_str(),
673+
Func::from_lifted_func(
674+
self.store,
675+
self.instance,
676+
self.data,
677+
*ty,
678+
func,
679+
options,
680+
),
681+
)),
682+
_ => None,
683+
})
684+
}
685+
663686
fn as_mut(&mut self) -> ExportInstance<'a, '_> {
664687
ExportInstance {
665688
exports: self.exports,

crates/wit-bindgen/src/lib.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ impl Wasmtime {
111111
Import::Function { sig, add_to_linker }
112112
}
113113
WorldItem::Interface(id) => {
114+
let iface = &resolve.interfaces[*id];
115+
if iface.functions.len() == 1 && "*" == iface.functions.keys().next().unwrap() {
116+
// Skip wildcard interfaces and let the application call `func_wrap` directly since we can't
117+
// know the names of the imported functions at code generation time.
118+
return;
119+
}
120+
114121
gen.current_interface = Some(*id);
115122
gen.types(*id);
116123
gen.generate_trappable_error_types(TypeOwner::Interface(*id));
@@ -157,10 +164,16 @@ impl Wasmtime {
157164
}
158165
WorldItem::Type(_) => unreachable!(),
159166
WorldItem::Interface(id) => {
167+
let iface = &resolve.interfaces[*id];
168+
if iface.functions.len() == 1 && "*" == iface.functions.keys().next().unwrap() {
169+
// Skip wildcard interfaces and let the application call `typed_func` directly since we can't
170+
// know the names of the exported functions at code generation time.
171+
return;
172+
}
173+
160174
gen.current_interface = Some(*id);
161175
gen.types(*id);
162176
gen.generate_trappable_error_types(TypeOwner::Interface(*id));
163-
let iface = &resolve.interfaces[*id];
164177

165178
let camel = to_rust_upper_camel_case(name);
166179
uwriteln!(gen.src, "pub struct {camel} {{");

tests/all/component_model/bindgen.rs

+118
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,121 @@ mod one_import {
113113
Ok(())
114114
}
115115
}
116+
117+
mod wildcards {
118+
use super::*;
119+
use component_test_util::TypedFuncExt;
120+
121+
// We won't actually use any code this produces, but it's here to assert that the macro doesn't get confused by
122+
// wildcards:
123+
wasmtime::component::bindgen!({
124+
inline: "
125+
default world wildcards {
126+
import imports: interface {
127+
*: func() -> u32
128+
}
129+
export exports: interface {
130+
*: func() -> u32
131+
}
132+
}
133+
"
134+
});
135+
136+
fn lambda(
137+
v: u32,
138+
) -> Box<dyn Fn(wasmtime::StoreContextMut<'_, ()>, ()) -> Result<(u32,)> + Send + Sync> {
139+
Box::new(move |_, _| Ok((v,)))
140+
}
141+
142+
#[test]
143+
fn run() -> Result<()> {
144+
let engine = engine();
145+
146+
let component = Component::new(
147+
&engine,
148+
r#"
149+
(component
150+
(import "imports" (instance $i
151+
(export "a" (func (result u32)))
152+
(export "b" (func (result u32)))
153+
(export "c" (func (result u32)))
154+
))
155+
(core module $m
156+
(import "" "a" (func (result i32)))
157+
(import "" "b" (func (result i32)))
158+
(import "" "c" (func (result i32)))
159+
(export "x" (func 0))
160+
(export "y" (func 1))
161+
(export "z" (func 2))
162+
)
163+
(core func $a (canon lower (func $i "a")))
164+
(core func $b (canon lower (func $i "b")))
165+
(core func $c (canon lower (func $i "c")))
166+
(core instance $j (instantiate $m
167+
(with "" (instance
168+
(export "a" (func $a))
169+
(export "b" (func $b))
170+
(export "c" (func $c))
171+
))
172+
))
173+
(func $x (result u32) (canon lift (core func $j "x")))
174+
(func $y (result u32) (canon lift (core func $j "y")))
175+
(func $z (export "z") (result u32) (canon lift (core func $j "z")))
176+
(instance $k
177+
(export "x" (func $x))
178+
(export "y" (func $y))
179+
(export "z" (func $z))
180+
)
181+
(export "exports" (instance $k))
182+
)
183+
"#,
184+
)?;
185+
186+
let mut linker = Linker::<()>::new(&engine);
187+
let mut instance = linker.instance("imports")?;
188+
// In this simple test case, we don't really need to use `Component::names` to discover the imported
189+
// function names, but in the general case, we would, so we verify they're all present.
190+
for name in component.names("imports") {
191+
instance.func_wrap(
192+
name,
193+
match name {
194+
"a" => lambda(42),
195+
"b" => lambda(43),
196+
"c" => lambda(44),
197+
_ => unreachable!(),
198+
},
199+
)?;
200+
}
201+
202+
let mut store = Store::new(&engine, ());
203+
let instance = linker.instantiate(&mut store, &component)?;
204+
let (mut x, mut y, mut z) = (None, None, None);
205+
206+
{
207+
let mut exports = instance.exports(&mut store);
208+
let mut exports = exports.instance("exports").unwrap();
209+
// In this simple test case, we don't really need to use `ExportInstance::funcs` to discover the
210+
// exported function names, but in the general case, we would, so we verify they're all present.
211+
for (name, func) in exports.funcs() {
212+
match name {
213+
"x" => x = Some(func),
214+
"y" => y = Some(func),
215+
"z" => z = Some(func),
216+
_ => unreachable!(),
217+
}
218+
}
219+
};
220+
221+
let (x, y, z) = (
222+
x.unwrap().typed::<(), (u32,)>(&store)?,
223+
y.unwrap().typed::<(), (u32,)>(&store)?,
224+
z.unwrap().typed::<(), (u32,)>(&store)?,
225+
);
226+
227+
assert_eq!(42, x.call_and_post_return(&mut store, ())?.0);
228+
assert_eq!(43, y.call_and_post_return(&mut store, ())?.0);
229+
assert_eq!(44, z.call_and_post_return(&mut store, ())?.0);
230+
231+
Ok(())
232+
}
233+
}

0 commit comments

Comments
 (0)