From 08a368f8c753a4514a573ebde9f9ba65075f5771 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Mon, 9 Nov 2026 13:01:14 -0700 Subject: [PATCH] go: add --stackful-exports for async exports Add an option that makes async exports stackful. The generated code uses the [async-lift-stackful] name, does not export a [callback] function and returns nothing from the wrapper, since the result goes through task.return. This is for runtimes that can block a thread, such as TinyGo with cooperative threads. In the core ABI, lower a result to memory for stackful exports the same way as for the other export variants. This replaces a todo. --- crates/core/src/abi.rs | 7 ++--- crates/go/src/lib.rs | 70 +++++++++++++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 73238fc93..1c4d73899 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -1329,7 +1329,9 @@ impl<'a, B: Bindgen> Generator<'a, B> { // (statically) and then write the result into that // memory, returning the pointer at the end. (_lower_to_memory @ true, _, variant) => match variant { - AbiVariant::GuestExport | AbiVariant::GuestExportAsync => { + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful => { let ElementInfo { size, align } = self.bindgen.sizes().params(&func.result); let ptr = self.bindgen.return_pointer(size, align); @@ -1347,9 +1349,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { func_name = func.name, ) } - AbiVariant::GuestExportAsyncStackful => { - todo!("stackful exports are not yet supported") - } }, } diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 6901d37bf..9406b234b 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -149,6 +149,16 @@ pub struct Opts { #[cfg_attr(feature = "clap", clap(long))] pub print_remote_pkg_version: bool, + /// If true, async exports are stackful (`[async-lift-stackful]`) instead of + /// callback-based. + /// + /// A stackful export runs on its own stack and may block on a waitable + /// set. The generated code does not export a `[callback]` function. This + /// needs a runtime that can block a thread, such as TinyGo with + /// cooperative threads. + #[cfg_attr(feature = "clap", clap(long))] + pub stackful_exports: bool, + /// When generating Go package names, include the WIT package version even /// if only one version of that package is referenced by the specified /// world. @@ -1313,7 +1323,14 @@ func {camel}({go_params}) {go_results} {{ let async_ = self.opts.async_.is_async(resolve, interface, func, false); - let (variant, prefix) = if async_ { + let stackful = async_ && self.opts.stackful_exports; + + let (variant, prefix) = if stackful { + ( + AbiVariant::GuestExportAsyncStackful, + "[async-lift-stackful]", + ) + } else if async_ { (AbiVariant::GuestExportAsync, "[async-lift]") } else { (AbiVariant::GuestExport, "") @@ -1398,30 +1415,47 @@ func {camel}({go_params}) {go_results} {{ .collect::>() .join(", "); - ( - if abi::guest_export_needs_post_return(resolve, func) { - format!( - "{PINNER} := &runtime.Pinner{{}} - defer {PINNER}.Unpin()" - ) - } else { - String::new() - }, + let pinner = if abi::guest_export_needs_post_return(resolve, func) { format!( - " + "{PINNER} := &runtime.Pinner{{}} + defer {PINNER}.Unpin()" + ) + } else { + String::new() + }; + let task_return = format!( + "//go:wasmimport [export]{module} [task-return]{function} +func wasm_export_task_return_{name}({task_return_params}) +" + ); + if stackful { + ( + pinner, + format!( + " + +{task_return}" + ), + "witAsync.Run(func() {", + "})", + ) + } else { + ( + pinner, + format!( + " //go:wasmexport [callback]{prefix}{export_name} func wasm_export_callback_{name}(event0 uint32, event1 uint32, event2 uint32) uint32 {{ return witAsync.Callback(event0, event1, event2) }} -//go:wasmimport [export]{module} [task-return]{function} -func wasm_export_task_return_{name}({task_return_params}) -" - ), - "return int32(witAsync.Run(func() {", - "}))", - ) +{task_return}" + ), + "return int32(witAsync.Run(func() {", + "}))", + ) + } } else if abi::guest_export_needs_post_return(resolve, func) { ( format!("{PINNER} := &{SYNC_EXPORT_PINNER}"),