From b781a7bc93a0d02cbd1da5ba0c3bfff6c92309bf Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 22 Sep 2022 10:55:37 -0500 Subject: [PATCH] Initial work towards getting post-return functions (#310) This adds a few new pseudo-instructions plus a new method on `Interface` to generate a `post-return` function. For now these are simply named `{name}_post_return` and the integration point there will probably change as the component model shapes up. The integration here is intended to still be relatively primitive in that the actual component model integration will likely look different in the future. Given how `wit-bindgen` works today, though, this should at least get things part of the way there. --- crates/gen-guest-c/src/lib.rs | 98 +++++- crates/gen-guest-rust/src/lib.rs | 137 ++++++-- crates/gen-host-js/src/lib.rs | 111 ++---- crates/gen-host-js/tests/runtime.rs | 1 + crates/gen-host-wasmtime-py/src/lib.rs | 121 +++---- crates/gen-host-wasmtime-rust/src/lib.rs | 154 ++++----- .../gen-host-wasmtime-rust/tests/runtime.rs | 2 + crates/guest-rust/src/lib.rs | 9 +- crates/wit-parser/src/abi.rs | 316 +++++++++++++++--- tests/runtime/lists/wasm.c | 28 +- 10 files changed, 602 insertions(+), 375 deletions(-) diff --git a/crates/gen-guest-c/src/lib.rs b/crates/gen-guest-c/src/lib.rs index 2177c22da..1c8cd9eb9 100644 --- a/crates/gen-guest-c/src/lib.rs +++ b/crates/gen-guest-c/src/lib.rs @@ -450,15 +450,6 @@ impl C { abort(); return ret; } - - __attribute__((weak, export_name(\"canonical_abi_free\"))) - void canonical_abi_free( - void *ptr, - size_t size, - size_t align - ) { - free(ptr); - } "); } @@ -518,12 +509,9 @@ impl C { self.free(iface, t, "&ptr->ptr[i]"); self.src.c("}\n"); } - uwriteln!( - self.src.c, - "canonical_abi_free(ptr->ptr, ptr->len * {}, {});", - self.sizes.size(t), - self.sizes.align(t), - ); + uwriteln!(self.src.c, "if (ptr->len > 0) {{"); + uwriteln!(self.src.c, "free(ptr->ptr);"); + uwriteln!(self.src.c, "}}"); } TypeDefKind::Variant(v) => { @@ -1120,6 +1108,42 @@ impl Generator for C { self.src.c(&src); self.src.c("}\n"); + if iface.guest_export_needs_post_return(func) { + uwriteln!( + self.src.c, + "__attribute__((export_name(\"cabi_post_{}\")))", + func.name + ); + uwrite!(self.src.c, "void {import_name}_post_return("); + + let mut params = Vec::new(); + let mut c_sig = CSig { + name: String::from("INVALID"), + sig: String::from("INVALID"), + params: Vec::new(), + ret: Return { + return_multiple: false, + scalar: None, + retptrs: Vec::new(), + }, + retptrs: Vec::new(), + }; + for (i, result) in sig.results.iter().enumerate() { + let name = format!("arg{i}"); + uwrite!(self.src.c, "{} {name}", wasm_type(*result)); + c_sig.params.push((false, name.clone())); + params.push(name); + } + self.src.c.push_str(") {\n"); + + let mut f = FunctionBindgen::new(self, c_sig, &import_name); + f.params = params; + iface.post_return(func, &mut f); + let FunctionBindgen { src, .. } = f; + self.src.c(&src); + self.src.c("}\n"); + } + let src = mem::replace(&mut self.src, prev); self.funcs .entry(iface.name.to_string()) @@ -1299,7 +1323,9 @@ impl Generator for C { }} void {0}_string_free({0}_string_t *ret) {{ - canonical_abi_free(ret->ptr, ret->len, 1); + if (ret->len > 0) {{ + free(ret->ptr); + }} ret->ptr = NULL; ret->len = 0; }} @@ -2204,9 +2230,47 @@ impl Bindgen for FunctionBindgen<'_> { self.load_ext("int16_t", *offset, operands, results) } - Instruction::Free { .. } => { + Instruction::GuestDeallocate { .. } => { uwriteln!(self.src, "free((void*) ({}));", operands[0]); } + Instruction::GuestDeallocateString => { + uwriteln!(self.src, "if (({}) > 0) {{", operands[1]); + uwriteln!(self.src, "free((void*) ({}));", operands[0]); + uwriteln!(self.src, "}}"); + } + Instruction::GuestDeallocateVariant { blocks } => { + let blocks = self + .blocks + .drain(self.blocks.len() - blocks..) + .collect::>(); + + uwriteln!(self.src, "switch ((int32_t) {}) {{", operands[0]); + for (i, (block, results)) in blocks.into_iter().enumerate() { + assert!(results.is_empty()); + uwriteln!(self.src, "case {}: {{", i); + self.src.push_str(&block); + self.src.push_str("break;\n}\n"); + } + self.src.push_str("}\n"); + } + Instruction::GuestDeallocateList { element } => { + let (body, results) = self.blocks.pop().unwrap(); + assert!(results.is_empty()); + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + uwriteln!(self.src, "int32_t {ptr} = {};", operands[0]); + uwriteln!(self.src, "int32_t {len} = {};", operands[1]); + let i = self.locals.tmp("i"); + uwriteln!(self.src, "for (int32_t {i} = 0; {i} < {len}; {i}++) {{"); + let size = self.gen.sizes.size(element); + uwriteln!(self.src, "int32_t base = {ptr} + {i} * {size};"); + uwriteln!(self.src, "(void) base;"); + uwrite!(self.src, "{body}"); + uwriteln!(self.src, "}}"); + uwriteln!(self.src, "if ({len} > 0) {{"); + uwriteln!(self.src, "free((void*) ({ptr}));"); + uwriteln!(self.src, "}}"); + } i => unimplemented!("{:?}", i), } diff --git a/crates/gen-guest-rust/src/lib.rs b/crates/gen-guest-rust/src/lib.rs index e07b8d1e5..c574ac0c2 100644 --- a/crates/gen-guest-rust/src/lib.rs +++ b/crates/gen-guest-rust/src/lib.rs @@ -528,23 +528,20 @@ impl Generator for RustWasm { fn export(&mut self, iface: &Interface, func: &Function) { let iface_name = iface.name.to_snake_case(); - self.src.push_str("#[export_name = \""); - match &iface.module { + let name_mangled = iface.mangle_funcname(func); + let name_snake = func.name.to_snake_case(); + let name = match &iface.module { Some(module) => { - self.src.push_str(module); - self.src.push_str("#"); - self.src.push_str(&iface.mangle_funcname(func)); + format!("{module}#{}", name_mangled) } - None => { - self.src.push_str(&self.opts.symbol_namespace); - self.src.push_str(&iface.mangle_funcname(func)); - } - } - self.src.push_str("\"]\n"); + None => format!("{}{}", self.opts.symbol_namespace, name_mangled), + }; + + self.src.push_str(&format!("#[export_name = \"{name}\"]\n")); self.src.push_str("unsafe extern \"C\" fn __wit_bindgen_"); self.src.push_str(&iface_name); self.src.push_str("_"); - self.src.push_str(&func.name.to_snake_case()); + self.src.push_str(&name_snake); self.src.push_str("("); let sig = iface.wasm_signature(AbiVariant::GuestExport, func); let mut params = Vec::new(); @@ -594,6 +591,37 @@ impl Generator for RustWasm { self.src.push_str(&String::from(src)); self.src.push_str("}\n"); + if iface.guest_export_needs_post_return(func) { + self.src.push_str(&format!( + "#[export_name = \"{}cabi_post_{}\"]\n", + self.opts.symbol_namespace, func.name, + )); + self.src.push_str(&format!( + "unsafe extern \"C\" fn __wit_bindgen_{iface_name}_{name_snake}_post_return(" + )); + let mut params = Vec::new(); + for (i, result) in sig.results.iter().enumerate() { + let name = format!("arg{}", i); + self.src.push_str(&name); + self.src.push_str(": "); + self.wasm_type(*result); + self.src.push_str(", "); + params.push(name); + } + self.src.push_str(") {\n"); + + let mut f = FunctionBindgen::new(self, params); + iface.post_return(func, &mut f); + let FunctionBindgen { + needs_cleanup_list, + src, + .. + } = f; + assert!(!needs_cleanup_list); + self.src.push_str(&String::from(src)); + self.src.push_str("}\n"); + } + let prev = mem::take(&mut self.src); self.in_trait = true; let mut sig = FnSig::default(); @@ -1290,10 +1318,7 @@ impl Bindgen for FunctionBindgen<'_> { results.push(len); } - Instruction::ListCanonLift { free, .. } => { - // This only happens when we're receiving a list from the - // outside world, so `free` should always be `Some`. - assert!(free.is_some()); + Instruction::ListCanonLift { .. } => { let tmp = self.tmp(); let len = format!("len{}", tmp); self.push_str(&format!("let {} = {} as usize;\n", len, operands[1])); @@ -1324,10 +1349,7 @@ impl Bindgen for FunctionBindgen<'_> { results.push(len); } - Instruction::StringLift { free, .. } => { - // This only happens when we're receiving a string from the - // outside world, so `free` should always be `Some`. - assert!(free.is_some()); + Instruction::StringLift => { let tmp = self.tmp(); let len = format!("len{}", tmp); self.push_str(&format!("let {} = {} as usize;\n", len, operands[1])); @@ -1383,10 +1405,7 @@ impl Bindgen for FunctionBindgen<'_> { } } - Instruction::ListLift { element, free, .. } => { - // This only happens when we're receiving a list from the - // outside world, so `free` should always be `Some`. - assert!(free.is_some()); + Instruction::ListLift { element, .. } => { let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); let size = self.gen.sizes.size(element); @@ -1421,7 +1440,7 @@ impl Bindgen for FunctionBindgen<'_> { self.push_str("}\n"); results.push(result); self.push_str(&format!( - "if {len} != 0 {{\nstd::alloc::dealloc({base} as *mut _, std::alloc::Layout::from_size_align_unchecked(({len} as usize) * {size}, {align}));\n}}\n", + "wit_bindgen_guest_rust::rt::dealloc({base}, ({len} as usize) * {size}, {align});\n", )); } @@ -1575,16 +1594,72 @@ impl Bindgen for FunctionBindgen<'_> { } Instruction::Malloc { .. } => unimplemented!(), - Instruction::Free { - free: _, - size, - align, - } => { + + Instruction::GuestDeallocate { size, align } => { self.push_str(&format!( - "wit_bindgen_guest_rust::rt::canonical_abi_free({} as *mut u8, {}, {});\n", + "wit_bindgen_guest_rust::rt::dealloc({}, {}, {});\n", operands[0], size, align )); } + + Instruction::GuestDeallocateString => { + self.push_str(&format!( + "wit_bindgen_guest_rust::rt::dealloc({}, ({}) as usize, 1);\n", + operands[0], operands[1], + )); + } + + Instruction::GuestDeallocateVariant { blocks } => { + let max = blocks - 1; + let blocks = self + .blocks + .drain(self.blocks.len() - blocks..) + .collect::>(); + let op0 = &operands[0]; + self.src.push_str(&format!("match {op0} {{\n")); + for (i, block) in blocks.into_iter().enumerate() { + let pat = if i == max { + String::from("_") + } else { + i.to_string() + }; + self.src.push_str(&format!("{pat} => {block},\n")); + } + self.src.push_str("}\n"); + } + + Instruction::GuestDeallocateList { element } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + let len = format!("len{tmp}"); + let base = format!("base{tmp}"); + self.push_str(&format!( + "let {base} = {operand0};\n", + operand0 = operands[0] + )); + self.push_str(&format!( + "let {len} = {operand1};\n", + operand1 = operands[1] + )); + + if body != "()" { + self.push_str("for i in 0.."); + self.push_str(&len); + self.push_str(" {\n"); + self.push_str("let base = "); + self.push_str(&base); + self.push_str(" + i *"); + self.push_str(&size.to_string()); + self.push_str(";\n"); + self.push_str(&body); + self.push_str("\n}\n"); + } + self.push_str(&format!( + "wit_bindgen_guest_rust::rt::dealloc({base}, ({len} as usize) * {size}, {align});\n", + )); + } } } } diff --git a/crates/gen-host-js/src/lib.rs b/crates/gen-host-js/src/lib.rs index 1dbbf4faa..90a608727 100644 --- a/crates/gen-host-js/src/lib.rs +++ b/crates/gen-host-js/src/lib.rs @@ -84,7 +84,6 @@ enum Intrinsic { Utf8EncodedLen, Slab, Promises, - WithCurrentPromise, ThrowInvalidBool, } @@ -113,7 +112,6 @@ impl Intrinsic { Intrinsic::Utf8EncodedLen => "UTF8_ENCODED_LEN", Intrinsic::Slab => "Slab", Intrinsic::Promises => "PROMISES", - Intrinsic::WithCurrentPromise => "with_current_promise", Intrinsic::ThrowInvalidBool => "throw_invalid_bool", } } @@ -643,7 +641,6 @@ impl Generator for Js { src, needs_memory, needs_realloc, - needs_free, .. } = f; @@ -659,11 +656,6 @@ impl Generator for Js { .js(&format!("const realloc = get_export(\"{}\");\n", name)); } - if let Some(name) = needs_free { - self.needs_get_export = true; - self.src - .js(&format!("const free = get_export(\"{}\");\n", name)); - } self.src.js(&src.js); self.src.js("}"); @@ -736,7 +728,6 @@ impl Generator for Js { src, needs_memory, needs_realloc, - needs_free, src_object, .. } = f; @@ -753,12 +744,6 @@ impl Generator for Js { )); } - if let Some(name) = needs_free { - self.src.js(&format!( - "const free = {}._exports[\"{}\"];\n", - src_object, name - )); - } self.src.js(&src.js); self.src.js("}\n"); @@ -1169,7 +1154,6 @@ struct FunctionBindgen<'a> { blocks: Vec<(String, Vec)>, needs_memory: bool, needs_realloc: Option, - needs_free: Option, params: Vec, src_object: String, } @@ -1184,7 +1168,6 @@ impl FunctionBindgen<'_> { blocks: Vec::new(), needs_memory: false, needs_realloc: None, - needs_free: None, params, src_object: "this".to_string(), } @@ -2041,32 +2024,18 @@ impl Bindgen for FunctionBindgen<'_> { results.push(format!("ptr{}", tmp)); results.push(format!("len{}", tmp)); } - Instruction::ListCanonLift { element, free, .. } => { + Instruction::ListCanonLift { element, .. } => { self.needs_memory = true; let tmp = self.tmp(); - self.src - .js(&format!("const ptr{} = {};\n", tmp, operands[0])); - self.src - .js(&format!("const len{} = {};\n", tmp, operands[1])); + self.src.js(&format!("const ptr{tmp} = {};\n", operands[0])); + self.src.js(&format!("const len{tmp} = {};\n", operands[1])); // TODO: this is the wrong endianness let array_ty = self.gen.array_ty(iface, element).unwrap(); - let result = format!( - "new {}(memory.buffer.slice(ptr{}, ptr{1} + len{1} * {}))", - array_ty, - tmp, + self.src.js(&format!( + "const result{tmp} = new {array_ty}(memory.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {}));\n", self.gen.sizes.size(element), - ); - let align = self.gen.sizes.align(element); - match free { - Some(free) => { - self.needs_free = Some(free.to_string()); - self.src.js(&format!("const list{} = {};\n", tmp, result)); - self.src - .js(&format!("free(ptr{}, len{0}, {});\n", tmp, align)); - results.push(format!("list{}", tmp)); - } - None => results.push(result), - } + )); + results.push(format!("result{tmp}")); } Instruction::StringLower { realloc } => { // Lowering only happens when we're passing strings into wasm, @@ -2089,27 +2058,16 @@ impl Bindgen for FunctionBindgen<'_> { results.push(format!("ptr{}", tmp)); results.push(format!("len{}", tmp)); } - Instruction::StringLift { free } => { + Instruction::StringLift => { self.needs_memory = true; let tmp = self.tmp(); - self.src - .js(&format!("const ptr{} = {};\n", tmp, operands[0])); - self.src - .js(&format!("const len{} = {};\n", tmp, operands[1])); + self.src.js(&format!("const ptr{tmp} = {};\n", operands[0])); + self.src.js(&format!("const len{tmp} = {};\n", operands[1])); let decoder = self.gen.intrinsic(Intrinsic::Utf8Decoder); - let result = format!( - "{}.decode(new Uint8Array(memory.buffer, ptr{}, len{1}))", - decoder, tmp, - ); - match free { - Some(free) => { - self.needs_free = Some(free.to_string()); - self.src.js(&format!("const list{} = {};\n", tmp, result)); - self.src.js(&format!("free(ptr{}, len{0}, 1);\n", tmp)); - results.push(format!("list{}", tmp)); - } - None => results.push(result), - } + self.src.js(&format!( + "const result{tmp} = {decoder}.decode(new Uint8Array(memory.buffer, ptr{tmp}, len{tmp}));\n", + )); + results.push(format!("result{tmp}")); } Instruction::ListLower { element, realloc } => { @@ -2149,11 +2107,10 @@ impl Bindgen for FunctionBindgen<'_> { results.push(len); } - Instruction::ListLift { element, free, .. } => { + Instruction::ListLift { element, .. } => { let (body, body_results) = self.blocks.pop().unwrap(); let tmp = self.tmp(); let size = self.gen.sizes.size(element); - let align = self.gen.sizes.align(element); let len = format!("len{}", tmp); self.src.js(&format!("const {} = {};\n", len, operands[1])); let base = format!("base{}", tmp); @@ -2171,12 +2128,6 @@ impl Bindgen for FunctionBindgen<'_> { self.src .js(&format!("{}.push({});\n", result, body_results[0])); self.src.js("}\n"); - - if let Some(free) = free { - self.needs_free = Some(free.to_string()); - self.src - .js(&format!("free({}, {} * {}, {});\n", base, len, size, align,)); - } } Instruction::IterElem { .. } => results.push("e".to_string()), @@ -2244,11 +2195,21 @@ impl Bindgen for FunctionBindgen<'_> { self.src.js(";\n"); } - Instruction::Return { amt, func: _ } => match amt { - 0 => {} - 1 => self.src.js(&format!("return {};\n", operands[0])), - _ => self.src.js(&format!("return [{}];\n", operands.join(", "))), - }, + Instruction::Return { amt, func } => { + if !self.gen.in_import && iface.guest_export_needs_post_return(func) { + let name = &func.name; + self.src.js(&format!( + "{}._exports[\"cabi_post_{name}\"](ret);\n", + self.src_object + )); + } + + match amt { + 0 => {} + 1 => self.src.js(&format!("return {};\n", operands[0])), + _ => self.src.js(&format!("return [{}];\n", operands.join(", "))), + } + } Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results), Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results), @@ -2532,18 +2493,6 @@ impl Js { "), Intrinsic::Promises => self.src.js("export const PROMISES = new Slab();\n"), - Intrinsic::WithCurrentPromise => self.src.js(" - let CUR_PROMISE = null; - export function with_current_promise(val, closure) { - const prev = CUR_PROMISE; - CUR_PROMISE = val; - try { - closure(prev); - } finally { - CUR_PROMISE = prev; - } - } - "), Intrinsic::ThrowInvalidBool => self.src.js(" export function throw_invalid_bool() { throw new RangeError(\"invalid variant discriminant for bool\"); diff --git a/crates/gen-host-js/tests/runtime.rs b/crates/gen-host-js/tests/runtime.rs index 3c2e846ff..f7dd67d1f 100644 --- a/crates/gen-host-js/tests/runtime.rs +++ b/crates/gen-host-js/tests/runtime.rs @@ -78,6 +78,7 @@ fn execute(name: &str, wasm: &Path, ts: &Path, imports: &Path, exports: &Path) { println!("{:?}", std::env::join_paths(&path)); run(Command::new("node") .arg("--experimental-wasi-unstable-preview1") + .arg("--stack-trace-limit=1000") .arg(dir.join("host.js")) .env("NODE_PATH", std::env::join_paths(&path).unwrap()) .arg(wasm)); diff --git a/crates/gen-host-wasmtime-py/src/lib.rs b/crates/gen-host-wasmtime-py/src/lib.rs index 3b314ea32..6ef7d5bbd 100644 --- a/crates/gen-host-wasmtime-py/src/lib.rs +++ b/crates/gen-host-wasmtime-py/src/lib.rs @@ -407,7 +407,6 @@ impl Generator for WasmtimePy { src, needs_memory, needs_realloc, - needs_free, mut locals, .. } = f; @@ -428,11 +427,6 @@ impl Generator for WasmtimePy { locals.insert("realloc").unwrap(); } - if let Some(name) = needs_free { - builder.push_str(&format!("free = caller[\"{}\"]\n", name)); - builder.push_str("assert(isinstance(free, wasmtime.Func))\n"); - locals.insert("free").unwrap(); - } builder.push_str(&src); builder.dedent(); @@ -507,7 +501,6 @@ impl Generator for WasmtimePy { src, needs_memory, needs_realloc, - needs_free, src_object, .. } = f; @@ -525,13 +518,6 @@ impl Generator for WasmtimePy { )); } - if let Some(name) = &needs_free { - builder.push_str(&format!( - "free = {}._{}\n", - src_object, - name.to_snake_case(), - )); - } builder.push_str(&src); builder.dedent(); @@ -557,15 +543,6 @@ impl Generator for WasmtimePy { }, ); } - if let Some(name) = &needs_free { - exports.fields.insert( - name.clone(), - Export { - python_type: "wasmtime.Func", - base_name: name.clone(), - }, - ); - } exports.fields.insert( iface.mangle_funcname(func), Export { @@ -887,7 +864,6 @@ struct FunctionBindgen<'a> { blocks: Vec<(String, Vec)>, needs_memory: bool, needs_realloc: Option, - needs_free: Option, params: Vec, payloads: Vec, src_object: String, @@ -910,7 +886,6 @@ impl FunctionBindgen<'_> { blocks: Vec::new(), needs_memory: false, needs_realloc: None, - needs_free: None, params, payloads: Vec::new(), src_object: "self".to_string(), @@ -1588,7 +1563,7 @@ impl Bindgen for FunctionBindgen<'_> { results.push(ptr); results.push(len); } - Instruction::ListCanonLift { element, free, .. } => { + Instruction::ListCanonLift { element, .. } => { self.needs_memory = true; let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); @@ -1604,32 +1579,14 @@ impl Bindgen for FunctionBindgen<'_> { array_ty, ); builder.deps.pyimport("typing", "cast"); - let align = self.gen.sizes.align(element); - match free { - Some(free) => { - self.needs_free = Some(free.to_string()); - let list = self.locals.tmp("list"); - builder.push_str(&list); - builder.push_str(" = cast("); - builder.print_list(element); - builder.push_str(", "); - builder.push_str(&lift); - builder.push_str(")\n"); - builder.push_str(&format!("free(caller, {}, {}, {})\n", ptr, len, align)); - results.push(list); - } - None => { - let mut result_src = Source::default(); - drop(builder); - let mut builder = result_src.builder(&mut self.gen.deps, iface); - builder.push_str("cast("); - builder.print_list(element); - builder.push_str(", "); - builder.push_str(&lift); - builder.push_str(")"); - results.push(result_src.to_string()); - } - } + let list = self.locals.tmp("list"); + builder.push_str(&list); + builder.push_str(" = cast("); + builder.print_list(element); + builder.push_str(", "); + builder.push_str(&lift); + builder.push_str(")\n"); + results.push(list); } Instruction::StringLower { realloc } => { // Lowering only happens when we're passing strings into wasm, @@ -1649,7 +1606,7 @@ impl Bindgen for FunctionBindgen<'_> { results.push(ptr); results.push(len); } - Instruction::StringLift { free, .. } => { + Instruction::StringLift => { self.needs_memory = true; let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); @@ -1657,17 +1614,9 @@ impl Bindgen for FunctionBindgen<'_> { builder.push_str(&format!("{} = {}\n", len, operands[1])); builder.deps.needs_decode_utf8 = true; let result = format!("_decode_utf8(memory, caller, {}, {})", ptr, len); - match free { - Some(free) => { - self.needs_free = Some(free.to_string()); - let list = self.locals.tmp("list"); - builder.push_str(&format!("{} = {}\n", list, result)); - self.src - .push_str(&format!("free(caller, {}, {}, 1)\n", ptr, len)); - results.push(list); - } - None => results.push(result), - } + let list = self.locals.tmp("list"); + builder.push_str(&format!("{} = {}\n", list, result)); + results.push(list); } Instruction::ListLower { element, realloc } => { @@ -1709,11 +1658,10 @@ impl Bindgen for FunctionBindgen<'_> { results.push(len); } - Instruction::ListLift { element, free, .. } => { + Instruction::ListLift { element, .. } => { let (body, body_results) = self.blocks.pop().unwrap(); let base = self.payloads.pop().unwrap(); let size = self.gen.sizes.size(element); - let align = self.gen.sizes.align(element); let ptr = self.locals.tmp("ptr"); let len = self.locals.tmp("len"); builder.push_str(&format!("{} = {}\n", ptr, operands[0])); @@ -1731,14 +1679,6 @@ impl Bindgen for FunctionBindgen<'_> { assert_eq!(body_results.len(), 1); builder.push_str(&format!("{}.append({})\n", result, body_results[0])); builder.dedent(); - - if let Some(free) = free { - self.needs_free = Some(free.to_string()); - builder.push_str(&format!( - "free(caller, {}, {} * {}, {})\n", - ptr, len, size, align, - )); - } results.push(result); } @@ -1819,14 +1759,33 @@ impl Bindgen for FunctionBindgen<'_> { builder.push_str("\n"); } - Instruction::Return { amt, .. } => match amt { - 0 => {} - 1 => builder.push_str(&format!("return {}\n", operands[0])), - _ => { - self.src - .push_str(&format!("return ({})\n", operands.join(", "))); + Instruction::Return { amt, func, .. } => { + if !self.gen.in_import && iface.guest_export_needs_post_return(func) { + let name = format!("cabi_post_{}", func.name); + let exports = self + .gen + .guest_exports + .entry(iface.name.to_string()) + .or_insert_with(Exports::default); + exports.fields.insert( + name.clone(), + Export { + python_type: "wasmtime.Func", + base_name: name.clone(), + }, + ); + let name = name.to_snake_case(); + builder.push_str(&format!("{}._{name}(caller, ret)\n", self.src_object)); } - }, + match amt { + 0 => {} + 1 => builder.push_str(&format!("return {}\n", operands[0])), + _ => { + self.src + .push_str(&format!("return ({})\n", operands.join(", "))); + } + } + } Instruction::I32Load { offset } => self.load("c_int32", *offset, operands, results), Instruction::I64Load { offset } => self.load("c_int64", *offset, operands, results), diff --git a/crates/gen-host-wasmtime-rust/src/lib.rs b/crates/gen-host-wasmtime-rust/src/lib.rs index bd7f2ee7d..0da6744a2 100644 --- a/crates/gen-host-wasmtime-rust/src/lib.rs +++ b/crates/gen-host-wasmtime-rust/src/lib.rs @@ -38,7 +38,6 @@ pub struct Wasmtime { enum NeededFunction { Realloc, - Free, } struct Import { @@ -507,7 +506,6 @@ impl Generator for Wasmtime { ); let FunctionBindgen { src, - cleanup, needs_borrow_checker, needs_memory, needs_buffer_transaction, @@ -515,7 +513,6 @@ impl Generator for Wasmtime { closures, .. } = f; - assert!(cleanup.is_none()); assert!(!needs_buffer_transaction); // Generate the signature this function will have in the final trait @@ -633,6 +630,7 @@ impl Generator for Wasmtime { fn import(&mut self, iface: &Interface, func: &Function) { let prev = mem::take(&mut self.src); + let wasm_sig = iface.wasm_signature(AbiVariant::GuestExport, func); let mut sig = FnSig::default(); sig.self_arg = Some("&self, mut caller: impl wasmtime::AsContextMut".to_string()); self.print_docs_and_params(iface, func, TypeMode::AllBorrowed("'_"), &sig); @@ -691,6 +689,23 @@ impl Generator for Wasmtime { ); exports.fields.insert(name, (func.ty(), get)); } + if iface.guest_export_needs_post_return(func) { + let name = func.name.to_snake_case(); + self.src + .push_str(&format!("let post_return = &self.{name}_post_return;\n")); + let ret = match wasm_sig.results.len() { + 1 => wasm_type(wasm_sig.results[0]), + _ => unimplemented!(), + }; + let get = format!( + "instance.get_typed_func::<{ret}, (), _>(&mut store, \"cabi_post_{}\")?", + func.name + ); + exports.fields.insert( + format!("{name}_post_return"), + (format!("wasmtime::TypedFunc<{ret}, ()>"), get), + ); + } self.src.push_str(&closures); @@ -726,14 +741,13 @@ impl Generator for Wasmtime { // Create the code snippet which will define the type of this field in // the struct that we're exporting and additionally extracts the // function from an instantiated instance. - let sig = iface.wasm_signature(AbiVariant::GuestExport, func); let mut cvt = "(".to_string(); - for param in sig.params.iter() { + for param in wasm_sig.params.iter() { cvt.push_str(wasm_type(*param)); cvt.push_str(","); } cvt.push_str("), ("); - for result in sig.results.iter() { + for result in wasm_sig.results.iter() { cvt.push_str(wasm_type(*result)); cvt.push_str(","); } @@ -1179,9 +1193,6 @@ struct FunctionBindgen<'a> { // Whether or not the `caller_memory` variable has been defined and is // available for use. caller_memory_available: bool, - // Code that must be executed before a return, generated during instruction - // lowering. - cleanup: Option, // Rust clousures for buffers that must be placed at the front of the // function. @@ -1193,6 +1204,9 @@ struct FunctionBindgen<'a> { needs_borrow_checker: bool, needs_memory: bool, needs_functions: HashMap, + + // Results of the `CallWasm` call, if one was found. + wasm_results: Option>, } impl FunctionBindgen<'_> { @@ -1205,13 +1219,13 @@ impl FunctionBindgen<'_> { after_call: false, caller_memory_available: false, tmp: 0, - cleanup: None, closures: Source::default(), needs_buffer_transaction: false, needs_borrow_checker: false, needs_memory: false, needs_functions: HashMap::new(), params, + wasm_results: None, } } @@ -1731,45 +1745,33 @@ impl Bindgen for FunctionBindgen<'_> { results.push(format!("{}.len() as i32", val)); } - Instruction::ListCanonLift { element, free, .. } => match free { - Some(free) => { + Instruction::ListCanonLift { element, .. } => { + let tmp = self.tmp(); + let ptr = &operands[0]; + let len = &operands[1]; + self.push_str(&format!("let ptr{tmp} = {ptr};\n")); + self.push_str(&format!("let len{tmp} = {len};\n")); + + if self.gen.in_import { + self.needs_borrow_checker = true; + let slice = format!("_bc.slice(ptr{0}, len{0})?", tmp); + results.push(slice); + } else { self.needs_memory = true; self.gen.needs_copy_slice = true; - self.needs_functions - .insert(free.to_string(), NeededFunction::Free); - let (align, el_size) = - (self.sizes().align(element), self.sizes().size(element)); - let tmp = self.tmp(); - self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); - self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); + let align = self.sizes().align(element); self.push_str(&format!( " let data{tmp} = copy_slice( &mut caller, memory, - ptr{tmp}, len{tmp}, {} + ptr{tmp}, len{tmp}, {align} )?; ", - align, - tmp = tmp, )); - self.call_intrinsic( - free, - // we use normal multiplication here as copy_slice has - // already verified that multiplied size fits i32 - format!("(ptr{tmp}, len{tmp} * {}, {})", el_size, align, tmp = tmp), - ); - results.push(format!("data{}", tmp)); + results.push(format!("data{tmp}")); } - None => { - self.needs_borrow_checker = true; - let tmp = self.tmp(); - self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); - self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); - let slice = format!("_bc.slice(ptr{0}, len{0})?", tmp); - results.push(slice); - } - }, + } Instruction::StringLower { realloc } => { // see above for this unwrap @@ -1799,15 +1801,20 @@ impl Bindgen for FunctionBindgen<'_> { results.push(format!("{}.len() as i32", val)); } - Instruction::StringLift { free } => match free { - Some(free) => { + Instruction::StringLift => { + let tmp = self.tmp(); + let ptr = &operands[0]; + let len = &operands[1]; + self.push_str(&format!("let ptr{tmp} = {ptr};\n")); + self.push_str(&format!("let len{tmp} = {len};\n")); + + if self.gen.in_import { + self.needs_borrow_checker = true; + let slice = format!("_bc.slice_str(ptr{0}, len{0})?", tmp); + results.push(slice); + } else { self.needs_memory = true; self.gen.needs_copy_slice = true; - self.needs_functions - .insert(free.to_string(), NeededFunction::Free); - let tmp = self.tmp(); - self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); - self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); self.push_str(&format!( " let data{tmp} = copy_slice( @@ -1818,27 +1825,13 @@ impl Bindgen for FunctionBindgen<'_> { ", tmp = tmp, )); - self.call_intrinsic( - free, - // we use normal multiplication here as copy_slice has - // already verified that multiplied size fits i32 - format!("(ptr{tmp}, len{tmp}, 1)", tmp = tmp), - ); results.push(format!( "String::from_utf8(data{}) - .map_err(|_| wasmtime::Trap::new(\"invalid utf-8\"))?", + .map_err(|_| wasmtime::Trap::new(\"invalid utf-8\"))?", tmp, )); } - None => { - self.needs_borrow_checker = true; - let tmp = self.tmp(); - self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); - self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); - let slice = format!("_bc.slice_str(ptr{0}, len{0})?", tmp); - results.push(slice); - } - }, + } Instruction::ListLower { element, realloc } => { let realloc = realloc.unwrap(); @@ -1875,11 +1868,10 @@ impl Bindgen for FunctionBindgen<'_> { results.push(len); } - Instruction::ListLift { element, free, .. } => { + Instruction::ListLift { element, .. } => { let body = self.blocks.pop().unwrap(); let tmp = self.tmp(); let size = self.gen.sizes.size(element); - let align = self.gen.sizes.align(element); let len = format!("len{}", tmp); self.push_str(&format!("let {} = {};\n", len, operands[1])); let base = format!("base{}", tmp); @@ -1904,12 +1896,6 @@ impl Bindgen for FunctionBindgen<'_> { self.push_str(");\n"); self.push_str("}\n"); results.push(result); - - if let Some(free) = free { - self.call_intrinsic(free, format!("({}, {} * {}, {})", base, len, size, align)); - self.needs_functions - .insert(free.to_string(), NeededFunction::Free); - } } Instruction::IterElem { .. } => { @@ -1948,6 +1934,9 @@ impl Bindgen for FunctionBindgen<'_> { self.push_str("?;\n"); self.after_call = true; self.caller_memory_available = false; // invalidated by call + + assert!(self.wasm_results.is_none()); + self.wasm_results = Some(results.clone()); } Instruction::CallInterface { module: _, func } => { @@ -2015,22 +2004,21 @@ impl Bindgen for FunctionBindgen<'_> { } } - Instruction::Return { amt, .. } => { - let result = match amt { + Instruction::Return { amt, func, .. } => { + let mut result = match amt { 0 => format!("Ok(())\n"), 1 => format!("Ok({})\n", operands[0]), _ => format!("Ok(({}))\n", operands.join(", ")), }; - match self.cleanup.take() { - Some(cleanup) => { - self.push_str("let ret = "); - self.push_str(&result); - self.push_str(";\n"); - self.push_str(&cleanup); - self.push_str("ret"); - } - None => self.push_str(&result), + if !self.gen.in_import && iface.guest_export_needs_post_return(func) { + let tmp = self.tmp(); + self.push_str(&format!("let result{tmp} = {result};\n")); + result = format!("result{tmp}"); + + let result = &self.wasm_results.as_ref().unwrap()[0]; + self.push_str(&format!("post_return.call(&mut caller, {result})?;\n",)); } + self.push_str(&result); } Instruction::I32Load { offset } => results.push(self.load(*offset, "i32", operands)), @@ -2079,7 +2067,10 @@ impl Bindgen for FunctionBindgen<'_> { results.push(ptr); } - Instruction::Free { .. } => unimplemented!(), + Instruction::GuestDeallocate { .. } => unreachable!(), + Instruction::GuestDeallocateString { .. } => unreachable!(), + Instruction::GuestDeallocateVariant { .. } => unreachable!(), + Instruction::GuestDeallocateList { .. } => unreachable!(), } } } @@ -2088,7 +2079,6 @@ impl NeededFunction { fn cvt(&self) -> &'static str { match self { NeededFunction::Realloc => "(i32, i32, i32, i32), i32", - NeededFunction::Free => "(i32, i32, i32), ()", } } diff --git a/crates/gen-host-wasmtime-rust/tests/runtime.rs b/crates/gen-host-wasmtime-rust/tests/runtime.rs index 91ffbad89..71ed9755b 100644 --- a/crates/gen-host-wasmtime-rust/tests/runtime.rs +++ b/crates/gen-host-wasmtime-rust/tests/runtime.rs @@ -1,3 +1,5 @@ +#![allow(type_alias_bounds)] // TODO: should fix generated code to not fire this + use anyhow::Result; use wasmtime::{Config, Engine, Instance, Linker, Module, Store}; diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index e61f53af1..dcc002653 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -149,13 +149,12 @@ pub mod rt { return ptr; } - #[no_mangle] - pub unsafe extern "C" fn canonical_abi_free(ptr: *mut u8, len: usize, align: usize) { - if len == 0 { + pub unsafe fn dealloc(ptr: i32, size: usize, align: usize) { + if size == 0 { return; } - let layout = Layout::from_size_align_unchecked(len, align); - alloc::dealloc(ptr, layout); + let layout = Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr as *mut u8, layout); } macro_rules! as_traits { diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs index 97fb921a1..7ffbbd18f 100644 --- a/crates/wit-parser/src/abi.rs +++ b/crates/wit-parser/src/abi.rs @@ -438,40 +438,24 @@ def_instruction! { /// exactly matches the canonical ABI definition of the type. /// /// This will consume two `i32` values from the stack, a pointer and a - /// length, and then produces an interface value list. If the `free` - /// field is set to `Some` then the pointer/length should be considered - /// an owned allocation and need to be deallocated by the receiver. If - /// it is set to `None` then a view is provided but it does not need to - /// be deallocated. - /// - /// The `free` field is set to `Some` in similar situations as described - /// by `ListCanonLower`. If `free` is `Some` then the memory must be - /// deallocated after the lifted list is done being consumed. If it is - /// `None` then the receiver of the lifted list does not own the memory - /// and must leave the memory as-is. + /// length, and then produces an interface value list. ListCanonLift { element: &'a Type, - free: Option<&'a str>, ty: TypeId, } : [2] => [1], /// Same as `ListCanonLift`, but used for strings - StringLift { - free: Option<&'a str>, - } : [2] => [1], + StringLift : [2] => [1], /// Lifts a list which into an interface types value. /// /// This will consume two `i32` values from the stack, a pointer and a - /// length, and then produces an interface value list. Note that the - /// pointer/length popped are **owned** and need to be deallocated with - /// the wasm `free` function when the list is no longer needed. + /// length, and then produces an interface value list. /// /// This will also pop a block from the block stack which is how to /// read each individual element from the list. ListLift { element: &'a Type, - free: Option<&'a str>, ty: TypeId, } : [2] => [1], @@ -659,13 +643,39 @@ def_instruction! { align: usize, } : [0] => [1], - /// Calls the `free` function specified to deallocate the pointer on the - /// stack which has `size` bytes with alignment `align`. - Free { - free: &'static str, + /// Used exclusively for guest-code generation this indicates that + /// the standard memory deallocation function needs to be invoked with + /// the specified parameters. + /// + /// This will pop a pointer from the stack and push nothing. + GuestDeallocate { size: usize, align: usize, } : [1] => [0], + + /// Used exclusively for guest-code generation this indicates that + /// a string is being deallocated. The ptr/length are on the stack and + /// are poppped off and used to deallocate the string. + GuestDeallocateString : [2] => [0], + + /// Used exclusively for guest-code generation this indicates that + /// a list is being deallocated. The ptr/length are on the stack and + /// are poppped off and used to deallocate the list. + /// + /// This variant also pops a block off the block stack to be used as the + /// body of the deallocation loop. + GuestDeallocateList { + element: &'a Type, + } : [2] => [0], + + /// Used exclusively for guest-code generation this indicates that + /// a variant is being deallocated. The integer discriminant is popped + /// off the stack as well as `blocks` number of blocks popped from the + /// blocks stack. The variant is used to select, at runtime, which of + /// the blocks is executed to deallocate the variant. + GuestDeallocateVariant { + blocks: usize, + } : [1] => [0], } } @@ -982,6 +992,73 @@ impl Interface { ) { Generator::new(self, variant, lift_lower, bindgen).call(func); } + + /// Returns whether the `Function` specified needs a post-return function to + /// be generated in guest code. + /// + /// This is used when the return value contains a memory allocation such as + /// a list or a string primarily. + pub fn guest_export_needs_post_return(&self, func: &Function) -> bool { + func.results.iter_types().any(|t| self.needs_post_return(t)) + } + + fn needs_post_return(&self, ty: &Type) -> bool { + match ty { + Type::String => true, + Type::Id(id) => match &self.types[*id].kind { + TypeDefKind::List(_) => true, + TypeDefKind::Type(t) => self.needs_post_return(t), + TypeDefKind::Record(r) => r.fields.iter().any(|f| self.needs_post_return(&f.ty)), + TypeDefKind::Tuple(t) => t.types.iter().any(|t| self.needs_post_return(t)), + TypeDefKind::Union(t) => t.cases.iter().any(|t| self.needs_post_return(&t.ty)), + TypeDefKind::Variant(t) => t + .cases + .iter() + .filter_map(|t| t.ty.as_ref()) + .any(|t| self.needs_post_return(t)), + TypeDefKind::Option(t) => self.needs_post_return(t), + TypeDefKind::Result(t) => [&t.ok, &t.err] + .iter() + .filter_map(|t| t.as_ref()) + .any(|t| self.needs_post_return(t)), + TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => false, + TypeDefKind::Future(_) | TypeDefKind::Stream(_) => unimplemented!(), + }, + + // TODO: this is probably not correct, unsure though as handles are + // in flux at the moment + Type::Handle(_) => false, + + Type::Bool + | Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::Float32 + | Type::Float64 + | Type::Char => false, + } + } + + /// Used in a similar manner as the `Interface::call` function except is + /// used to generate the `post-return` callback for `func`. + /// + /// This is only intended to be used in guest generators for exported + /// functions and will primarily generate `GuestDeallocate*` instructions, + /// plus others used as input to those instructions. + pub fn post_return(&self, func: &Function, bindgen: &mut impl Bindgen) { + Generator::new( + self, + AbiVariant::GuestExport, + LiftLower::LiftArgsLowerResults, + bindgen, + ) + .post_return(func); + } } struct Generator<'a, B: Bindgen> { @@ -1160,11 +1237,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { .sizes() .record(func.params.iter().map(|t| &t.1)); self.emit(&Instruction::GetArg { nth: 0 }); - self.emit(&Instruction::Free { - free: "canonical_abi_free", - size, - align, - }); + self.emit(&Instruction::GuestDeallocate { size, align }); } } @@ -1224,6 +1297,34 @@ impl<'a, B: Bindgen> Generator<'a, B> { ); } + fn post_return(&mut self, func: &Function) { + let sig = self.iface.wasm_signature(self.variant, func); + + // Currently post-return is only used for lists and lists are always + // returned indirectly through memory due to their flat representation + // having more than one type. Assert that a return pointer is used, + // though, in case this ever changes. + assert!(sig.retptr); + + self.emit(&Instruction::GetArg { nth: 0 }); + let addr = self.stack.pop().unwrap(); + for (offset, ty) in self + .bindgen + .sizes() + .field_offsets(func.results.iter_types()) + { + let offset = i32::try_from(offset).unwrap(); + self.deallocate(ty, addr.clone(), offset); + } + self.emit(&Instruction::Return { func, amt: 0 }); + + assert!( + self.stack.is_empty(), + "stack has {} items remaining", + self.stack.len() + ); + } + fn emit(&mut self, inst: &Instruction<'_>) { self.operands.clear(); self.results.clear(); @@ -1517,32 +1618,20 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&HandleOwnedFromI32 { ty }); } } - Type::String => { - let free = self.list_free(); - self.emit(&StringLift { free }); - } + Type::String => self.emit(&StringLift), Type::Id(id) => match &self.iface.types[id].kind { TypeDefKind::Type(t) => self.lift(t), TypeDefKind::List(element) => { - let free = self.list_free(); if self.is_char(element) || self.bindgen.is_list_canonical(self.iface, element) { - self.emit(&ListCanonLift { - element, - free, - ty: id, - }); + self.emit(&ListCanonLift { element, ty: id }); } else { self.push_block(); self.emit(&IterBasePointer); let addr = self.stack.pop().unwrap(); self.read_from_memory(element, addr, 0); self.finish_block(1); - self.emit(&ListLift { - element, - free, - ty: id, - }); + self.emit(&ListLift { element, ty: id }); } } TypeDefKind::Record(record) => { @@ -1669,16 +1758,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn list_free(&self) -> Option<&'static str> { - // Lifting the arguments of a defined import means that, if - // possible, the caller still retains ownership and we don't - // free anything. - match (self.variant, self.lift_lower) { - (AbiVariant::GuestImport, LiftLower::LiftArgsLowerResults) => None, - _ => Some("canonical_abi_free"), - } - } - fn write_to_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { use Instruction::*; @@ -2077,6 +2156,137 @@ impl<'a, B: Bindgen> Generator<'a, B> { _ => false, } } + + fn deallocate(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + use Instruction::*; + + // No need to execute any instructions if this type itself doesn't + // require any form of post-return. + if !self.iface.needs_post_return(ty) { + return; + } + + match *ty { + Type::Handle(_) => unimplemented!(), + + Type::String => { + self.stack.push(addr.clone()); + self.emit(&Instruction::I32Load { offset }); + self.stack.push(addr); + self.emit(&Instruction::I32Load { offset: offset + 4 }); + self.emit(&Instruction::GuestDeallocateString); + } + + Type::Bool + | Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::Char + | Type::U64 + | Type::S64 + | Type::Float32 + | Type::Float64 => {} + + Type::Id(id) => match &self.iface.types[id].kind { + TypeDefKind::Type(t) => self.deallocate(t, addr, offset), + + TypeDefKind::List(element) => { + self.push_block(); + self.emit(&IterBasePointer); + let elemaddr = self.stack.pop().unwrap(); + self.deallocate(element, elemaddr, 0); + self.finish_block(0); + + self.stack.push(addr.clone()); + self.emit(&Instruction::I32Load { offset }); + self.stack.push(addr); + self.emit(&Instruction::I32Load { offset: offset + 4 }); + self.emit(&Instruction::GuestDeallocateList { element }); + } + + TypeDefKind::Record(record) => { + self.deallocate_fields( + &record.fields.iter().map(|f| f.ty).collect::>(), + addr, + offset, + ); + } + TypeDefKind::Tuple(tuple) => { + self.deallocate_fields(&tuple.types, addr, offset); + } + + TypeDefKind::Flags(_) => {} + + TypeDefKind::Variant(variant) => { + self.deallocate_variant( + offset, + addr, + variant.tag(), + variant.cases.iter().map(|c| c.ty.as_ref()), + ); + self.emit(&GuestDeallocateVariant { + blocks: variant.cases.len(), + }); + } + + TypeDefKind::Option(t) => { + self.deallocate_variant(offset, addr, Int::U8, [None, Some(t)]); + self.emit(&GuestDeallocateVariant { blocks: 2 }); + } + + TypeDefKind::Result(e) => { + self.deallocate_variant(offset, addr, Int::U8, [e.ok.as_ref(), e.err.as_ref()]); + self.emit(&GuestDeallocateVariant { blocks: 2 }); + } + + TypeDefKind::Enum(_) => {} + + TypeDefKind::Union(union) => { + self.deallocate_variant( + offset, + addr, + union.tag(), + union.cases.iter().map(|c| Some(&c.ty)), + ); + self.emit(&GuestDeallocateVariant { + blocks: union.cases.len(), + }); + } + + TypeDefKind::Future(_) => todo!("read future from memory"), + TypeDefKind::Stream(_) => todo!("read stream from memory"), + }, + } + } + + fn deallocate_variant<'b>( + &mut self, + offset: i32, + addr: B::Operand, + tag: Int, + cases: impl IntoIterator> + Clone, + ) { + self.stack.push(addr.clone()); + self.load_intrepr(offset, tag); + let payload_offset = + offset + (self.bindgen.sizes().payload_offset(tag, cases.clone()) as i32); + for ty in cases { + self.push_block(); + if let Some(ty) = ty { + self.deallocate(ty, addr.clone(), payload_offset); + } + self.finish_block(0); + } + } + + fn deallocate_fields(&mut self, tys: &[Type], addr: B::Operand, offset: i32) { + for (field_offset, ty) in self.bindgen.sizes().field_offsets(tys) { + self.deallocate(ty, addr.clone(), offset + (field_offset as i32)); + } + } } fn cast(from: WasmType, to: WasmType) -> Bitcast { diff --git a/tests/runtime/lists/wasm.c b/tests/runtime/lists/wasm.c index e5833608c..58ae2166b 100644 --- a/tests/runtime/lists/wasm.c +++ b/tests/runtime/lists/wasm.c @@ -8,30 +8,8 @@ #include #include -// "custom allocator" which just keeps track of allocated bytes - -static size_t ALLOCATED_BYTES = 0; - -__attribute__((export_name("cabi_realloc"))) -void *cabi_realloc( void *ptr, size_t orig_size, size_t orig_align, size_t new_size) { - void *ret = realloc(ptr, new_size); - if (!ret) - abort(); - ALLOCATED_BYTES -= orig_size; - ALLOCATED_BYTES += new_size; - return ret; -} - -__attribute__((export_name("canonical_abi_free"))) -void canonical_abi_free(void *ptr, size_t size, size_t align) { - if (size > 0) { - ALLOCATED_BYTES -= size; - free(ptr); - } -} - uint32_t exports_allocated_bytes(void) { - return ALLOCATED_BYTES; + return 0; } void exports_test_imports() { @@ -306,7 +284,7 @@ void exports_list_param4(exports_list_list_string_t *a) { } void exports_list_result(exports_list_u8_t *ret0) { - ret0->ptr = cabi_realloc(NULL, 0, 1, 5); + ret0->ptr = malloc(5); ret0->len = 5; ret0->ptr[0] = 1; ret0->ptr[1] = 2; @@ -321,7 +299,7 @@ void exports_list_result2(exports_string_t *ret0) { void exports_list_result3(exports_list_string_t *ret0) { ret0->len = 2; - ret0->ptr = cabi_realloc(NULL, 0, alignof(exports_string_t), 2 * sizeof(exports_string_t)); + ret0->ptr = malloc(2 * sizeof(exports_string_t)); exports_string_dup(&ret0->ptr[0], "hello,"); exports_string_dup(&ret0->ptr[1], "world!");