Skip to content

Commit 3b839cf

Browse files
authored
drop borrows earlier for async exports in Rust generator (#1124)
While testing runtime borrow enforcement in Wasmtime, I discovered that the guest code generated by `wit-bindgen` was not dropping borrows early enough for async exports. Specifically, all borrows need to be dropped before `task.return` is called, which is what this patch does. Signed-off-by: Joel Dice <[email protected]>
1 parent 5928056 commit 3b839cf

File tree

2 files changed

+24
-4
lines changed

2 files changed

+24
-4
lines changed

crates/rust/src/bindgen.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub(super) struct FunctionBindgen<'a, 'b> {
2020
pub import_return_pointer_area_align: usize,
2121
pub handle_decls: Vec<String>,
2222
always_owned: bool,
23+
pub async_result_name: Option<String>,
2324
}
2425

2526
impl<'a, 'b> FunctionBindgen<'a, 'b> {
@@ -45,6 +46,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
4546
import_return_pointer_area_align: 0,
4647
handle_decls: Vec::new(),
4748
always_owned,
49+
async_result_name: None,
4850
}
4951
}
5052

@@ -877,10 +879,8 @@ impl Bindgen for FunctionBindgen<'_, '_> {
877879

878880
Instruction::CallInterface { func, .. } => {
879881
if self.async_ {
880-
let tmp = self.tmp();
881-
let result = format!("result{tmp}");
882-
self.push_str(&format!("let {result} = "));
883-
results.push(result);
882+
self.push_str(&format!("let result = "));
883+
results.push("result".into());
884884
} else {
885885
self.let_results(func.results.len(), results);
886886
};
@@ -950,6 +950,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
950950

951951
Instruction::AsyncPostCallInterface { func } => {
952952
let result = &operands[0];
953+
self.async_result_name = Some(result.clone());
953954
results.push("result".into());
954955
let params = (0..func.results.len())
955956
.map(|_| {
@@ -973,12 +974,18 @@ impl Bindgen for FunctionBindgen<'_, '_> {
973974
// `abi::Generator` emit a `AsyncCallReturn` first, which would
974975
// push a closure expression we can consume here).
975976
//
977+
// Also, see `InterfaceGenerator::generate_guest_export` for
978+
// where we emit the open bracket corresponding to the `};` we
979+
// emit here.
980+
//
976981
// The async-specific `Instruction`s will probably need to be
977982
// refactored anyway once we start implementing support for
978983
// other languages besides Rust.
979984
uwriteln!(
980985
self.src,
981986
"\
987+
{result}
988+
}};
982989
let result = {async_support}::first_poll({result}, |{params}| {{
983990
"
984991
);

crates/rust/src/interface.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,9 +1085,22 @@ pub mod vtable{ordinal} {{
10851085
needs_cleanup_list,
10861086
src,
10871087
handle_decls,
1088+
async_result_name,
10881089
..
10891090
} = f;
10901091
assert!(!needs_cleanup_list);
1092+
if let Some(name) = async_result_name {
1093+
// When `async_result_name` is `Some(_)`, we wrap the call and any
1094+
// `handle_decls` in a block scope to ensure any resource handles
1095+
// are dropped before we call `async_support::first_poll`, which may
1096+
// call `task.return` at which point any borrow handles must already
1097+
// have been dropped.
1098+
//
1099+
// The corresponding close bracket is emitted in the
1100+
// `Instruction::AsyncPostCallInterface` case inside
1101+
// `FunctionBindgen::emit`.
1102+
uwriteln!(self.src, "let {name} = {{");
1103+
}
10911104
for decl in handle_decls {
10921105
self.src.push_str(&decl);
10931106
self.src.push_str("\n");

0 commit comments

Comments
 (0)