Skip to content

Commit e566d45

Browse files
authored
fix lowering results for async exports (#1129)
Previously, we were either dropping the result too early (e.g. `wit_bindgen_rt::async_support::ErrorContext`) or not at all (e.g. `String` and `Vec`) due to the lowering code expecting to pass ownership to the caller, which would later free memory using a post-return function. But async exports don't have post-return functions; they use `task.return` instead, and they should free any memory (and/or drop handles) after `task.return` returns. So now we do that! Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 5905093 commit e566d45

File tree

4 files changed

+24
-9
lines changed

4 files changed

+24
-9
lines changed

crates/core/src/abi.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,11 +1474,12 @@ impl<'a, B: Bindgen> Generator<'a, B> {
14741474
}
14751475

14761476
fn list_realloc(&self) -> Option<&'static str> {
1477-
// Lowering parameters calling a wasm import means
1478-
// we don't need to pass ownership, but we pass
1479-
// ownership in all other cases.
1480-
match (self.variant, self.lift_lower) {
1481-
(AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults) => None,
1477+
// Lowering parameters calling a wasm import _or_ returning a result
1478+
// from an async-lifted wasm export means we don't need to pass
1479+
// ownership, but we pass ownership in all other cases.
1480+
match (self.variant, self.lift_lower, self.async_) {
1481+
(AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, _)
1482+
| (AbiVariant::GuestExport, LiftLower::LiftArgsLowerResults, true) => None,
14821483
_ => Some("cabi_realloc"),
14831484
}
14841485
}

crates/guest-rust/rt/src/async_support.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ unsafe fn poll(state: *mut FutureState) -> Poll<()> {
122122
#[doc(hidden)]
123123
pub fn first_poll<T: 'static>(
124124
future: impl Future<Output = T> + 'static,
125-
fun: impl FnOnce(T) + 'static,
125+
fun: impl FnOnce(&T) + 'static,
126126
) -> *mut u8 {
127127
let state = Box::into_raw(Box::new(FutureState {
128128
todo: 0,
129129
tasks: Some(
130-
[Box::pin(future.map(fun)) as BoxFuture]
130+
[Box::pin(future.map(|v| fun(&v))) as BoxFuture]
131131
.into_iter()
132132
.collect(),
133133
),

crates/rust/src/bindgen.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub(super) struct FunctionBindgen<'a, 'b> {
2121
pub handle_decls: Vec<String>,
2222
always_owned: bool,
2323
pub async_result_name: Option<String>,
24+
emitted_cleanup: bool,
2425
}
2526

2627
impl<'a, 'b> FunctionBindgen<'a, 'b> {
@@ -47,10 +48,15 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
4748
handle_decls: Vec::new(),
4849
always_owned,
4950
async_result_name: None,
51+
emitted_cleanup: false,
5052
}
5153
}
5254

5355
fn emit_cleanup(&mut self) {
56+
if self.emitted_cleanup {
57+
return;
58+
}
59+
self.emitted_cleanup = true;
5460
for (ptr, layout) in mem::take(&mut self.cleanup) {
5561
let alloc = self.gen.path_to_std_alloc_module();
5662
self.push_str(&format!(
@@ -1001,10 +1007,11 @@ impl Bindgen for FunctionBindgen<'_, '_> {
10011007
self.src,
10021008
"\
10031009
{func}({});
1004-
}});
10051010
",
10061011
operands.join(", ")
10071012
);
1013+
self.emit_cleanup();
1014+
self.src.push_str("});\n");
10081015
}
10091016

10101017
Instruction::Flush { amt } => {

crates/rust/src/interface.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,14 @@ pub mod vtable{ordinal} {{
10881088
async_result_name,
10891089
..
10901090
} = f;
1091-
assert!(!needs_cleanup_list);
1091+
if async_ {
1092+
if needs_cleanup_list {
1093+
let vec = self.path_to_vec();
1094+
uwriteln!(self.src, "let mut cleanup_list = {vec}::new();");
1095+
}
1096+
} else {
1097+
assert!(!needs_cleanup_list);
1098+
}
10921099
if let Some(name) = async_result_name {
10931100
// When `async_result_name` is `Some(_)`, we wrap the call and any
10941101
// `handle_decls` in a block scope to ensure any resource handles

0 commit comments

Comments
 (0)