diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 73238fc93..46e295402 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -1069,10 +1069,8 @@ impl<'a, B: Bindgen> Generator<'a, B> { let ptr = match variant { // When a wasm module calls an import it will provide // space that isn't explicitly deallocated. - AbiVariant::GuestImport => self.bindgen.return_pointer(size, align), - - AbiVariant::GuestImportAsync => { - todo!("direct param lowering for async guest import not implemented") + AbiVariant::GuestImport | AbiVariant::GuestImportAsync => { + self.bindgen.return_pointer(size, align) } // When calling a wasm module from the outside, though, @@ -1105,7 +1103,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.realloc = None; // If necessary we may need to prepare a return pointer for this ABI. - if variant == AbiVariant::GuestImport && sig.retptr { + if matches!( + variant, + AbiVariant::GuestImport | AbiVariant::GuestImportAsync + ) && sig.retptr + { let info = self.bindgen.sizes().params(&func.result); let ptr = self.bindgen.return_pointer(info.size, info.align); self.return_pointer = Some(ptr.clone()); @@ -1135,14 +1137,17 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.return_pointer.take().unwrap() } + AbiVariant::GuestImportAsync => { + assert_eq!(sig.results.len(), 1); + self.return_pointer.take().unwrap() + } + // guest exports means that this is a host // calling wasm so wasm returned a pointer to where // the result is stored AbiVariant::GuestExport => self.stack.pop().unwrap(), - AbiVariant::GuestImportAsync - | AbiVariant::GuestExportAsync - | AbiVariant::GuestExportAsyncStackful => { + AbiVariant::GuestExportAsync | AbiVariant::GuestExportAsyncStackful => { unreachable!() } }; @@ -1177,9 +1182,20 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Emit the function return if async_ { + if sig.retptr { + // In an async guest import with retptr, CallWasm pushed the status result `ret`, + // and `read_results_from_memory` pushed the lifted result. Pop `ret` so only the + // lifted result remains on the stack for AsyncTaskReturn. + let status_idx = self.stack.len() - 1 - usize::from(func.result.is_some()); + self.stack.remove(status_idx); + } self.emit(&Instruction::AsyncTaskReturn { name: &func.name, - params: &sig.results, + params: if sig.retptr && func.result.is_some() { + &[WasmType::Pointer] + } else { + &sig.results + }, }); } else { self.emit(&Instruction::Return { diff --git a/crates/cpp/DESIGN.md b/crates/cpp/DESIGN.md index c50dd5ede..fbaede74a 100644 --- a/crates/cpp/DESIGN.md +++ b/crates/cpp/DESIGN.md @@ -94,3 +94,16 @@ For now for functions the guest import convention is used in both directions: with one modification: Resource IDs become usize, so you can optimize the resource table away. + +## C++20 Coroutine & Async Support + +The C++ backend targets C++20 (`-std=c++20`). + +Asynchronous functions in WIT are mapped to idiomatic C++20 coroutines: +- Functions defined as `async func(...) -> T` or filtered via `--async`/`--sync` return `wit::Task` (or `wit::Task` for functions returning void). +- Callers can `co_await` imported async functions or inspect completion with `.done()`, `.get()`. +- Export implementations use `co_return` (or `co_return value;`) within a coroutine body returning `wit::Task`. +- WIT `future` is mapped to `wit::FutureReader`. +- WIT `stream` is mapped to `wit::StreamReader`. +- WIT `error-context` is mapped to `wit::ErrorContext`. + diff --git a/crates/cpp/helper-types/wit.h b/crates/cpp/helper-types/wit.h index e643a5262..ea0fe17ff 100644 --- a/crates/cpp/helper-types/wit.h +++ b/crates/cpp/helper-types/wit.h @@ -14,6 +14,7 @@ #include #include #include // pair +#include namespace wit { /// @brief Helper class to map between IDs and resources @@ -331,4 +332,170 @@ class ResourceImportBase { } ResourceImportBase &operator=(ResourceImportBase const &r) = delete; }; + +/// @brief Represents an ongoing asynchronous computation in C++20 coroutines. +template +class Task { +public: + struct promise_type; + using handle_type = std::coroutine_handle; + + struct promise_type { + std::optional value; + std::coroutine_handle<> continuation = nullptr; + + Task get_return_object() { + return Task(handle_type::from_promise(*this)); + } + std::suspend_never initial_suspend() noexcept { return {}; } + auto final_suspend() noexcept { + struct FinalAwaiter { + bool await_ready() noexcept { return false; } + std::coroutine_handle<> await_suspend(handle_type h) noexcept { + auto cont = h.promise().continuation; + if (cont) return cont; + return std::noop_coroutine(); + } + void await_resume() noexcept {} + }; + return FinalAwaiter{}; + } + void return_value(T v) { + value = std::move(v); + } + void unhandled_exception() { + assert(false && "unhandled exception in wit::Task"); + } + }; + + handle_type handle = nullptr; + + Task() = default; + explicit Task(handle_type h) : handle(h) {} + Task(Task &&other) noexcept : handle(other.handle) { other.handle = nullptr; } + Task &operator=(Task &&other) noexcept { + if (this != &other) { + if (handle) handle.destroy(); + handle = other.handle; + other.handle = nullptr; + } + return *this; + } + ~Task() { + if (handle) handle.destroy(); + } + Task(const Task &) = delete; + Task &operator=(const Task &) = delete; + + bool await_ready() const noexcept { + return !handle || handle.done(); + } + std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting) noexcept { + handle.promise().continuation = awaiting; + return handle; + } + T await_resume() { + assert(handle && handle.promise().value.has_value()); + return std::move(*handle.promise().value); + } + + bool done() const noexcept { + return !handle || handle.done(); + } + + T get() { + assert(handle && handle.promise().value.has_value()); + return std::move(*handle.promise().value); + } +}; + +template <> +class Task { +public: + struct promise_type; + using handle_type = std::coroutine_handle; + + struct promise_type { + std::coroutine_handle<> continuation = nullptr; + + Task get_return_object() { + return Task(handle_type::from_promise(*this)); + } + std::suspend_never initial_suspend() noexcept { return {}; } + auto final_suspend() noexcept { + struct FinalAwaiter { + bool await_ready() noexcept { return false; } + std::coroutine_handle<> await_suspend(handle_type h) noexcept { + auto cont = h.promise().continuation; + if (cont) return cont; + return std::noop_coroutine(); + } + void await_resume() noexcept {} + }; + return FinalAwaiter{}; + } + void return_void() {} + void unhandled_exception() { + assert(false && "unhandled exception in wit::Task"); + } + }; + + handle_type handle = nullptr; + + Task() = default; + explicit Task(handle_type h) : handle(h) {} + Task(Task &&other) noexcept : handle(other.handle) { other.handle = nullptr; } + Task &operator=(Task &&other) noexcept { + if (this != &other) { + if (handle) handle.destroy(); + handle = other.handle; + other.handle = nullptr; + } + return *this; + } + ~Task() { + if (handle) handle.destroy(); + } + Task(const Task &) = delete; + Task &operator=(const Task &) = delete; + + bool await_ready() const noexcept { + return !handle || handle.done(); + } + std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting) noexcept { + handle.promise().continuation = awaiting; + return handle; + } + void await_resume() {} + + bool done() const noexcept { + return !handle || handle.done(); + } +}; + +template +class FutureReader { +public: + int32_t handle = -1; + FutureReader() = default; + explicit FutureReader(int32_t h) : handle(h) {} + int32_t get_handle() const { return handle; } +}; + +template +class StreamReader { +public: + int32_t handle = -1; + StreamReader() = default; + explicit StreamReader(int32_t h) : handle(h) {} + int32_t get_handle() const { return handle; } +}; + +class ErrorContext { +public: + int32_t handle = -1; + ErrorContext() = default; + explicit ErrorContext(int32_t h) : handle(h) {} + int32_t get_handle() const { return handle; } +}; } // namespace wit diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index bb9c623c6..40e10bc20 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -12,7 +12,7 @@ use std::{ use symbol_name::{make_external_component, make_external_symbol}; use wit_bindgen_c::to_c_ident; use wit_bindgen_core::{ - Files, InterfaceGenerator, Source, Types, WorldGenerator, + AsyncFilterSet, Files, InterfaceGenerator, Source, Types, WorldGenerator, abi::{self, AbiVariant, Bindgen, Bitcast, LiftLower, WasmSignature, WasmType}, name_package_module, uwrite, uwriteln, wit_parser::{ @@ -185,6 +185,10 @@ pub struct Opts { /// `k1=v1,k2=v2`. #[cfg_attr(feature = "clap", arg(long, value_parser = parse_with, value_delimiter = ','))] pub with: Vec<(String, String)>, + + /// Functions that are not using async will be using synchronous calls + #[cfg_attr(feature = "clap", clap(flatten))] + pub async_: AsyncFilterSet, } /// Supported API styles for the generated bindings. @@ -325,6 +329,24 @@ impl Cpp { }) } + pub fn is_async( + &mut self, + resolve: &Resolve, + interface: Option<&WorldKey>, + func: &Function, + is_import: bool, + ) -> bool { + matches!( + func.kind, + FunctionKind::AsyncFreestanding + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) + ) || self + .opts + .async_ + .is_async(resolve, interface, func, is_import) + } + fn interface<'a>( &'a mut self, resolve: &'a Resolve, @@ -562,14 +584,18 @@ impl WorldGenerator for Cpp { for (_name, func) in resolve.interfaces[id].functions.iter() { if matches!( func.kind, - FunctionKind::Freestanding | FunctionKind::Getter | FunctionKind::Setter + FunctionKind::Freestanding + | FunctionKind::Getter + | FunctionKind::Setter + | FunctionKind::AsyncFreestanding ) { + let variant = if r#gen.is_async(func) { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }; r#gen.r#gen.h_src.change_namespace(&namespace); - r#gen.generate_function( - func, - &TypeOwner::Interface(id), - AbiVariant::GuestImport, - ); + r#gen.generate_function(func, &TypeOwner::Interface(id), variant); } } self.finish_file(&namespace, store); @@ -621,10 +647,18 @@ impl WorldGenerator for Cpp { for (_name, func) in resolve.interfaces[id].functions.iter() { if matches!( func.kind, - FunctionKind::Freestanding | FunctionKind::Getter | FunctionKind::Setter + FunctionKind::Freestanding + | FunctionKind::Getter + | FunctionKind::Setter + | FunctionKind::AsyncFreestanding ) { + let variant = if r#gen.is_async(func) { + AbiVariant::GuestExportAsync + } else { + AbiVariant::GuestExport + }; r#gen.r#gen.h_src.change_namespace(&namespace); - r#gen.generate_function(func, &TypeOwner::Interface(id), AbiVariant::GuestExport); + r#gen.generate_function(func, &TypeOwner::Interface(id), variant); } } self.finish_file(&namespace, store); @@ -649,10 +683,18 @@ impl WorldGenerator for Cpp { for (_name, func) in funcs.iter() { if matches!( func.kind, - FunctionKind::Freestanding | FunctionKind::Getter | FunctionKind::Setter + FunctionKind::Freestanding + | FunctionKind::Getter + | FunctionKind::Setter + | FunctionKind::AsyncFreestanding ) { + let variant = if r#gen.is_async(func) { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }; r#gen.r#gen.h_src.change_namespace(&namespace); - r#gen.generate_function(func, &TypeOwner::World(world), AbiVariant::GuestImport); + r#gen.generate_function(func, &TypeOwner::World(world), variant); } } Ok(()) @@ -673,10 +715,18 @@ impl WorldGenerator for Cpp { for (_name, func) in funcs.iter() { if matches!( func.kind, - FunctionKind::Freestanding | FunctionKind::Getter | FunctionKind::Setter + FunctionKind::Freestanding + | FunctionKind::Getter + | FunctionKind::Setter + | FunctionKind::AsyncFreestanding ) { + let variant = if r#gen.is_async(func) { + AbiVariant::GuestExportAsync + } else { + AbiVariant::GuestExport + }; r#gen.r#gen.h_src.change_namespace(&namespace); - r#gen.generate_function(func, &TypeOwner::World(world), AbiVariant::GuestExport); + r#gen.generate_function(func, &TypeOwner::World(world), variant); } } Ok(()) @@ -702,6 +752,7 @@ impl WorldGenerator for Cpp { world_id: WorldId, files: &mut Files, ) -> std::result::Result<(), anyhow::Error> { + self.opts.async_.ensure_all_used()?; let world = &resolve.worlds[world_id]; let snake = world.name.to_snake_case(); let linking_symbol = wit_bindgen_c::component_type_object::linking_symbol(&world.name); @@ -913,6 +964,11 @@ struct CppInterfaceGenerator<'a> { } impl CppInterfaceGenerator<'_> { + fn is_async(&mut self, func: &Function) -> bool { + self.r#gen + .is_async(self.resolve, self._name, func, self.in_guest_import) + } + fn types(&mut self, iface: InterfaceId) { let iface_data = &self.resolve().interfaces[iface]; @@ -938,10 +994,27 @@ impl CppInterfaceGenerator<'_> { TypeDefKind::Result(r) => self.type_result(id, name, r, &ty.docs), TypeDefKind::List(t) => self.type_list(id, name, t, &ty.docs), TypeDefKind::Type(t) => self.type_alias(id, name, t, &ty.docs), - TypeDefKind::Future(_) => todo!("generate for future"), - TypeDefKind::Stream(_) => todo!("generate for stream"), - TypeDefKind::Handle(_) => todo!("generate for handle"), - TypeDefKind::FixedLengthList(_, _) => todo!(), + TypeDefKind::Future(t) => self.type_future(id, name, t, &ty.docs), + TypeDefKind::Stream(t) => self.type_stream(id, name, t, &ty.docs), + TypeDefKind::Handle(handle) => { + let ty = &self.resolve.types[id]; + let guest_export = self.is_exported_type(ty); + let namespc = namespace(self.resolve, &ty.owner, guest_export, &*self.r#gen); + self.r#gen.h_src.change_namespace(&namespc); + let pascal = name.to_pascal_case(); + Self::docs(&mut self.r#gen.h_src.src, &ty.docs); + let inner = match handle { + Handle::Own(id) => self.type_name(&Type::Id(*id), &namespc, Flavor::InStruct), + Handle::Borrow(id) => format!( + "std::reference_wrapper", + self.type_name(&Type::Id(*id), &namespc, Flavor::InStruct) + ), + }; + uwriteln!(self.r#gen.h_src.src, "using {pascal} = {inner};"); + } + TypeDefKind::FixedLengthList(t, size) => { + self.type_fixed_length_list(id, name, t, *size, &ty.docs) + } TypeDefKind::Map(k, v) => self.type_map(id, name, k, v, &ty.docs), TypeDefKind::Unknown => unreachable!(), } @@ -965,9 +1038,9 @@ impl CppInterfaceGenerator<'_> { FunctionKind::StaticGetter(i) => Some(i), FunctionKind::StaticSetter(i) => Some(i), FunctionKind::Constructor(i) => Some(i), - FunctionKind::AsyncFreestanding => todo!(), - FunctionKind::AsyncMethod(_id) => todo!(), - FunctionKind::AsyncStatic(_id) => todo!(), + FunctionKind::AsyncFreestanding => None, + FunctionKind::AsyncMethod(id) => Some(id), + FunctionKind::AsyncStatic(id) => Some(id), } .map(|i| { let ty = &self.resolve.types[*i]; @@ -983,7 +1056,10 @@ impl CppInterfaceGenerator<'_> { let is_drop = is_special_method(func); let func_name_h = if !matches!( &func.kind, - FunctionKind::Freestanding | FunctionKind::Getter | FunctionKind::Setter + FunctionKind::Freestanding + | FunctionKind::Getter + | FunctionKind::Setter + | FunctionKind::AsyncFreestanding ) { namespace.push(object.clone()); if let FunctionKind::Constructor(_i) = &func.kind { @@ -1085,9 +1161,14 @@ impl CppInterfaceGenerator<'_> { res.push('#'); res }); + let prefix = if matches!(variant, AbiVariant::GuestExportAsync) { + "[async-lift]" + } else { + "" + }; uwriteln!( self.r#gen.c_src.src, - r#"extern "C" __attribute__((__export_name__("{module_prefix}{func_name}")))"# + r#"extern "C" __attribute__((__export_name__("{prefix}{module_prefix}{func_name}")))"# ); let return_via_pointer = false; self.r#gen @@ -1174,6 +1255,15 @@ impl CppInterfaceGenerator<'_> { } else { res.result = "void".into(); } + if matches!( + abi_variant, + AbiVariant::GuestImportAsync + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful + ) { + self.r#gen.dependencies.needs_wit = true; + res.result = format!("wit::Task<{}>", res.result); + } if matches!(abi_variant, AbiVariant::GuestExport) && abi::guest_export_needs_post_return(self.resolve, func) { @@ -1182,7 +1272,10 @@ impl CppInterfaceGenerator<'_> { } if (matches!( func.kind, - FunctionKind::Static(_) | FunctionKind::StaticGetter(_) | FunctionKind::StaticSetter(_) + FunctionKind::Static(_) + | FunctionKind::StaticGetter(_) + | FunctionKind::StaticSetter(_) + | FunctionKind::AsyncStatic(_) ) || is_fallible_constructor) && !(matches!(&is_drop, SpecialMethod::ResourceDrop) && matches!(abi_variant, AbiVariant::GuestImport)) @@ -1203,6 +1296,7 @@ impl CppInterfaceGenerator<'_> { FunctionKind::Method(_) | FunctionKind::MethodGetter(_) | FunctionKind::MethodSetter(_) + | FunctionKind::AsyncMethod(_) ) || (matches!(&is_drop, SpecialMethod::ResourceDrop) && matches!(abi_variant, AbiVariant::GuestImport))) { @@ -1224,10 +1318,16 @@ impl CppInterfaceGenerator<'_> { )); } // default to non-const when exporting a method - let import = matches!(abi_variant, AbiVariant::GuestImport); + let import = matches!( + abi_variant, + AbiVariant::GuestImport | AbiVariant::GuestImportAsync + ); if matches!( func.kind, - FunctionKind::Method(_) | FunctionKind::MethodGetter(_) | FunctionKind::MethodSetter(_) + FunctionKind::Method(_) + | FunctionKind::MethodGetter(_) + | FunctionKind::MethodSetter(_) + | FunctionKind::AsyncMethod(_) ) && import { res.const_member = true; @@ -1347,12 +1447,20 @@ impl CppInterfaceGenerator<'_> { ) -> Vec { let owner = &cifg.resolve.types[match &func.kind { FunctionKind::Static(id) => *id, + FunctionKind::AsyncStatic(id) => *id, + FunctionKind::Method(id) => *id, + FunctionKind::AsyncMethod(id) => *id, _ => panic!("special func should be static"), }]; let mut namespace = namespace( cifg.resolve, &owner.owner, - matches!(variant, AbiVariant::GuestExport), + matches!( + variant, + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful + ), &*cifg.r#gen, ); namespace.push(owner.name.as_ref().unwrap().to_upper_camel_case()); @@ -1360,11 +1468,10 @@ impl CppInterfaceGenerator<'_> { } let export = match variant { - AbiVariant::GuestImport => false, - AbiVariant::GuestExport => true, - AbiVariant::GuestImportAsync => todo!(), - AbiVariant::GuestExportAsync => todo!(), - AbiVariant::GuestExportAsyncStackful => todo!(), + AbiVariant::GuestImport | AbiVariant::GuestImportAsync => false, + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful => true, }; if func.docs.contents.is_some() && !self.r#gen.h_src.src.ends_with('\n') { uwriteln!(self.r#gen.h_src.src, ""); @@ -1459,12 +1566,20 @@ impl CppInterfaceGenerator<'_> { // normal methods let namespace = if matches!( func.kind, - FunctionKind::Freestanding | FunctionKind::Getter | FunctionKind::Setter + FunctionKind::Freestanding + | FunctionKind::Getter + | FunctionKind::Setter + | FunctionKind::AsyncFreestanding ) { namespace( self.resolve, owner, - matches!(variant, AbiVariant::GuestExport), + matches!( + variant, + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful + ), &*self.r#gen, ) } else { @@ -1479,15 +1594,20 @@ impl CppInterfaceGenerator<'_> { FunctionKind::Freestanding => unreachable!(), FunctionKind::Getter => unreachable!(), FunctionKind::Setter => unreachable!(), - FunctionKind::AsyncFreestanding => todo!(), - FunctionKind::AsyncMethod(_id) => todo!(), - FunctionKind::AsyncStatic(_id) => todo!(), + FunctionKind::AsyncFreestanding => unreachable!(), + FunctionKind::AsyncMethod(id) => *id, + FunctionKind::AsyncStatic(id) => *id, }] .clone(); let mut namespace = namespace( self.resolve, &owner.owner, - matches!(variant, AbiVariant::GuestExport), + matches!( + variant, + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful + ), &*self.r#gen, ); namespace.push(owner.name.as_ref().unwrap().to_upper_camel_case()); @@ -1499,8 +1619,15 @@ impl CppInterfaceGenerator<'_> { } f.variant = variant; f.needs_dealloc = needs_dealloc; + f.has_result = func.result.is_some(); f.cabi_post = None; - abi::call(f.r#gen.resolve, variant, lift_lower, func, &mut f, false); + let is_async = matches!( + variant, + AbiVariant::GuestImportAsync + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful + ); + abi::call(f.r#gen.resolve, variant, lift_lower, func, &mut f, is_async); let ret_area_decl = f.emit_ret_area_if_needed(); let code = format!("{}{}", ret_area_decl, String::from(f.src)); self.r#gen.c_src.src.push_str(&code); @@ -1675,14 +1802,46 @@ impl CppInterfaceGenerator<'_> { let is_exported = self.is_exported_type(resource_ty); match (false, flavor) { - (false, Flavor::Argument(AbiVariant::GuestImport)) - | (true, Flavor::Argument(AbiVariant::GuestExport)) => { - typename.push_str("&&") - } - (false, Flavor::Argument(AbiVariant::GuestExport)) - | (false, Flavor::Result(AbiVariant::GuestExport)) - | (true, Flavor::Argument(AbiVariant::GuestImport)) - | (true, Flavor::Result(AbiVariant::GuestImport)) => { + ( + false, + Flavor::Argument( + AbiVariant::GuestImport | AbiVariant::GuestImportAsync, + ), + ) + | ( + true, + Flavor::Argument( + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful, + ), + ) => typename.push_str("&&"), + ( + false, + Flavor::Argument( + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful, + ), + ) + | ( + false, + Flavor::Result( + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful, + ), + ) + | ( + true, + Flavor::Argument( + AbiVariant::GuestImport | AbiVariant::GuestImportAsync, + ), + ) + | ( + true, + Flavor::Result(AbiVariant::GuestImport | AbiVariant::GuestImportAsync), + ) => { // Only exported resources have ::Owned typedef if is_exported { typename.push_str(&format!("::{OWNED_CLASS_NAME}")) @@ -1690,8 +1849,18 @@ impl CppInterfaceGenerator<'_> { typename.push_str("&&") } } - (false, Flavor::Result(AbiVariant::GuestImport)) - | (true, Flavor::Result(AbiVariant::GuestExport)) => (), + ( + false, + Flavor::Result(AbiVariant::GuestImport | AbiVariant::GuestImportAsync), + ) + | ( + true, + Flavor::Result( + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful, + ), + ) => (), (_, Flavor::InStruct) => (), (false, Flavor::BorrowedArgument) => (), (_, _) => todo!(), @@ -1795,8 +1964,22 @@ impl CppInterfaceGenerator<'_> { } } } - TypeDefKind::Future(_) => todo!(), - TypeDefKind::Stream(_) => todo!(), + TypeDefKind::Future(ty) => { + self.r#gen.dependencies.needs_wit = true; + let inner = match ty { + Some(ty) => self.type_name(ty, from_namespace, flavor), + None => "void".to_string(), + }; + format!("wit::FutureReader<{inner}>") + } + TypeDefKind::Stream(ty) => { + self.r#gen.dependencies.needs_wit = true; + let inner = match ty { + Some(ty) => self.type_name(ty, from_namespace, flavor), + None => "void".to_string(), + }; + format!("wit::StreamReader<{inner}>") + } TypeDefKind::Type(ty) => self.type_name(ty, from_namespace, flavor), TypeDefKind::FixedLengthList(ty, size) => { self.r#gen.dependencies.needs_array = true; @@ -1831,7 +2014,10 @@ impl CppInterfaceGenerator<'_> { } TypeDefKind::Unknown => todo!(), }, - Type::ErrorContext => todo!(), + Type::ErrorContext => { + self.r#gen.dependencies.needs_wit = true; + "wit::ErrorContext".to_string() + } } } @@ -2061,11 +2247,20 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> FunctionKind::StaticGetter(mid) => *mid == id, FunctionKind::StaticSetter(mid) => *mid == id, FunctionKind::Constructor(mid) => *mid == id, - FunctionKind::AsyncFreestanding => todo!(), - FunctionKind::AsyncMethod(_id) => todo!(), - FunctionKind::AsyncStatic(_id) => todo!(), + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(mid) => *mid == id, + FunctionKind::AsyncStatic(mid) => *mid == id, } { - self.generate_function(func, &TypeOwner::Interface(intf), variant); + let func_variant = if self.is_async(func) { + if guest_import { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestExportAsync + } + } else { + variant + }; + self.generate_function(func, &TypeOwner::Interface(intf), func_variant); // For non-fallible constructors on export side, generate a New allocator method // For fallible constructors, the user provides their own Create method let is_fallible_constructor = @@ -2350,13 +2545,24 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> fn type_fixed_length_list( &mut self, - _id: TypeId, - _name: &str, - _ty: &wit_bindgen_core::wit_parser::Type, - _size: u32, - _docs: &wit_bindgen_core::wit_parser::Docs, + id: TypeId, + name: &str, + ty: &wit_bindgen_core::wit_parser::Type, + size: u32, + docs: &wit_bindgen_core::wit_parser::Docs, ) { - todo!("named fixed-length list types are not yet supported in the C++ backend") + let ty_def = &self.resolve.types[id]; + let guest_export = self.is_exported_type(ty_def); + let namespc = namespace(self.resolve, &ty_def.owner, guest_export, &*self.r#gen); + self.r#gen.h_src.change_namespace(&namespc); + let pascal = name.to_pascal_case(); + Self::docs(&mut self.r#gen.h_src.src, docs); + self.r#gen.dependencies.needs_array = true; + let inner = self.type_name(ty, &namespc, Flavor::InStruct); + uwriteln!( + self.r#gen.h_src.src, + "using {pascal} = std::array<{inner}, {size}>;" + ); } fn type_map( @@ -2380,12 +2586,40 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> todo!() } - fn type_future(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { - todo!() + fn type_future(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + let ty_def = &self.resolve.types[id]; + let guest_export = self.is_exported_type(ty_def); + let namespc = namespace(self.resolve, &ty_def.owner, guest_export, &*self.r#gen); + self.r#gen.h_src.change_namespace(&namespc); + let pascal = name.to_pascal_case(); + Self::docs(&mut self.r#gen.h_src.src, docs); + self.r#gen.dependencies.needs_wit = true; + let inner = match ty { + Some(t) => self.type_name(t, &namespc, Flavor::InStruct), + None => "void".to_string(), + }; + uwriteln!( + self.r#gen.h_src.src, + "using {pascal} = wit::FutureReader<{inner}>;" + ); } - fn type_stream(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { - todo!() + fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option, docs: &Docs) { + let ty_def = &self.resolve.types[id]; + let guest_export = self.is_exported_type(ty_def); + let namespc = namespace(self.resolve, &ty_def.owner, guest_export, &*self.r#gen); + self.r#gen.h_src.change_namespace(&namespc); + let pascal = name.to_pascal_case(); + Self::docs(&mut self.r#gen.h_src.src, docs); + self.r#gen.dependencies.needs_wit = true; + let inner = match ty { + Some(t) => self.type_name(t, &namespc, Flavor::InStruct), + None => "void".to_string(), + }; + uwriteln!( + self.r#gen.h_src.src, + "using {pascal} = wit::StreamReader<{inner}>;" + ); } } @@ -2409,6 +2643,7 @@ struct FunctionBindgen<'a, 'b> { variant: AbiVariant, cabi_post: Option, needs_dealloc: bool, + has_result: bool, leak_on_insertion: Option, return_pointer_area_size: ArchitectureSize, return_pointer_area_align: Alignment, @@ -2428,6 +2663,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { variant: AbiVariant::GuestImport, cabi_post: None, needs_dealloc: false, + has_result: false, leak_on_insertion: None, return_pointer_area_size: Default::default(), return_pointer_area_align: Default::default(), @@ -2982,10 +3218,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let op = &operands[0]; match (handle, false) { (Handle::Own(ty), true) => match self.variant { - AbiVariant::GuestExport => { + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful => { results.push(format!("wit::{RESOURCE_EXPORT_BASE_CLASS_NAME}{{{op}}}")) } - AbiVariant::GuestImport => { + AbiVariant::GuestImport | AbiVariant::GuestImportAsync => { let tmp = self.tmp(); let var = self.tempname("obj", tmp); let tname = self.r#gen.type_name( @@ -3000,15 +3238,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ); results.push(format!("{tname}::Owned(*{var})")); } - AbiVariant::GuestImportAsync => todo!(), - AbiVariant::GuestExportAsync => todo!(), - AbiVariant::GuestExportAsyncStackful => todo!(), }, (Handle::Own(ty), false) => match self.variant { - AbiVariant::GuestImport => { + AbiVariant::GuestImport | AbiVariant::GuestImportAsync => { results.push(format!("wit::{RESOURCE_IMPORT_BASE_CLASS_NAME}{{{op}}}")) } - AbiVariant::GuestExport => { + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful => { let tmp = self.tmp(); let var = self.tempname("obj", tmp); let tname = self.r#gen.type_name( @@ -3041,9 +3278,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(format!("std::move({var})")) } - AbiVariant::GuestImportAsync => todo!(), - AbiVariant::GuestExportAsync => todo!(), - AbiVariant::GuestExportAsyncStackful => todo!(), }, (Handle::Borrow(ty), true) => { let tname = self.r#gen.type_name( @@ -3054,8 +3288,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(format!("**{tname}::lookup_resource({op})")); } (Handle::Borrow(ty), false) => match self.variant { - AbiVariant::GuestImport => results.push(op.clone()), - AbiVariant::GuestExport => { + AbiVariant::GuestImport | AbiVariant::GuestImportAsync => { + results.push(op.clone()) + } + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful => { let tname = self.r#gen.type_name( &Type::Id(*ty), &self.namespace, @@ -3063,9 +3301,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ); results.push(format!("std::ref(*({tname} *){op})")); } - AbiVariant::GuestImportAsync => todo!(), - AbiVariant::GuestExportAsync => todo!(), - AbiVariant::GuestExportAsyncStackful => todo!(), }, } } @@ -3467,13 +3702,26 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { }) .unwrap(); - let func = self - .r#gen - .declare_import(&module_name, name, &sig.params, &sig.results); + let import_name = if self.variant == AbiVariant::GuestImportAsync { + format!("[async-lower]{name}") + } else { + name.to_string() + }; + + let func = self.r#gen.declare_import( + &module_name, + &import_name, + &sig.params, + &sig.results, + ); // ... then call the function with all our operands if !sig.results.is_empty() { - self.src.push_str("auto ret = "); + if self.variant == AbiVariant::GuestImportAsync { + self.src.push_str("[[maybe_unused]] auto ret = "); + } else { + self.src.push_str("auto ret = "); + } results.push("ret".to_string()); } self.src.push_str(&func); @@ -3487,33 +3735,68 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ); self.src.push_str(");\n"); } - abi::Instruction::CallInterface { func, .. } => { + abi::Instruction::CallInterface { func, async_ } => { // dbg!(func); - self.let_results(if func.result.is_some() { 1 } else { 0 }, results); let (namespace, func_name_h) = self.r#gen.func_namespace_name(func, true, true); - if matches!( - func.kind, - FunctionKind::Method(_) - | FunctionKind::MethodGetter(_) - | FunctionKind::MethodSetter(_) - ) { - let this = operands.remove(0); - uwrite!(self.src, "({this}).get()."); + if *async_ { + let task_ret = self.tmp(); + uwrite!(self.src, "auto task{task_ret} = "); + if matches!( + func.kind, + FunctionKind::Method(_) + | FunctionKind::MethodGetter(_) + | FunctionKind::MethodSetter(_) + | FunctionKind::AsyncMethod(_) + ) { + let this = operands.remove(0); + uwrite!(self.src, "({this}).get()."); + } else { + let mut relative = SourceWithState::default(); + relative.qualify(&namespace); + self.push_str(&relative.src); + } + self.src.push_str(&func_name_h); + self.push_str("("); + self.push_str( + &operands + .iter() + .map(|op| move_if_necessary(op)) + .collect::>() + .join(", "), + ); + self.push_str(");\n"); + if func.result.is_some() { + let ret = self.tmp(); + uwriteln!(self.src, "auto ret{ret} = task{task_ret}.get();"); + results.push(format!("ret{ret}")); + } } else { - let mut relative = SourceWithState::default(); - relative.qualify(&namespace); - self.push_str(&relative.src); + self.let_results(if func.result.is_some() { 1 } else { 0 }, results); + if matches!( + func.kind, + FunctionKind::Method(_) + | FunctionKind::MethodGetter(_) + | FunctionKind::MethodSetter(_) + | FunctionKind::AsyncMethod(_) + ) { + let this = operands.remove(0); + uwrite!(self.src, "({this}).get()."); + } else { + let mut relative = SourceWithState::default(); + relative.qualify(&namespace); + self.push_str(&relative.src); + } + self.src.push_str(&func_name_h); + self.push_str("("); + self.push_str( + &operands + .iter() + .map(|op| move_if_necessary(op)) + .collect::>() + .join(", "), + ); + self.push_str(");\n"); } - self.src.push_str(&func_name_h); - self.push_str("("); - self.push_str( - &operands - .iter() - .map(|op| move_if_necessary(op)) - .collect::>() - .join(", "), - ); - self.push_str(");\n"); if self.needs_dealloc { uwriteln!( self.src, @@ -3658,12 +3941,31 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.store(ptr_type, *offset, operands) } abi::Instruction::LengthStore { offset } => self.store("size_t", *offset, operands), - abi::Instruction::FutureLower { .. } => todo!(), - abi::Instruction::FutureLift { .. } => todo!(), - abi::Instruction::StreamLower { .. } => todo!(), - abi::Instruction::StreamLift { .. } => todo!(), - abi::Instruction::ErrorContextLower { .. } => todo!(), - abi::Instruction::ErrorContextLift { .. } => todo!(), + abi::Instruction::FutureLower { .. } => { + results.push(format!("((int32_t) ({}).get_handle())", operands[0])); + } + abi::Instruction::FutureLift { ty, .. } => { + let ty_name = + self.r#gen + .type_name(&Type::Id(*ty), &self.namespace, Flavor::InStruct); + results.push(format!("{ty_name}({})", operands[0])); + } + abi::Instruction::StreamLower { .. } => { + results.push(format!("((int32_t) ({}).get_handle())", operands[0])); + } + abi::Instruction::StreamLift { ty, .. } => { + let ty_name = + self.r#gen + .type_name(&Type::Id(*ty), &self.namespace, Flavor::InStruct); + results.push(format!("{ty_name}({})", operands[0])); + } + abi::Instruction::ErrorContextLower { .. } => { + results.push(format!("((int32_t) ({}).get_handle())", operands[0])); + } + abi::Instruction::ErrorContextLift { .. } => { + self.r#gen.r#gen.dependencies.needs_wit = true; + results.push(format!("wit::ErrorContext({})", operands[0])); + } abi::Instruction::Flush { amt } => { for i in operands.iter().take(*amt) { let tmp = self.tmp(); @@ -3672,8 +3974,37 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(result); } } - abi::Instruction::AsyncTaskReturn { .. } => todo!(), - abi::Instruction::DropHandle { .. } => todo!(), + abi::Instruction::AsyncTaskReturn { name, params } => match self.variant { + AbiVariant::GuestImport | AbiVariant::GuestImportAsync => { + if self.has_result && !operands.is_empty() { + uwriteln!(self.src, "co_return {};", operands[0]); + } else { + if !operands.is_empty() { + uwriteln!(self.src, "(void) {};", operands[0]); + } + uwriteln!(self.src, "co_return;"); + } + } + AbiVariant::GuestExport + | AbiVariant::GuestExportAsync + | AbiVariant::GuestExportAsyncStackful => { + let module_name = format!( + "[export]{}", + self.r#gen.wasm_import_module.as_deref().unwrap_or("$root") + ); + let wasm_func = self.r#gen.declare_import(&module_name, name, params, &[]); + uwriteln!(self.src, "{wasm_func}({});", operands.join(", ")); + if matches!( + self.variant, + AbiVariant::GuestExportAsync | AbiVariant::GuestExportAsyncStackful + ) { + uwriteln!(self.src, "return 0;"); + } + } + }, + abi::Instruction::DropHandle { .. } => { + uwriteln!(self.src, "(void) {};", operands[0]); + } abi::Instruction::MapLower { key, value, diff --git a/crates/cpp/src/symbol_name.rs b/crates/cpp/src/symbol_name.rs index 4b71d1005..f9a813d0c 100644 --- a/crates/cpp/src/symbol_name.rs +++ b/crates/cpp/src/symbol_name.rs @@ -39,11 +39,18 @@ pub fn make_external_symbol(module_name: &str, name: &str, variant: abi::AbiVari make_external_component(name) } else { let mut res = make_external_component(module_name); - res.push_str(if matches!(variant, abi::AbiVariant::GuestExport) { - "X23" // Hash character - } else { - "X00" // NUL character (some tools use '.' for display) - }); + res.push_str( + if matches!( + variant, + abi::AbiVariant::GuestExport + | abi::AbiVariant::GuestExportAsync + | abi::AbiVariant::GuestExportAsyncStackful + ) { + "X23" // Hash character + } else { + "X00" // NUL character (some tools use '.' for display) + }, + ); res.push_str(&make_external_component(name)); res } diff --git a/crates/cpp/tests/async_tests.rs b/crates/cpp/tests/async_tests.rs new file mode 100644 index 000000000..e439bff6c --- /dev/null +++ b/crates/cpp/tests/async_tests.rs @@ -0,0 +1,155 @@ +use std::path::PathBuf; +use std::process::Command; +use wit_bindgen_core::{Files, wit_parser::Resolve}; + +#[test] +fn test_async_cpp_generation() { + const WIT: &str = r#" + package test:asyncdemo; + + interface types { + resource descriptor { + test: async func(arg1: string, arg2: list); + } + compute: async func(a: u32, b: u32) -> u32; + fetch: async func(name: string) -> string; + } + + world demo { + import types; + export run: async func() -> u32; + } + "#; + + let mut resolve = Resolve::default(); + let package = resolve.push_str("test.wit", WIT).unwrap(); + let world = resolve.select_world(&[package], Some("demo")).unwrap(); + let mut files = Files::default(); + let mut generator = wit_bindgen_cpp::Opts::default().build(None); + + generator.generate(&mut resolve, world, &mut files).unwrap(); + + let cpp_src = String::from_utf8(files.remove("demo.cpp").unwrap()).unwrap(); + let header_src = String::from_utf8(files.remove("demo_cpp.h").unwrap()).unwrap(); + + // Verify coroutine Task return types + assert!(header_src.contains("Task")); + assert!(header_src.contains("Task")); + assert!(header_src.contains("Task")); + + // Verify co_return in cpp source + assert!(cpp_src.contains("co_return;")); + assert!(cpp_src.contains("co_return result")); + + // Verify [[maybe_unused]] auto ret to prevent -Wunused-variable warnings + assert!(cpp_src.contains("[[maybe_unused]] auto ret = ")); +} + +#[test] +fn test_async_resource_void_returns() { + const WIT: &str = r#" + package foo:bar; + + interface types { + resource descriptor { + test: async func(arg1: string, arg2: list); + } + } + + world bindings { + import types; + } + "#; + + let mut resolve = Resolve::default(); + let package = resolve.push_str("test.wit", WIT).unwrap(); + let world = resolve.select_world(&[package], Some("bindings")).unwrap(); + let mut files = Files::default(); + let mut generator = wit_bindgen_cpp::Opts::default().build(None); + + generator.generate(&mut resolve, world, &mut files).unwrap(); + + let cpp_src = String::from_utf8(files.remove("bindings.cpp").unwrap()).unwrap(); + let header_src = String::from_utf8(files.remove("bindings_cpp.h").unwrap()).unwrap(); + + assert!(header_src.contains("Task")); + assert!(cpp_src.contains("[[maybe_unused]] auto ret = ")); + assert!(cpp_src.contains("co_return;")); +} + +#[test] +fn test_async_cpp_clang_compilation() { + let clang = match which_clangpp() { + Some(path) => path, + None => return, + }; + + const WIT: &str = r#" + package test:asynccompile; + + interface api { + compute: async func(a: u32, b: u32) -> u32; + ping: async func(); + } + + world app { + import api; + export run: async func() -> u32; + } + "#; + + let mut resolve = Resolve::default(); + let package = resolve.push_str("test.wit", WIT).unwrap(); + let world = resolve.select_world(&[package], Some("app")).unwrap(); + let mut files = Files::default(); + let mut generator = wit_bindgen_cpp::Opts::default().build(None); + + generator.generate(&mut resolve, world, &mut files).unwrap(); + + let temp_dir = std::env::temp_dir().join("wit_bindgen_cpp_async_test"); + let _ = std::fs::remove_dir_all(&temp_dir); + std::fs::create_dir_all(&temp_dir).unwrap(); + + for (name, content) in files.iter() { + std::fs::write(temp_dir.join(name), content).unwrap(); + } + + let helper_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("helper-types"); + let test_headers_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test_headers"); + + let status = Command::new(&clang) + .arg("-std=c++20") + .arg("-I") + .arg(&temp_dir) + .arg("-I") + .arg(&helper_dir) + .arg("-I") + .arg(&test_headers_dir) + .arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wc++-compat") + .arg("-Wno-unused-parameter") + .arg("-Wno-unknown-attributes") + .arg("-fsyntax-only") + .arg(temp_dir.join("app.cpp")) + .status() + .expect("failed to execute clang++"); + + assert!(status.success(), "clang++ syntax check failed"); + let _ = std::fs::remove_dir_all(&temp_dir); +} + +fn which_clangpp() -> Option { + for cmd in ["clang++", "wasm32-wasip2-clang++"] { + if let Ok(output) = Command::new("which").arg(cmd).output() { + if output.status.success() { + let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !path.is_empty() { + return Some(PathBuf::from(path)); + } + } + } + } + None +} diff --git a/crates/test/src/cpp.rs b/crates/test/src/cpp.rs index 8a2e887f9..46f5f0220 100644 --- a/crates/test/src/cpp.rs +++ b/crates/test/src/cpp.rs @@ -42,18 +42,11 @@ impl LanguageMethods for Cpp { fn should_fail_verify( &self, _runner: &Runner, - name: &str, - config: &crate::config::WitConfig, + _name: &str, + _config: &crate::config::WitConfig, _args: &[String], ) -> bool { - // Compiles on C++ despite the blanket async exclusion below. - if name == "issue-1598.wit" { - return false; - } - return match name { - "named-fixed-length-list.wit" => true, - _ => false, - } || config.async_; + false } fn prepare(&self, runner: &mut Runner) -> anyhow::Result<()> {