Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion crates/component-macro/src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use syn::{Token, braced, token};
use wasmtime_wit_bindgen::{
FunctionConfig, FunctionFilter, FunctionFlags, Opts, Ownership, TrappableError,
};
use wit_parser::{PackageId, Resolve, WorldId};
use wit_parser::{Function, IndexMap, PackageId, Resolve, WorldId, WorldItem, WorldKey};

pub struct Config {
opts: Opts,
Expand All @@ -30,6 +30,34 @@ pub fn expand(input: &mut Config) -> Result<TokenStream> {
return Ok(quote::quote!(#src));
}

let world = &input.resolve.worlds[input.world];
let sync_exports = fn_filter(&input.resolve, &world.exports, |f| !f.kind.is_async());
let async_imports = fn_filter(&input.resolve, &world.imports, |f| f.kind.is_async());
if !sync_exports.is_empty() && !async_imports.is_empty() {
let format_list = |names: &[String]| {
names
.iter()
.map(|n| format!(" - {n}\n"))
.collect::<String>()
};

return Err(Error::new(
Span::call_site(),
format!(
"world `{world}` mixes synchronous exports with asynchronous imports, which is not allowed\n\
\n\
synchronous exports:\n{sync_exports}\n\
asynchronous imports:\n{async_imports}\n\
\n\
help: synchronous exports that call asynchronous imports will trap at runtime. Either mark the \
listed exports `async`, or make the listed imports synchronous",
world = world.name,
sync_exports = format_list(&sync_exports),
async_imports = format_list(&async_imports),
),
));
}

// If a magical `WASMTIME_DEBUG_BINDGEN` environment variable is set then
// place a formatted version of the expanded code into a file. This file
// will then show up in rustc error messages for any codegen issues and can
Expand Down Expand Up @@ -74,6 +102,32 @@ pub fn expand(input: &mut Config) -> Result<TokenStream> {
Ok(contents)
}

/// Get a list of world and interface-level function names that match some predicate
fn fn_filter(
resolve: &Resolve,
items: &IndexMap<WorldKey, WorldItem>,
pred: impl Fn(&Function) -> bool,
) -> Vec<String> {
let mut names = vec![];
for (key, item) in items {
match item {
WorldItem::Function(f) if pred(f) => names.push(f.name.clone()),
WorldItem::Interface { id, .. } => {
let iface = resolve.name_world_key(key);
names.extend(
resolve.interfaces[*id]
.functions
.values()
.filter(|f| pred(f))
.map(|f| format!("{iface}.{}", f.name)),
);
}
_ => {}
}
}
names
}

impl Parse for Config {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let call_site = Span::call_site();
Expand Down
46 changes: 38 additions & 8 deletions crates/wasmtime/src/runtime/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,28 @@ pub(crate) use self::store::ComponentStoreData;
/// // functionality per-function or by default for all imports. Note that
/// // exports are also supported via the `exports` key below.
/// //
/// // Functions in this list are specified as their interface first then
/// // the raw wasm name of the function. Interface versions can be
/// // optionally omitted and prefixes are also supported to configure
/// // entire interfaces at once for example. Only the first matching item
/// // in this list is used to configure a function.
/// // Each key names either one function or a group of functions:
/// //
/// // * `"my-func"` - a function imported directly by the world.
/// // * `"wasi:io/poll.poll"` - the `poll` function in the `wasi:io/poll`
/// // interface. A version is optional: `"wasi:io/poll@0.2.0.poll"`.
/// // * `"wasi:io/poll"` - every function in the `wasi:io/poll` interface.
/// // * `"wasi:io"` or `"wasi"` - every function in a package or namespace.
/// //
/// // Resource functions are named by their component-model name, written
/// // after the interface:
/// //
/// // * `"my:local/api.[constructor]my-resource"`
/// // * `"my:local/api.[method]my-resource.my-method"`
/// // * `"my:local/api.[static]my-resource.my-static-func"`
/// // * `"my:local/api.[drop]my-resource"`
/// //
/// // A resource name on its own (`"my:local/api.my-resource"`) isn't a valid
/// // key. List each resource function by itself, or use the interface key
/// // to cover all of them.
/// //
/// // When more than one key matches a function, the most specific one is
/// // used and the rest are ignored for that function.
/// //
/// // Configuration for a function is a set of flags which can be added
/// // per-function. Each flag's meaning is documented below and the final
Expand Down Expand Up @@ -353,6 +370,14 @@ pub(crate) use self::store::ComponentStoreData;
/// // for example and instead return `Result<WitOk, RustErrorType>`.
/// "my:local/api.fallible": trappable,
///
/// // Resource methods, constructors, and static functions are
/// // configured with their `[method]`, `[constructor]`, `[drop]`, or `[static]`
/// // name as described above.
/// "my:local/api.[method]my-resource.my-method": async | trappable,
/// "my:local/api.[static]my-resource.my-static-func": trappable,
/// "my:local/api.[constructor]my-resource": trappable,
/// "my:local/api.[drop]my-resource": trappable,
///
/// // The `exact` flag ensures that the filter, here "f", only matches
/// // functions exactly. For example "f" here would only refer to
/// // `import f: func()` in a world. Without this flag then "f"
Expand All @@ -369,9 +394,14 @@ pub(crate) use self::store::ComponentStoreData;
///
/// // Mostly the same as `imports` above, but applies to exported functions.
/// //
/// // The one difference here is that, whereas the `task_exit` flag has no
/// // effect for `imports`, it changes how bindings are generated for
/// // exported functions as described below.
/// // Two notable differences from `imports`:
/// // 1. Whereas the `task_exit` flag has no
/// // effect for `imports`, it changes how bindings are generated for
/// // exported functions as described below.
/// // 2. If any import is marked `async`, all exports must be `async` too,
/// // or `bindgen!` reports a compile error; otherwise, a synchronous
/// // function export that calls an `async` function import will trap at
/// // runtime. Synchronous and `async` imports may be freely mixed.
/// exports: {
/// /* ... */
///
Expand Down
Loading