Skip to content

Commit

Permalink
Add the ability to generate async drop methods for resources.
Browse files Browse the repository at this point in the history
In the component model, `resource.drop` is a canonical built-in without a proper name. So I invented a custom naming scheme for the component bindgen config. I went with:
`"[drop]{resource-name}"` where `{resource-name}` is the name as defined in WIT. e.g. `"[drop]input-stream"`.

This shouldn't conflict with anything existing in the wild as WIT identifiers are not allowed to contain square brackets.
  • Loading branch information
badeend committed Aug 8, 2024
1 parent 895180d commit 140772a
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 27 deletions.
30 changes: 30 additions & 0 deletions crates/wasmtime/src/runtime/component/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,36 @@ impl<T> LinkerInstance<'_, T> {
Ok(())
}

/// Identical to [`Self::resource`], except that it takes an async destructor.
pub fn resource_async<F>(&mut self, name: &str, ty: ResourceType, dtor: F) -> Result<()>
where
F: for<'a> Fn(
StoreContextMut<'a, T>,
u32,
) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
+ Send
+ Sync
+ 'static,
{
assert!(
self.engine.config().async_support,
"cannot use `resource_async` without enabling async support in the config"
);
let dtor = Arc::new(crate::func::HostFunc::wrap_inner(
&self.engine,
move |mut cx: crate::Caller<'_, T>, (param,): (u32,)| {
let async_cx = cx.as_context_mut().0.async_cx().expect("async cx");
let mut future = Pin::from(dtor(cx.as_context_mut(), param));
match unsafe { async_cx.block_on(future.as_mut()) } {
Ok(Ok(())) => Ok(()),
Ok(Err(trap)) | Err(trap) => Err(trap),
}
},
));
self.insert(name, Definition::Resource(ty, dtor))?;
Ok(())
}

/// Defines a nested instance within this instance.
///
/// This can be used to describe arbitrarily nested levels of instances
Expand Down
106 changes: 79 additions & 27 deletions crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ pub struct TrappableError {
pub rust_type_name: String,
}

/// Which imports should be generated as async functions.
///
/// The imports should be declared in the following format:
/// - Regular functions: `"{function-name}"`
/// - Resource methods: `"[method]{resource-name}.{method-name}"`
/// - Resource destructors: `"[drop]{resource-name}"`
///
/// Examples:
/// - Regular function: `"get-environment"`
/// - Resource method: `"[method]input-stream.read"`
/// - Resource destructor: `"[drop]input-stream"`
#[derive(Default, Debug, Clone)]
pub enum AsyncConfig {
/// No functions are `async`.
Expand All @@ -205,6 +216,10 @@ impl AsyncConfig {
}
}

pub fn is_drop_async(&self, r: &str) -> bool {
self.is_import_async(&format!("[drop]{r}"))
}

pub fn maybe_async(&self) -> bool {
match self {
AsyncConfig::None => false,
Expand Down Expand Up @@ -1318,17 +1333,12 @@ impl Wasmtime {
"
);
for name in get_world_resources(resolve, world) {
let camel = name.to_upper_camel_case();
uwriteln!(
self.src,
"
linker.resource(
\"{name}\",
{wt}::component::ResourceType::host::<{camel}>(),
move |mut store, rep| -> {wt}::Result<()> {{
Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep))
}},
)?;"
Self::generate_add_resource_to_linker(
&mut self.src,
&self.opts,
&wt,
"linker",
name,
);
}
for f in self.import_functions.iter() {
Expand Down Expand Up @@ -1366,6 +1376,41 @@ impl Wasmtime {
uwriteln!(self.src, "Ok(())\n}}");
}
}

fn generate_add_resource_to_linker(
src: &mut Source,
opts: &Opts,
wt: &str,
inst: &str,
name: &str,
) {
let camel = name.to_upper_camel_case();
if opts.async_.is_drop_async(name) {
uwriteln!(
src,
"{inst}.resource_async(
\"{name}\",
{wt}::component::ResourceType::host::<{camel}>(),
move |mut store, rep| {{
std::boxed::Box::new(async move {{
Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep)).await
}})
}},
)?;"
)
} else {
uwriteln!(
src,
"{inst}.resource(
\"{name}\",
{wt}::component::ResourceType::host::<{camel}>(),
move |mut store, rep| -> {wt}::Result<()> {{
Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep))
}},
)?;"
)
}
}
}

struct InterfaceGenerator<'a> {
Expand Down Expand Up @@ -1490,6 +1535,9 @@ impl<'a> InterfaceGenerator<'a> {
self.push_str(";\n");
}

if self.gen.opts.async_.is_drop_async(name) {
uwrite!(self.src, "async ");
}
uwrite!(
self.src,
"fn drop(&mut self, rep: {wt}::component::Resource<{camel}>) -> {wt}::Result<()>;"
Expand Down Expand Up @@ -1527,11 +1575,19 @@ impl<'a> InterfaceGenerator<'a> {
}
uwriteln!(self.src, "}}");
}
uwriteln!(self.src, "
fn drop(&mut self, rep: {wt}::component::Resource<{camel}>) -> {wt}::Result<()> {{
Host{camel}::drop(*self, rep)
}}",
);
if self.gen.opts.async_.is_drop_async(name) {
uwriteln!(self.src, "
async fn drop(&mut self, rep: {wt}::component::Resource<{camel}>) -> {wt}::Result<()> {{
Host{camel}::drop(*self, rep).await
}}",
);
} else {
uwriteln!(self.src, "
fn drop(&mut self, rep: {wt}::component::Resource<{camel}>) -> {wt}::Result<()> {{
Host{camel}::drop(*self, rep)
}}",
);
}
uwriteln!(self.src, "}}");
}
} else {
Expand Down Expand Up @@ -2225,17 +2281,13 @@ impl<'a> InterfaceGenerator<'a> {
uwriteln!(self.src, "let mut inst = linker.instance(\"{name}\")?;");

for name in get_resources(self.resolve, id) {
let camel = name.to_upper_camel_case();
uwriteln!(
self.src,
"inst.resource(
\"{name}\",
{wt}::component::ResourceType::host::<{camel}>(),
move |mut store, rep| -> {wt}::Result<()> {{
Host{camel}::drop(&mut host_getter(store.data_mut()), {wt}::component::Resource::new_own(rep))
}},
)?;"
)
Wasmtime::generate_add_resource_to_linker(
&mut self.src,
&self.gen.opts,
&wt,
"inst",
name,
);
}

for (_, func) in iface.functions.iter() {
Expand Down

0 comments on commit 140772a

Please sign in to comment.