From 09789d90f889e4a57220d9ce78c383263ffc3fb2 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 14:09:48 +0800 Subject: [PATCH 01/61] refactor: extract file writing Move file writing to each stage to simplify the data structure --- crates/moonbit/src/lib.rs | 428 ++++++++++++++++++++------------------ 1 file changed, 226 insertions(+), 202 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 7f8edbc6c..f97fec55d 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::Result; use core::panic; -use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use std::{ collections::{HashMap, HashSet}, fmt::Write, @@ -38,6 +38,8 @@ pub(crate) const FFI_DIR: &str = "ffi"; pub(crate) const FFI: &str = include_str!("./ffi/ffi.mbt"); +const VERSION: &str = env!("CARGO_PKG_VERSION"); + #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Parser))] pub struct Opts { @@ -114,23 +116,27 @@ enum PayloadFor { #[derive(Default)] pub struct MoonBit { opts: Opts, - name: String, + project_name: String, needs_cleanup: bool, - import_interface_fragments: HashMap, - export_interface_fragments: HashMap, import_world_fragment: InterfaceFragment, export_world_fragment: InterfaceFragment, sizes: SizeAlign, + // Collision may happen when a package is imported with multiple versions. + // see multiverison interface_ns: Ns, // dependencies between packages pkg_resolver: PkgResolver, export: HashMap, + export_ns: Ns, // return area allocation return_area_size: ArchitectureSize, return_area_align: Alignment, + // Collected inline ffi functions used in the final export directory + builtins: HashSet<&'static str>, + async_support: AsyncSupport, } @@ -156,12 +162,64 @@ impl MoonBit { derive_opts, } } + + fn write_moon_pkg(&self, moon_pkg: &mut Source, imports: Option<&Imports>, link: bool) { + // Disable warning for invalid inline wasm + moon_pkg.push_str("{\n\"warn-list\": \"-44\""); + // Dependencies + if let Some(imports) = imports { + moon_pkg.push_str(",\n\"import\": [\n"); + moon_pkg.indent(1); + let mut deps = imports + .packages + .iter() + .map(|(k, v)| { + format!( + "{{ \"path\" : \"{}/{}\", \"alias\" : \"{}\" }}", + self.project_name, + k.replace(".", "/"), + v + ) + }) + .collect::>(); + deps.sort(); + uwrite!(moon_pkg, "{}", deps.join(",\n")); + moon_pkg.deindent(1); + moon_pkg.push_str("\n]"); + } + // Link target + if link { + moon_pkg.push_str(",\n\"link\": {\n\"wasm\": {\n"); + moon_pkg.push_str("\"export-memory-name\": \"memory\",\n"); + moon_pkg.push_str("\"heap-start-address\": 16,\n"); + moon_pkg.push_str("\"exports\": [\n"); + moon_pkg.indent(1); + let mut exports = self + .export + .iter() + .map(|(k, v)| format!("\"{k}:{v}\"")) + .collect::>(); + exports.sort(); + uwrite!(moon_pkg, "{}", exports.join(",\n")); + moon_pkg.deindent(1); + moon_pkg.push_str("\n]\n}\n}\n"); + } + moon_pkg.push_str("\n}\n"); + } } impl WorldGenerator for MoonBit { fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { self.pkg_resolver.resolve = resolve.clone(); - self.name = PkgResolver::world_name(resolve, world); + self.project_name = self + .opts + .project_name + .clone() + .or(resolve.worlds[world].package.map(|id| { + let package = &resolve.packages[id].name; + format!("{}/{}", package.namespace, package.name) + })) + .unwrap_or("generated".into()); self.sizes.fill(resolve); } @@ -178,15 +236,6 @@ impl WorldGenerator for MoonBit { .import_interface_names .insert(id, name.clone()); - if let Some(content) = &resolve.interfaces[id].docs.contents { - if !content.is_empty() { - files.push( - &format!("{}/README.md", name.replace(".", "/")), - content.as_bytes(), - ); - } - } - let module = &resolve.name_world_key(key); let mut r#gen = self.interface(resolve, &name, module, Direction::Import); r#gen.types(id); @@ -195,9 +244,43 @@ impl WorldGenerator for MoonBit { r#gen.import(Some(key), func); } - let result = r#gen.finish(); - self.import_interface_fragments - .insert(name.to_owned(), result); + let fragment = r#gen.finish(); + // Write files + { + let directory = name.replace('.', "/"); + + // README + if let Some(content) = &resolve.interfaces[id].docs.contents + && !content.is_empty() + { + files.push(&format!("{}/README.md", directory), content.as_bytes()); + } + + assert!(fragment.stub.is_empty()); + // Source + let mut src = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", fragment.src); + files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); + + // FFI + let mut ffi = Source::default(); + wit_bindgen_core::generated_preamble(&mut ffi, VERSION); + uwriteln!(ffi, "{}", fragment.ffi); + for builtin in fragment.builtins { + uwriteln!(ffi, "{}", builtin); + } + files.push(&format!("{directory}/ffi.mbt"), indent(&ffi).as_bytes()); + + // moon.pkg.json + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); + } Ok(()) } @@ -237,15 +320,6 @@ impl WorldGenerator for MoonBit { .export_interface_names .insert(id, name.clone()); - if let Some(content) = &resolve.interfaces[id].docs.contents { - if !content.is_empty() { - files.push( - &format!("{}/README.md", name.replace(".", "/")), - content.as_bytes(), - ); - } - } - let module = &resolve.name_world_key(key); let mut r#gen = self.interface(resolve, &name, module, Direction::Export); r#gen.types(id); @@ -254,9 +328,59 @@ impl WorldGenerator for MoonBit { r#gen.export(Some(key), func); } - let result = r#gen.finish(); - self.export_interface_fragments - .insert(name.to_owned(), result); + let fragment = r#gen.finish(); + + // Write files + { + let directory = name.replace('.', "/"); + + // README + if let Some(content) = &resolve.interfaces[id].docs.contents + && !content.is_empty() + { + files.push( + &format!("{}/README.md", name.replace(".", "/")), + content.as_bytes(), + ); + } + // Source + let mut src = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", fragment.src); + files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); + + if !self.opts.ignore_stub { + // Stub + let mut stub = Source::default(); + generated_preamble(&mut stub, VERSION); + uwriteln!(stub, "{}", fragment.stub); + files.push(&format!("{directory}/stub.mbt"), indent(&stub).as_bytes()); + + // moon.pkg.json + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); + } + + let mut body = Source::default(); + wit_bindgen_core::generated_preamble(&mut body, VERSION); + + uwriteln!(&mut body, "{}", fragment.ffi); + files.push( + &format!( + "{}/{}_export.mbt", + self.opts.r#gen_dir, + directory.to_snake_case() + ), + indent(&body).as_bytes(), + ); + + self.builtins.extend(fragment.builtins.iter()); + } Ok(()) } @@ -303,103 +427,74 @@ impl WorldGenerator for MoonBit { } fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { - let project_name = self - .opts - .project_name - .clone() - .or(resolve.worlds[id].package.map(|id| { - let package = &resolve.packages[id].name; - format!("{}/{}", package.namespace, package.name) - })) - .unwrap_or("generated".into()); let name = PkgResolver::world_name(resolve, id); - if let Some(content) = &resolve.worlds[id].docs.contents { - if !content.is_empty() { - files.push( - &format!("{}/README.md", name.replace(".", "/")), - content.as_bytes(), - ); - } - } - let version = env!("CARGO_PKG_VERSION"); - let generate_pkg_definition = |name: &String, files: &mut Files| { + // Import world fragments + { let directory = name.replace('.', "/"); - let imports: Option<&Imports> = self.pkg_resolver.package_import.get(name); - if let Some(imports) = imports { - let mut deps = imports - .packages - .iter() - .map(|(k, v)| { - format!( - "{{ \"path\" : \"{project_name}/{}\", \"alias\" : \"{}\" }}", - k.replace(".", "/"), - v - ) - }) - .collect::>(); - deps.sort(); - files.push( - &format!("{directory}/moon.pkg.json"), - format!( - "{{ \"import\": [{}], \"warn-list\": \"-44\" }}", - deps.join(", ") - ) - .as_bytes(), - ); - } else { - files.push( - &format!("{directory}/moon.pkg.json"), - "{ \"warn-list\": \"-44\" }".to_string().as_bytes(), - ); + if let Some(content) = &resolve.worlds[id].docs.contents + && !content.is_empty() + { + files.push(&format!("{}/README.md", directory), content.as_bytes()); + } + let mut src = Source::default(); + let mut ffi = Source::default(); + let mut builtins: HashSet<&'static str> = HashSet::new(); + wit_bindgen_core::generated_preamble(&mut src, version); + wit_bindgen_core::generated_preamble(&mut ffi, version); + uwriteln!(src, "{}", self.import_world_fragment.src); + uwriteln!(ffi, "{}", self.import_world_fragment.ffi); + builtins.extend(self.import_world_fragment.builtins.iter()); + assert!(self.import_world_fragment.stub.is_empty()); + for b in builtins.iter() { + uwriteln!(ffi, "{}", b); } - }; - // Import world fragments - let mut src = Source::default(); - let mut ffi = Source::default(); - let mut builtins: HashSet<&'static str> = HashSet::new(); - wit_bindgen_core::generated_preamble(&mut src, version); - wit_bindgen_core::generated_preamble(&mut ffi, version); - uwriteln!(src, "{}", self.import_world_fragment.src); - uwriteln!(ffi, "{}", self.import_world_fragment.ffi); - builtins.extend(self.import_world_fragment.builtins.iter()); - assert!(self.import_world_fragment.stub.is_empty()); - for b in builtins.iter() { - uwriteln!(ffi, "{}", b); + files.push(&format!("{directory}/import.mbt"), indent(&src).as_bytes()); + files.push( + &format!("{directory}/ffi_import.mbt"), + indent(&ffi).as_bytes(), + ); + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); } - let directory = name.replace('.', "/"); - files.push(&format!("{directory}/import.mbt"), indent(&src).as_bytes()); - files.push( - &format!("{directory}/ffi_import.mbt"), - indent(&ffi).as_bytes(), - ); - generate_pkg_definition(&name, files); - // Export world fragments - let mut src = Source::default(); - let mut stub = Source::default(); - wit_bindgen_core::generated_preamble(&mut src, version); - generated_preamble(&mut stub, version); - uwriteln!(src, "{}", self.export_world_fragment.src); - uwriteln!(stub, "{}", self.export_world_fragment.stub); - - files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); - if !self.opts.ignore_stub { - files.push( - &format!("{}/{directory}/stub.mbt", self.opts.r#gen_dir), - indent(&stub).as_bytes(), + { + let name = format!( + "{}.{}", + self.opts.r#gen_dir, + PkgResolver::world_name(resolve, id) ); - generate_pkg_definition(&format!("{}.{}", self.opts.r#gen_dir, name), files); + let directory = name.replace('.', "/"); + let mut src = Source::default(); + let mut stub = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, version); + generated_preamble(&mut stub, version); + uwriteln!(src, "{}", self.export_world_fragment.src); + uwriteln!(stub, "{}", self.export_world_fragment.stub); + + files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); + if !self.opts.ignore_stub { + files.push(&format!("{directory}/stub.mbt"), indent(&stub).as_bytes()); + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); + } } - let mut builtins: HashSet<&'static str> = HashSet::new(); - builtins.insert(ffi::MALLOC); - builtins.insert(ffi::FREE); let mut generate_ffi = |directory: String, fragment: &InterfaceFragment, files: &mut Files| { // For cabi_realloc @@ -408,7 +503,7 @@ impl WorldGenerator for MoonBit { wit_bindgen_core::generated_preamble(&mut body, version); uwriteln!(&mut body, "{}", fragment.ffi); - builtins.extend(fragment.builtins.iter()); + self.builtins.extend(fragment.builtins.iter()); files.push( &format!( @@ -420,46 +515,11 @@ impl WorldGenerator for MoonBit { ); }; - generate_ffi(directory, &self.export_world_fragment, files); - - // Import interface fragments - for (name, fragment) in &self.import_interface_fragments { - let mut src = Source::default(); - let mut ffi = Source::default(); - wit_bindgen_core::generated_preamble(&mut src, version); - wit_bindgen_core::generated_preamble(&mut ffi, version); - let mut builtins: HashSet<&'static str> = HashSet::new(); - uwriteln!(src, "{}", fragment.src); - uwriteln!(ffi, "{}", fragment.ffi); - builtins.extend(fragment.builtins.iter()); - assert!(fragment.stub.is_empty()); - for builtin in builtins { - uwriteln!(ffi, "{}", builtin); - } - - let directory = name.replace('.', "/"); - files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); - files.push(&format!("{directory}/ffi.mbt"), indent(&ffi).as_bytes()); - generate_pkg_definition(name, files); - } - - // Export interface fragments - for (name, fragment) in &self.export_interface_fragments { - let mut src = Source::default(); - let mut stub = Source::default(); - wit_bindgen_core::generated_preamble(&mut src, version); - generated_preamble(&mut stub, version); - uwriteln!(src, "{}", fragment.src); - uwriteln!(stub, "{}", fragment.stub); - - let directory = name.replace('.', "/"); - files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); - if !self.opts.ignore_stub { - files.push(&format!("{directory}/stub.mbt"), indent(&stub).as_bytes()); - generate_pkg_definition(name, files); - } - generate_ffi(directory, fragment, files); - } + generate_ffi( + self.opts.r#gen_dir.clone(), + &self.export_world_fragment, + files, + ); // Export FFI Utils // Export Async utils @@ -472,7 +532,8 @@ impl WorldGenerator for MoonBit { let mut body = Source::default(); uwriteln!( &mut body, - "{{ \"name\": \"{project_name}\", \"preferred-target\": \"wasm\" }}" + "{{ \"name\": \"{}\", \"preferred-target\": \"wasm\" }}", + self.project_name ); files.push("moon.mod.json", body.as_bytes()); } @@ -481,6 +542,8 @@ impl WorldGenerator for MoonBit { let mut body = Source::default(); wit_bindgen_core::generated_preamble(&mut body, version); uwriteln!(&mut body, "{}", ffi::CABI_REALLOC); + self.builtins.insert(ffi::MALLOC); + self.builtins.insert(ffi::FREE); if !self.return_area_size.is_empty() { uwriteln!( @@ -490,8 +553,9 @@ impl WorldGenerator for MoonBit { ", self.return_area_size.size_wasm32(), ); + self.builtins.insert(ffi::MALLOC); } - for builtin in builtins { + for builtin in &self.builtins { uwriteln!(&mut body, "{}", builtin); } files.push( @@ -502,54 +566,15 @@ impl WorldGenerator for MoonBit { self.export .insert("mbt_ffi_cabi_realloc".into(), "cabi_realloc".into()); - let mut body = Source::default(); - let mut exports = self - .export - .iter() - .map(|(k, v)| format!("\"{k}:{v}\"")) - .collect::>(); - exports.sort(); - - uwrite!( - &mut body, - r#" - {{ - "link": {{ - "wasm": {{ - "exports": [{}], - "export-memory-name": "memory", - "heap-start-address": 16 - }} - }} - "#, - exports.join(", ") - ); - if let Some(imports) = self.pkg_resolver.package_import.get(&self.opts.r#gen_dir) { - let mut deps = imports - .packages - .iter() - .map(|(k, v)| { - format!( - "{{ \"path\" : \"{project_name}/{}\", \"alias\" : \"{}\" }}", - k.replace(".", "/"), - v - ) - }) - .collect::>(); - deps.sort(); - - uwrite!(&mut body, " ,\"import\": [{}]", deps.join(", ")); - } - uwrite!( - &mut body, - " - , \"warn-list\": \"-44\" - }} - ", + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&self.opts.r#gen_dir), + true, ); files.push( &format!("{}/moon.pkg.json", self.opts.r#gen_dir,), - indent(&body).as_bytes(), + indent(&moon_pkg).as_bytes(), ); Ok(()) @@ -797,11 +822,10 @@ impl InterfaceGenerator<'_> { .insert(func_name, format!("{async_export_prefix}{export_name}")); if async_ { - let snake = self.r#gen.name.to_lower_camel_case(); let export_func_name = self .r#gen .export_ns - .tmp(&format!("wasmExport{snake}Async{camel_name}")); + .tmp(&format!("wasmExportAsync{camel_name}")); let DeferredTaskReturn::Emitted { body: task_return_body, params: task_return_params, From e8a3977b5b19db2ce2bd8603726bc728fed5a231 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 14:13:23 +0800 Subject: [PATCH 02/61] fix: written but never read --- crates/moonbit/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index f97fec55d..e7ca9f469 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -117,7 +117,6 @@ enum PayloadFor { pub struct MoonBit { opts: Opts, project_name: String, - needs_cleanup: bool, import_world_fragment: InterfaceFragment, export_world_fragment: InterfaceFragment, sizes: SizeAlign, @@ -651,8 +650,6 @@ impl InterfaceGenerator<'_> { let mut src = bindgen.src.clone(); let cleanup_list = if bindgen.needs_cleanup_list { - self.r#gen.needs_cleanup = true; - " let cleanup_list : Array[Int] = [] " From 4c74e9aaeaf7536cccad743054b0a6299e028bb5 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 14:25:54 +0800 Subject: [PATCH 03/61] refactor: extract more from finish --- crates/moonbit/src/lib.rs | 196 +++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 107 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index e7ca9f469..ce92cdb48 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -118,7 +118,6 @@ pub struct MoonBit { opts: Opts, project_name: String, import_world_fragment: InterfaceFragment, - export_world_fragment: InterfaceFragment, sizes: SizeAlign, // Collision may happen when a package is imported with multiple versions. @@ -302,6 +301,64 @@ impl WorldGenerator for MoonBit { self.import_world_fragment.concat(result); } + fn import_types( + &mut self, + resolve: &Resolve, + world: WorldId, + types: &[(&str, TypeId)], + _files: &mut Files, + ) { + let name = PkgResolver::world_name(resolve, world); + let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); + + for (ty_name, ty) in types { + r#gen.define_type(ty_name, *ty); + } + + let result = r#gen.finish(); + self.import_world_fragment.concat(result); + } + + fn finish_imports(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) { + let name = PkgResolver::world_name(resolve, world); + let directory = name.replace('.', "/"); + + assert!(self.import_world_fragment.stub.is_empty()); + + // README + if let Some(content) = &resolve.worlds[world].docs.contents + && !content.is_empty() + { + files.push(&format!("{}/README.md", directory), content.as_bytes()); + } + // Source + let mut src = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", self.import_world_fragment.src); + files.push(&format!("{directory}/import.mbt"), indent(&src).as_bytes()); + // FFI + let mut ffi = Source::default(); + let mut builtins: HashSet<&'static str> = HashSet::new(); + wit_bindgen_core::generated_preamble(&mut ffi, VERSION); + uwriteln!(ffi, "{}", self.import_world_fragment.ffi); + builtins.extend(self.import_world_fragment.builtins.iter()); + for b in builtins.iter() { + uwriteln!(ffi, "{}", b); + } + files.push( + &format!("{directory}/ffi_import.mbt"), + indent(&ffi).as_bytes(), + ); + // moon.pkg.json + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); + } + fn export_interface( &mut self, resolve: &Resolve, @@ -389,7 +446,7 @@ impl WorldGenerator for MoonBit { resolve: &Resolve, world: WorldId, funcs: &[(&str, &Function)], - _files: &mut Files, + files: &mut Files, ) -> Result<()> { let name = format!( "{}.{}", @@ -402,88 +459,24 @@ impl WorldGenerator for MoonBit { r#gen.export(None, func); } - let result = r#gen.finish(); - self.export_world_fragment.concat(result); - Ok(()) - } - - fn import_types( - &mut self, - resolve: &Resolve, - world: WorldId, - types: &[(&str, TypeId)], - _files: &mut Files, - ) { - let name = PkgResolver::world_name(resolve, world); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); - - for (ty_name, ty) in types { - r#gen.define_type(ty_name, *ty); - } - - let result = r#gen.finish(); - self.import_world_fragment.concat(result); - } - - fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { - let name = PkgResolver::world_name(resolve, id); - - let version = env!("CARGO_PKG_VERSION"); - - // Import world fragments - { - let directory = name.replace('.', "/"); - - if let Some(content) = &resolve.worlds[id].docs.contents - && !content.is_empty() - { - files.push(&format!("{}/README.md", directory), content.as_bytes()); - } - let mut src = Source::default(); - let mut ffi = Source::default(); - let mut builtins: HashSet<&'static str> = HashSet::new(); - wit_bindgen_core::generated_preamble(&mut src, version); - wit_bindgen_core::generated_preamble(&mut ffi, version); - uwriteln!(src, "{}", self.import_world_fragment.src); - uwriteln!(ffi, "{}", self.import_world_fragment.ffi); - builtins.extend(self.import_world_fragment.builtins.iter()); - assert!(self.import_world_fragment.stub.is_empty()); - for b in builtins.iter() { - uwriteln!(ffi, "{}", b); - } - - files.push(&format!("{directory}/import.mbt"), indent(&src).as_bytes()); - files.push( - &format!("{directory}/ffi_import.mbt"), - indent(&ffi).as_bytes(), - ); - let mut moon_pkg = Source::default(); - self.write_moon_pkg( - &mut moon_pkg, - self.pkg_resolver.package_import.get(&name), - false, - ); - files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); - } + let fragment = r#gen.finish(); - // Export world fragments + // Write files { - let name = format!( - "{}.{}", - self.opts.r#gen_dir, - PkgResolver::world_name(resolve, id) - ); let directory = name.replace('.', "/"); + // Source let mut src = Source::default(); - let mut stub = Source::default(); - wit_bindgen_core::generated_preamble(&mut src, version); - generated_preamble(&mut stub, version); - uwriteln!(src, "{}", self.export_world_fragment.src); - uwriteln!(stub, "{}", self.export_world_fragment.stub); - + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", fragment.src); files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); + if !self.opts.ignore_stub { + // Stub + let mut stub = Source::default(); + generated_preamble(&mut stub, VERSION); + uwriteln!(stub, "{}", fragment.stub); files.push(&format!("{directory}/stub.mbt"), indent(&stub).as_bytes()); + // moon.pkg.json let mut moon_pkg = Source::default(); self.write_moon_pkg( &mut moon_pkg, @@ -492,39 +485,28 @@ impl WorldGenerator for MoonBit { ); files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); } - } - - let mut generate_ffi = - |directory: String, fragment: &InterfaceFragment, files: &mut Files| { - // For cabi_realloc - - let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, version); - - uwriteln!(&mut body, "{}", fragment.ffi); - self.builtins.extend(fragment.builtins.iter()); - - files.push( - &format!( - "{}/{}_export.mbt", - self.opts.r#gen_dir, - directory.to_snake_case() - ), - indent(&body).as_bytes(), - ); - }; - generate_ffi( - self.opts.r#gen_dir.clone(), - &self.export_world_fragment, - files, - ); + // FFI + let mut export = Source::default(); + wit_bindgen_core::generated_preamble(&mut export, VERSION); + uwriteln!(&mut export, "{}", fragment.ffi); + self.builtins.extend(fragment.builtins.iter()); + files.push( + &format!( + "{}/{}_export.mbt", + self.opts.r#gen_dir, + directory.to_snake_case() + ), + indent(&export).as_bytes(), + ); + } - // Export FFI Utils - // Export Async utils + Ok(()) + } + fn finish(&mut self, _resolve: &Resolve, _id: WorldId, files: &mut Files) -> Result<()> { // If async is used, export async utils - self.async_support.emit_utils(files, version); + self.async_support.emit_utils(files, VERSION); // Export project files if !self.opts.ignore_stub && !self.opts.ignore_module_file { @@ -539,7 +521,7 @@ impl WorldGenerator for MoonBit { // Export project entry point let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, version); + wit_bindgen_core::generated_preamble(&mut body, VERSION); uwriteln!(&mut body, "{}", ffi::CABI_REALLOC); self.builtins.insert(ffi::MALLOC); self.builtins.insert(ffi::FREE); From c708051e1727e8ffe0954d5217a1438ae854b630 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 14:30:19 +0800 Subject: [PATCH 04/61] doc: add some documentation --- crates/moonbit/src/lib.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index ce92cdb48..2e236a06b 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -206,6 +206,31 @@ impl MoonBit { } } +/// World generator implementation for MoonBit. +/// +/// This implementation connects the generic `wit-bindgen` world generation +/// workflow with MoonBit-specific codegen details. It consumes the parsed +/// WIT `Resolve` structure and emits MoonBit source (`*.mbt`) and package +/// metadata files into the provided `Files` collection. +/// +/// Responsibilities and behavior: +/// - `preprocess`: Initialize generator-wide state (package resolver, +/// project name, and size/align information) for the current world. +/// - `import_interface` / `export_interface`: Generate per-interface +/// sources, FFI glue, README documentation and `moon.pkg.json` metadata. +/// - `import_funcs` / `export_funcs` / `import_types`: Collect and accumulate +/// world-level functions and types (the `$root` module) into fragments +/// that are later written out by `finish_imports` or `finish`. +/// - `finish_imports` / `finish`: Emit aggregated import artifacts and the +/// final project entrypoints such as the combined FFI module and package +/// descriptor files. +/// +/// Implementation notes: +/// - Namespacing and collision avoidance are handled using `PkgResolver` and +/// an internal `Ns` to make import/export package names stable even when +/// multiple package versions are present. +/// - Inline FFI helpers and builtins are collected and written once into the +/// final export FFI module. Async helpers are emitted when required. impl WorldGenerator for MoonBit { fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { self.pkg_resolver.resolve = resolve.clone(); From aca7674e2299e9fe343c4a1c0de98e49bd26d4d8 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 15:09:07 +0800 Subject: [PATCH 05/61] fix: use pkg_resolver directly --- crates/moonbit/src/lib.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 2e236a06b..fc55cdae7 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1091,12 +1091,6 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { let func_name = self.r#gen.export_ns.tmp(&format!("wasmExport{name}Dtor")); - let export_dir = self.r#gen.opts.r#gen_dir.clone(); - - let r#gen = - self.r#gen - .interface(self.resolve, export_dir.as_str(), "", Direction::Export); - uwrite!( self.ffi, r#" @@ -1104,10 +1098,9 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { {}{name}::dtor(handle) }} "#, - r#gen - .r#gen + self.r#gen .pkg_resolver - .qualify_package(r#gen.name, self.name) + .qualify_package(self.r#gen.opts.r#gen_dir.as_str(), self.name) ); self.r#gen From 78569c0c92c33ec05ace86ee5d6b71829773459d Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 16:07:58 +0800 Subject: [PATCH 06/61] refactor: rename fields --- crates/moonbit/src/async_support.rs | 36 +-- crates/moonbit/src/lib.rs | 343 +++++++++++++++------------- 2 files changed, 208 insertions(+), 171 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 7975c6990..e22258a95 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -115,14 +115,14 @@ impl<'a> InterfaceGenerator<'a> { } multiple_params => { let params = multiple_params.iter().map(|Param { ty, .. }| ty); - let offsets = self.r#gen.sizes.field_offsets(params.clone()); - let elem_info = self.r#gen.sizes.params(params); + let offsets = self.world_gen.sizes.field_offsets(params.clone()); + let elem_info = self.world_gen.sizes.params(params); body.push_str(&format!( r#" let _lower_ptr : Int = {ffi}malloc({}) "#, elem_info.size.size_wasm32(), - ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR) + ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR) )); for ((offset, ty), name) in offsets.iter().zip( @@ -145,14 +145,14 @@ impl<'a> InterfaceGenerator<'a> { } else { let mut f = FunctionBindgen::new(self, "INVALID", self.name, Box::new([])); for (name, ty) in mbt_sig.params.iter() { - lower_params.extend(abi::lower_flat(f.r#gen.resolve, &mut f, name.clone(), ty)); + lower_params.extend(abi::lower_flat(f.interface_gen.resolve, &mut f, name.clone(), ty)); } lower_results.push(f.src.clone()); } let func_name = func.name.to_upper_camel_case(); - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); let call_import = |params: &Vec| { format!( @@ -255,18 +255,18 @@ impl<'a> InterfaceGenerator<'a> { result_type: Option<&Type>, ) { if !self - .r#gen + .world_gen .async_support .register_future_or_stream(module, ty) { return; } let result = match result_type { - Some(ty) => self.r#gen.pkg_resolver.type_name(self.name, ty), + Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), None => "Unit".into(), }; - let type_name = self.r#gen.pkg_resolver.type_name(self.name, &Type::Id(ty)); + let type_name = self.world_gen.pkg_resolver.type_name(self.name, &Type::Id(ty)); let name = result.to_upper_camel_case(); let kind = match payload_for { PayloadFor::Future => "future", @@ -283,7 +283,7 @@ impl<'a> InterfaceGenerator<'a> { PayloadFor::Future => "", PayloadFor::Stream => "List", }; - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); let mut dealloc_list; let malloc; @@ -415,26 +415,26 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ module: &str, ) -> String { let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - abi::deallocate_lists_in_types(f.r#gen.resolve, types, operands, indirect, &mut f); + abi::deallocate_lists_in_types(f.interface_gen.resolve, types, operands, indirect, &mut f); f.src } fn lift_from_memory(&mut self, address: &str, ty: &Type, module: &str) -> (String, String) { let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - let result = abi::lift_from_memory(f.r#gen.resolve, &mut f, address.into(), ty); + let result = abi::lift_from_memory(f.interface_gen.resolve, &mut f, address.into(), ty); (f.src, result) } fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - abi::lower_to_memory(f.r#gen.resolve, &mut f, address.into(), value.into(), ty); + abi::lower_to_memory(f.interface_gen.resolve, &mut f, address.into(), value.into(), ty); f.src } fn malloc_memory(&mut self, address: &str, length: &str, ty: &Type) -> String { - let size = self.r#gen.sizes.size(ty).size_wasm32(); - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let size = self.world_gen.sizes.size(ty).size_wasm32(); + let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); format!("let {address} = {ffi}malloc({size} * {length});") } @@ -452,7 +452,7 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ lift_func: &str, ty: &Type, ) -> String { - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); if self.is_list_canonical(self.resolve, ty) { if ty == &Type::U8 { return format!("{ffi}ptr2bytes({address}, {length})"); @@ -469,7 +469,7 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ return format!("{ffi}ptr2{ty}_array({address}, {length})"); } - let size = self.r#gen.sizes.size(ty).size_wasm32(); + let size = self.world_gen.sizes.size(ty).size_wasm32(); format!( r#" FixedArray::makei( @@ -485,7 +485,7 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ fn list_lower_to_memory(&mut self, lower_func: &str, value: &str, ty: &Type) -> String { // Align the address, moonbit only supports wasm32 for now - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); if self.is_list_canonical(self.resolve, ty) { if ty == &Type::U8 { return format!("{ffi}bytes2ptr({value})"); @@ -502,7 +502,7 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ }; return format!("{ffi}{ty}_array2ptr({value})"); } - let size = self.r#gen.sizes.size(ty).size_wasm32(); + let size = self.world_gen.sizes.size(ty).size_wasm32(); format!( r#" let address = {ffi}malloc(({value}).length() * {size}); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index fc55cdae7..c1781791f 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -151,7 +151,7 @@ impl MoonBit { src: String::new(), stub: String::new(), ffi: String::new(), - r#gen: self, + world_gen: self, resolve, name, module, @@ -594,7 +594,7 @@ struct InterfaceGenerator<'a> { // Collect of FFI imports used in this interface ffi_imports: HashSet<&'static str>, - r#gen: &'a mut MoonBit, + world_gen: &'a mut MoonBit, resolve: &'a Resolve, // The current interface getting generated name: &'a str, @@ -617,12 +617,12 @@ impl InterfaceGenerator<'_> { fn import(&mut self, module: Option<&WorldKey>, func: &Function) { let async_ = self - .r#gen + .world_gen .opts .async_ .is_async(self.resolve, module, func, false); if async_ { - self.r#gen.async_support.mark_async(); + self.world_gen.async_support.mark_async(); } let interface_name = match module { @@ -646,7 +646,7 @@ impl InterfaceGenerator<'_> { }; abi::call( - bindgen.r#gen.resolve, + bindgen.interface_gen.resolve, AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, func, @@ -688,7 +688,7 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); - let mbt_sig = self.r#gen.pkg_resolver.mbt_sig(self.name, func, false); + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); let sig = self.sig_string(&mbt_sig, async_); let module = match module { @@ -722,12 +722,12 @@ impl InterfaceGenerator<'_> { fn export(&mut self, interface: Option<&WorldKey>, func: &Function) { let async_ = self - .r#gen + .world_gen .opts .async_ .is_async(self.resolve, interface, func, false); if async_ { - self.r#gen.async_support.mark_async(); + self.world_gen.async_support.mark_async(); } let variant = if async_ { @@ -737,12 +737,12 @@ impl InterfaceGenerator<'_> { }; let sig = self.resolve.wasm_signature(variant, func); - let mbt_sig = self.r#gen.pkg_resolver.mbt_sig(self.name, func, false); + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); let func_sig = self.sig_string(&mbt_sig, async_); - let export_dir = self.r#gen.opts.r#gen_dir.clone(); + let export_dir = self.world_gen.opts.r#gen_dir.clone(); - let mut toplevel_generator = self.r#gen.interface( + let mut toplevel_generator = self.world_gen.interface( self.resolve, export_dir.as_str(), self.module, @@ -757,7 +757,7 @@ impl InterfaceGenerator<'_> { ); abi::call( - bindgen.r#gen.resolve, + bindgen.interface_gen.resolve, variant, LiftLower::LiftArgsLowerResults, func, @@ -787,7 +787,10 @@ impl InterfaceGenerator<'_> { let camel_name = func.name.to_upper_camel_case(); - let func_name = self.r#gen.export_ns.tmp(&format!("wasmExport{camel_name}")); + let func_name = self + .world_gen + .export_ns + .tmp(&format!("wasmExport{camel_name}")); let params = sig .params @@ -821,13 +824,13 @@ impl InterfaceGenerator<'_> { "#, ); - self.r#gen + self.world_gen .export .insert(func_name, format!("{async_export_prefix}{export_name}")); if async_ { let export_func_name = self - .r#gen + .world_gen .export_ns .tmp(&format!("wasmExportAsync{camel_name}")); let DeferredTaskReturn::Emitted { @@ -840,7 +843,7 @@ impl InterfaceGenerator<'_> { }; let func_name = func.name.clone(); let import_module = self.resolve.name_world_key(interface.unwrap()); - self.r#gen.export.insert( + self.world_gen.export.insert( export_func_name.clone(), format!("[callback]{async_export_prefix}{export_name}"), ); @@ -857,7 +860,7 @@ impl InterfaceGenerator<'_> { .join(", "); let return_ty = match &func.result { Some(result) => self - .r#gen + .world_gen .pkg_resolver .type_name(self.name, result) .to_string(), @@ -868,7 +871,10 @@ impl InterfaceGenerator<'_> { _ => format!("{return_param}: {return_ty}",), }; let snake_func_name = func.name.to_moonbit_ident().to_string(); - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); uwriteln!( self.src, @@ -909,12 +915,12 @@ impl InterfaceGenerator<'_> { (0..sig.results.len()).map(|i| format!("p{i}")).collect(), ); - abi::post_return(bindgen.r#gen.resolve, func, &mut bindgen); + abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen); let src = bindgen.src; let func_name = self - .r#gen + .world_gen .export_ns .tmp(&format!("wasmExport{camel_name}PostReturn")); @@ -926,7 +932,7 @@ impl InterfaceGenerator<'_> { }} "# ); - self.r#gen + self.world_gen .export .insert(func_name, format!("cabi_post_{export_name}")); } @@ -947,7 +953,7 @@ impl InterfaceGenerator<'_> { .params .iter() .map(|(name, ty)| { - let ty = self.r#gen.pkg_resolver.type_name(self.name, ty); + let ty = self.world_gen.pkg_resolver.type_name(self.name, ty); format!("{name} : {ty}") }) .collect::>(); @@ -956,7 +962,7 @@ impl InterfaceGenerator<'_> { let (async_prefix, async_suffix) = if async_ { ("async ", "") } else { ("", "") }; let result_type = match &sig.result_type { None => "Unit".into(), - Some(ty) => self.r#gen.pkg_resolver.type_name(self.name, ty), + Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), }; format!( "pub {async_prefix}fn {}({params}) -> {}{async_suffix}", @@ -982,7 +988,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { format!( "{} : {}", field.name.to_moonbit_ident(), - self.r#gen.pkg_resolver.type_name(self.name, &field.ty), + self.world_gen.pkg_resolver.type_name(self.name, &field.ty), ) }) .collect::>() @@ -1089,7 +1095,10 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { "# ); - let func_name = self.r#gen.export_ns.tmp(&format!("wasmExport{name}Dtor")); + let func_name = self + .world_gen + .export_ns + .tmp(&format!("wasmExport{name}Dtor")); uwrite!( self.ffi, @@ -1098,12 +1107,12 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { {}{name}::dtor(handle) }} "#, - self.r#gen + self.world_gen .pkg_resolver - .qualify_package(self.r#gen.opts.r#gen_dir.as_str(), self.name) + .qualify_package(self.world_gen.opts.r#gen_dir.as_str(), self.name) ); - self.r#gen + self.world_gen .export .insert(func_name, format!("{module}#[dtor]{type_name}")); } @@ -1215,7 +1224,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .map(|case| { let name = case.name.to_upper_camel_case(); if let Some(ty) = case.ty { - let ty = self.r#gen.pkg_resolver.type_name(self.name, &ty); + let ty = self.world_gen.pkg_resolver.type_name(self.name, &ty); format!("{name}({ty})") } else { name.to_string() @@ -1393,7 +1402,7 @@ enum DeferredTaskReturn { } struct FunctionBindgen<'a, 'b> { - r#gen: &'b mut InterfaceGenerator<'a>, + interface_gen: &'b mut InterfaceGenerator<'a>, func_name: &'b str, func_interface: &'b str, params: Box<[String]>, @@ -1419,7 +1428,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { locals.tmp(str); }); Self { - r#gen, + interface_gen: r#gen, func_name, func_interface, params, @@ -1474,8 +1483,8 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .join(", "); let payload = if self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver .non_empty_type(ty.as_ref()) .is_some() @@ -1542,10 +1551,10 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { // Hacky way to get the type name without type parameter let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_constructor(self.r#gen.name, ty); + .type_constructor(self.interface_gen.name, ty); let lifted = self.locals.tmp("lifted"); let cases = cases @@ -1554,8 +1563,8 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .enumerate() .map(|(i, ((case_name, case_ty), Block { body, results, .. }))| { let payload = if self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver .non_empty_type(case_ty.as_ref()) .is_some() @@ -1659,13 +1668,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::U8FromI32 => results.push(format!("({}).to_byte()", operands[0])), Instruction::I32FromS8 => { - self.r#gen.ffi_imports.insert(ffi::EXTEND8); + self.interface_gen.ffi_imports.insert(ffi::EXTEND8); results.push(format!("mbt_ffi_extend8({})", operands[0])) } Instruction::S8FromI32 => results.push(format!("({} - 0x100)", operands[0])), Instruction::S16FromI32 => results.push(format!("({} - 0x10000)", operands[0])), Instruction::I32FromS16 => { - self.r#gen.ffi_imports.insert(ffi::EXTEND16); + self.interface_gen.ffi_imports.insert(ffi::EXTEND16); results.push(format!("mbt_ffi_extend16({})", operands[0])) } Instruction::U16FromI32 => results.push(format!( @@ -1696,10 +1705,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; let flag = self.locals.tmp("flag"); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + .type_constructor(self.interface_gen.name, &Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1712,10 +1721,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; let flag = self.locals.tmp("flag"); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + .type_constructor(self.interface_gen.name, &Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1728,10 +1737,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; let flag = self.locals.tmp("flag"); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + .type_constructor(self.interface_gen.name, &Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1747,27 +1756,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U8 => { results.push(format!( "{}({}.to_byte())", - self.r#gen - .r#gen + self.interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)), + .type_name(self.interface_gen.name, &Type::Id(*ty)), operands[0] )); } Int::U16 | Int::U32 => { results.push(format!( "{}({}.reinterpret_as_uint())", - self.r#gen - .r#gen + self.interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)), + .type_name(self.interface_gen.name, &Type::Id(*ty)), operands[0] )); } Int::U64 => { results.push(format!( "{}(({}).reinterpret_as_uint().to_uint64() | (({}).reinterpret_as_uint().to_uint64() << 32))", - self.r#gen.r#gen.pkg_resolver.type_name(self.r#gen.name, &Type::Id(*ty)), + self.interface_gen.world_gen.pkg_resolver.type_name(self.interface_gen.name, &Type::Id(*ty)), operands[0], operands[1] )); @@ -1778,10 +1787,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; let handle = self.locals.tmp("handle"); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + .type_constructor(self.interface_gen.name, &Type::Id(*ty)); uwrite!( self.src, r#" @@ -1793,10 +1802,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::HandleLift { ty, .. } => { let op = &operands[0]; let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + .type_constructor(self.interface_gen.name, &Type::Id(*ty)); results.push(format!( "{}::{}({})", @@ -1826,10 +1835,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!( "{}::{{{ops}}}", - self.r#gen - .r#gen + self.interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)) + .type_name(self.interface_gen.name, &Type::Id(*ty)) )); } @@ -1954,16 +1963,16 @@ impl Bindgen for FunctionBindgen<'_, '_> { let _none = self.blocks.pop().unwrap(); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)); + .type_name(self.interface_gen.name, &Type::Id(*ty)); let lifted = self.locals.tmp("lifted"); let op = &operands[0]; let payload = if self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver .non_empty_type(Some(*payload)) .is_some() @@ -2016,10 +2025,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::EnumLift { ty, .. } => results.push(format!( "{}::from({})", - self.r#gen - .r#gen + self.interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)), + .type_name(self.interface_gen.name, &Type::Id(*ty)), operands[0] )), @@ -2027,7 +2036,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Type::U8 => { let op = &operands[0]; let ptr = self.locals.tmp("ptr"); - self.r#gen.ffi_imports.insert(ffi::BYTES2PTR); + self.interface_gen.ffi_imports.insert(ffi::BYTES2PTR); uwriteln!( self.src, " @@ -2045,27 +2054,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { let ptr = self.locals.tmp("ptr"); let ty = match element { Type::U32 => { - self.r#gen.ffi_imports.insert(ffi::UINT_ARRAY2PTR); + self.interface_gen.ffi_imports.insert(ffi::UINT_ARRAY2PTR); "uint" } Type::U64 => { - self.r#gen.ffi_imports.insert(ffi::UINT64_ARRAY2PTR); + self.interface_gen.ffi_imports.insert(ffi::UINT64_ARRAY2PTR); "uint64" } Type::S32 => { - self.r#gen.ffi_imports.insert(ffi::INT_ARRAY2PTR); + self.interface_gen.ffi_imports.insert(ffi::INT_ARRAY2PTR); "int" } Type::S64 => { - self.r#gen.ffi_imports.insert(ffi::INT64_ARRAY2PTR); + self.interface_gen.ffi_imports.insert(ffi::INT64_ARRAY2PTR); "int64" } Type::F32 => { - self.r#gen.ffi_imports.insert(ffi::FLOAT_ARRAY2PTR); + self.interface_gen.ffi_imports.insert(ffi::FLOAT_ARRAY2PTR); "float" } Type::F64 => { - self.r#gen.ffi_imports.insert(ffi::DOUBLE_ARRAY2PTR); + self.interface_gen.ffi_imports.insert(ffi::DOUBLE_ARRAY2PTR); "double" } _ => unreachable!(), @@ -2091,7 +2100,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = self.locals.tmp("result"); let address = &operands[0]; let length = &operands[1]; - self.r#gen.ffi_imports.insert(ffi::PTR2BYTES); + self.interface_gen.ffi_imports.insert(ffi::PTR2BYTES); uwrite!( self.src, " @@ -2104,27 +2113,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64 => { let ty = match element { Type::U32 => { - self.r#gen.ffi_imports.insert(ffi::PTR2UINT_ARRAY); + self.interface_gen.ffi_imports.insert(ffi::PTR2UINT_ARRAY); "uint" } Type::U64 => { - self.r#gen.ffi_imports.insert(ffi::PTR2UINT64_ARRAY); + self.interface_gen.ffi_imports.insert(ffi::PTR2UINT64_ARRAY); "uint64" } Type::S32 => { - self.r#gen.ffi_imports.insert(ffi::PTR2INT_ARRAY); + self.interface_gen.ffi_imports.insert(ffi::PTR2INT_ARRAY); "int" } Type::S64 => { - self.r#gen.ffi_imports.insert(ffi::PTR2INT64_ARRAY); + self.interface_gen.ffi_imports.insert(ffi::PTR2INT64_ARRAY); "int64" } Type::F32 => { - self.r#gen.ffi_imports.insert(ffi::PTR2FLOAT_ARRAY); + self.interface_gen.ffi_imports.insert(ffi::PTR2FLOAT_ARRAY); "float" } Type::F64 => { - self.r#gen.ffi_imports.insert(ffi::PTR2DOUBLE_ARRAY); + self.interface_gen.ffi_imports.insert(ffi::PTR2DOUBLE_ARRAY); "double" } _ => unreachable!(), @@ -2150,7 +2159,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; let ptr = self.locals.tmp("ptr"); - self.r#gen.ffi_imports.insert(ffi::STR2PTR); + self.interface_gen.ffi_imports.insert(ffi::STR2PTR); uwrite!( self.src, " @@ -2170,7 +2179,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; - self.r#gen.ffi_imports.insert(ffi::PTR2STR); + self.interface_gen.ffi_imports.insert(ffi::PTR2STR); uwrite!( self.src, " @@ -2189,17 +2198,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { assert!(block_results.is_empty()); let op = &operands[0]; - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); - let _align = self.r#gen.r#gen.sizes.align(element).align_wasm32(); + let size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); + let _align = self + .interface_gen + .world_gen + .sizes + .align(element) + .align_wasm32(); let address = self.locals.tmp("address"); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, element); + .type_name(self.interface_gen.name, element); let index = self.locals.tmp("index"); - self.r#gen.ffi_imports.insert(ffi::MALLOC); + self.interface_gen.ffi_imports.insert(ffi::MALLOC); uwrite!( self.src, " @@ -2229,11 +2248,16 @@ impl Bindgen for FunctionBindgen<'_, '_> { let length = &operands[1]; let array = self.locals.tmp("array"); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, element); - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + .type_name(self.interface_gen.name, element); + let size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); // let align = self.r#gen.r#gen.sizes.align(element); let index = self.locals.tmp("index"); @@ -2242,7 +2266,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { _ => todo!("result count == {}", results.len()), }; - self.r#gen.ffi_imports.insert(ffi::FREE); + self.interface_gen.ffi_imports.insert(ffi::FREE); uwrite!( self.src, " @@ -2286,8 +2310,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::CallInterface { func, async_ } => { - let name = self.r#gen.r#gen.pkg_resolver.func_call( - self.r#gen.name, + let name = self.interface_gen.world_gen.pkg_resolver.func_call( + self.interface_gen.name, func, self.func_interface, ); @@ -2302,10 +2326,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { ( res.clone(), res, - self.r#gen - .r#gen + self.interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &ty), + .type_name(self.interface_gen.name, &ty), ) } None => ("_ignore".into(), "".into(), "Unit".into()), @@ -2315,10 +2339,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(async_func_result.clone()); } let ffi = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .qualify_package(self.r#gen.name, FFI_DIR); + .qualify_package(self.interface_gen.name, FFI_DIR); uwrite!( self.src, r#" @@ -2367,10 +2391,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { Some(ty) => { let ty = format!( "({})", - self.r#gen - .r#gen + self.interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &ty) + .type_name(self.interface_gen.name, &ty) ); let result = self.locals.tmp("result"); if func.result.is_some() { @@ -2408,13 +2432,18 @@ impl Bindgen for FunctionBindgen<'_, '_> { for clean in &self.cleanup { let address = &clean.address; - self.r#gen.ffi_imports.insert(ffi::FREE); + self.interface_gen.ffi_imports.insert(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({address})",); } if self.needs_cleanup_list { - self.r#gen.ffi_imports.insert(ffi::FREE); - uwriteln!(self.src, "cleanup_list.each(mbt_ffi_free)",); + self.interface_gen.ffi_imports.insert(ffi::FREE); + uwrite!( + self.src, + " + cleanup_list.each(mbt_ffi_free) + ", + ); } match *amt { @@ -2430,7 +2459,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Load { offset } | Instruction::PointerLoad { offset } | Instruction::LengthLoad { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD32); + self.interface_gen.ffi_imports.insert(ffi::LOAD32); results.push(format!( "mbt_ffi_load32(({}) + {offset})", operands[0], @@ -2439,7 +2468,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load8U { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD8_U); + self.interface_gen.ffi_imports.insert(ffi::LOAD8_U); results.push(format!( "mbt_ffi_load8_u(({}) + {offset})", operands[0], @@ -2448,7 +2477,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load8S { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD8); + self.interface_gen.ffi_imports.insert(ffi::LOAD8); results.push(format!( "mbt_ffi_load8(({}) + {offset})", operands[0], @@ -2457,7 +2486,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load16U { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD16_U); + self.interface_gen.ffi_imports.insert(ffi::LOAD16_U); results.push(format!( "mbt_ffi_load16_u(({}) + {offset})", operands[0], @@ -2466,7 +2495,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load16S { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD16); + self.interface_gen.ffi_imports.insert(ffi::LOAD16); results.push(format!( "mbt_ffi_load16(({}) + {offset})", operands[0], @@ -2475,7 +2504,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I64Load { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD64); + self.interface_gen.ffi_imports.insert(ffi::LOAD64); results.push(format!( "mbt_ffi_load64(({}) + {offset})", operands[0], @@ -2484,7 +2513,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F32Load { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOADF32); + self.interface_gen.ffi_imports.insert(ffi::LOADF32); results.push(format!( "mbt_ffi_loadf32(({}) + {offset})", operands[0], @@ -2493,7 +2522,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F64Load { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOADF64); + self.interface_gen.ffi_imports.insert(ffi::LOADF64); results.push(format!( "mbt_ffi_loadf64(({}) + {offset})", operands[0], @@ -2504,7 +2533,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Store { offset } | Instruction::PointerStore { offset } | Instruction::LengthStore { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE32); + self.interface_gen.ffi_imports.insert(ffi::STORE32); uwriteln!( self.src, "mbt_ffi_store32(({}) + {offset}, {})", @@ -2515,7 +2544,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Store8 { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE8); + self.interface_gen.ffi_imports.insert(ffi::STORE8); uwriteln!( self.src, "mbt_ffi_store8(({}) + {offset}, {})", @@ -2526,7 +2555,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Store16 { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE16); + self.interface_gen.ffi_imports.insert(ffi::STORE16); uwriteln!( self.src, "mbt_ffi_store16(({}) + {offset}, {})", @@ -2537,7 +2566,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I64Store { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE64); + self.interface_gen.ffi_imports.insert(ffi::STORE64); uwriteln!( self.src, "mbt_ffi_store64(({}) + {offset}, {})", @@ -2548,7 +2577,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F32Store { offset } => { - self.r#gen.ffi_imports.insert(ffi::STOREF32); + self.interface_gen.ffi_imports.insert(ffi::STOREF32); uwriteln!( self.src, "mbt_ffi_storef32(({}) + {offset}, {})", @@ -2559,7 +2588,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F64Store { offset } => { - self.r#gen.ffi_imports.insert(ffi::STOREF64); + self.interface_gen.ffi_imports.insert(ffi::STOREF64); uwriteln!( self.src, "mbt_ffi_storef64(({}) + {offset}, {})", @@ -2570,17 +2599,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { } // TODO: see what we can do with align Instruction::Malloc { size, .. } => { - self.r#gen.ffi_imports.insert(ffi::MALLOC); + self.interface_gen.ffi_imports.insert(ffi::MALLOC); uwriteln!(self.src, "mbt_ffi_malloc({})", size.size_wasm32()) } Instruction::GuestDeallocate { .. } => { - self.r#gen.ffi_imports.insert(ffi::FREE); + self.interface_gen.ffi_imports.insert(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({})", operands[0]) } Instruction::GuestDeallocateString => { - self.r#gen.ffi_imports.insert(ffi::FREE); + self.interface_gen.ffi_imports.insert(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({})", operands[0]) } @@ -2624,7 +2653,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + let size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); // let align = self.r#gen.r#gen.sizes.align(element); if !body.trim().is_empty() { @@ -2641,7 +2675,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - self.r#gen.ffi_imports.insert(ffi::FREE); + self.interface_gen.ffi_imports.insert(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({address})",); } @@ -2654,15 +2688,15 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; // let qualifier = self.r#gen.qualify_package(self.func_interface); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)); + .type_name(self.interface_gen.name, &Type::Id(*ty)); let ffi = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .qualify_package(self.r#gen.name, FFI_DIR); + .qualify_package(self.interface_gen.name, FFI_DIR); let snake_name = format!("static_{}_future_table", ty.to_snake_case(),); @@ -2711,20 +2745,20 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = self.locals.tmp("result"); let op = &operands[0]; let qualifier = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .qualify_package(self.r#gen.name, self.func_interface); + .qualify_package(self.interface_gen.name, self.func_interface); let ty = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)); + .type_name(self.interface_gen.name, &Type::Id(*ty)); let ffi = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .qualify_package(self.r#gen.name, FFI_DIR); + .qualify_package(self.interface_gen.name, FFI_DIR); let snake_name = format!( "static_{}_stream_table", ty.replace(&qualifier, "").to_snake_case(), @@ -2832,8 +2866,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { } fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { - if self.r#gen.direction == Direction::Import { - self.r#gen.ffi_imports.insert(ffi::MALLOC); + if self.interface_gen.direction == Direction::Import { + self.interface_gen.ffi_imports.insert(ffi::MALLOC); let address = self.locals.tmp("return_area"); uwriteln!( self.src, @@ -2845,8 +2879,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { }); address } else { - self.r#gen.r#gen.return_area_size = self.r#gen.r#gen.return_area_size.max(size); - self.r#gen.r#gen.return_area_align = self.r#gen.r#gen.return_area_align.max(align); + self.interface_gen.world_gen.return_area_size = + self.interface_gen.world_gen.return_area_size.max(size); + self.interface_gen.world_gen.return_area_align = + self.interface_gen.world_gen.return_area_align.max(align); "return_area".into() } } @@ -2863,6 +2899,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if !self.cleanup.is_empty() { self.needs_cleanup_list = true; + self.interface_gen.ffi_imports.insert(ffi::FREE); for cleanup in &self.cleanup { let address = &cleanup.address; @@ -2879,7 +2916,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } fn sizes(&self) -> &SizeAlign { - &self.r#gen.r#gen.sizes + &self.interface_gen.world_gen.sizes } fn is_list_canonical(&self, _resolve: &Resolve, element: &Type) -> bool { From 4eecfd10c4c871ace16ba46e574493f7bc97339c Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 16:22:00 +0800 Subject: [PATCH 07/61] refactor: extract small functions --- crates/moonbit/src/lib.rs | 236 +++++++++++++++----------------------- 1 file changed, 90 insertions(+), 146 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index c1781791f..316cfe51c 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1550,11 +1550,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .collect::>(); // Hacky way to get the type name without type parameter - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_constructor(self.interface_gen.name, ty); + let ty = self.resolve_constructor(ty); let lifted = self.locals.tmp("lifted"); let cases = cases @@ -1612,6 +1608,32 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { results.push(lifted); } + + // Utilities + fn resolve_constructor(&mut self, ty: &Type) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .type_constructor(self.interface_gen.name, ty) + } + + fn resolve_type_name(&mut self, ty: &Type) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .type_name(self.interface_gen.name, ty) + } + + fn resolve_pkg(&mut self, pkg: &str) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .qualify_package(self.interface_gen.name, pkg) + } + + fn use_ffi(&mut self, str: &'static str) { + self.interface_gen.ffi_imports.insert(str); + } } impl Bindgen for FunctionBindgen<'_, '_> { @@ -1668,13 +1690,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::U8FromI32 => results.push(format!("({}).to_byte()", operands[0])), Instruction::I32FromS8 => { - self.interface_gen.ffi_imports.insert(ffi::EXTEND8); + self.use_ffi(ffi::EXTEND8); results.push(format!("mbt_ffi_extend8({})", operands[0])) } Instruction::S8FromI32 => results.push(format!("({} - 0x100)", operands[0])), Instruction::S16FromI32 => results.push(format!("({} - 0x10000)", operands[0])), Instruction::I32FromS16 => { - self.interface_gen.ffi_imports.insert(ffi::EXTEND16); + self.use_ffi(ffi::EXTEND16); results.push(format!("mbt_ffi_extend16({})", operands[0])) } Instruction::U16FromI32 => results.push(format!( @@ -1704,11 +1726,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U8 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_constructor(self.interface_gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1720,11 +1738,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U16 | Int::U32 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_constructor(self.interface_gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1736,11 +1750,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U64 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_constructor(self.interface_gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1756,27 +1766,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U8 => { results.push(format!( "{}({}.to_byte())", - self.interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0] )); } Int::U16 | Int::U32 => { results.push(format!( "{}({}.reinterpret_as_uint())", - self.interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0] )); } Int::U64 => { results.push(format!( "{}(({}).reinterpret_as_uint().to_uint64() | (({}).reinterpret_as_uint().to_uint64() << 32))", - self.interface_gen.world_gen.pkg_resolver.type_name(self.interface_gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0], operands[1] )); @@ -1786,11 +1790,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::HandleLower { ty, .. } => { let op = &operands[0]; let handle = self.locals.tmp("handle"); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_constructor(self.interface_gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwrite!( self.src, r#" @@ -1801,12 +1801,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::HandleLift { ty, .. } => { let op = &operands[0]; - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_constructor(self.interface_gen.name, &Type::Id(*ty)); - + let ty = self.resolve_constructor(&Type::Id(*ty)); results.push(format!( "{}::{}({})", ty, @@ -1835,10 +1830,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!( "{}::{{{ops}}}", - self.interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &Type::Id(*ty)) + self.resolve_type_name(&Type::Id(*ty)) )); } @@ -1962,11 +1954,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let some = self.blocks.pop().unwrap(); let _none = self.blocks.pop().unwrap(); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &Type::Id(*ty)); + let ty = self.resolve_type_name(&Type::Id(*ty)); let lifted = self.locals.tmp("lifted"); let op = &operands[0]; @@ -2025,10 +2013,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::EnumLift { ty, .. } => results.push(format!( "{}::from({})", - self.interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0] )), @@ -2036,7 +2021,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Type::U8 => { let op = &operands[0]; let ptr = self.locals.tmp("ptr"); - self.interface_gen.ffi_imports.insert(ffi::BYTES2PTR); + self.use_ffi(ffi::BYTES2PTR); uwriteln!( self.src, " @@ -2054,27 +2039,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { let ptr = self.locals.tmp("ptr"); let ty = match element { Type::U32 => { - self.interface_gen.ffi_imports.insert(ffi::UINT_ARRAY2PTR); + self.use_ffi(ffi::UINT_ARRAY2PTR); "uint" } Type::U64 => { - self.interface_gen.ffi_imports.insert(ffi::UINT64_ARRAY2PTR); + self.use_ffi(ffi::UINT64_ARRAY2PTR); "uint64" } Type::S32 => { - self.interface_gen.ffi_imports.insert(ffi::INT_ARRAY2PTR); + self.use_ffi(ffi::INT_ARRAY2PTR); "int" } Type::S64 => { - self.interface_gen.ffi_imports.insert(ffi::INT64_ARRAY2PTR); + self.use_ffi(ffi::INT64_ARRAY2PTR); "int64" } Type::F32 => { - self.interface_gen.ffi_imports.insert(ffi::FLOAT_ARRAY2PTR); + self.use_ffi(ffi::FLOAT_ARRAY2PTR); "float" } Type::F64 => { - self.interface_gen.ffi_imports.insert(ffi::DOUBLE_ARRAY2PTR); + self.use_ffi(ffi::DOUBLE_ARRAY2PTR); "double" } _ => unreachable!(), @@ -2100,7 +2085,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = self.locals.tmp("result"); let address = &operands[0]; let length = &operands[1]; - self.interface_gen.ffi_imports.insert(ffi::PTR2BYTES); + self.use_ffi(ffi::PTR2BYTES); uwrite!( self.src, " @@ -2113,27 +2098,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64 => { let ty = match element { Type::U32 => { - self.interface_gen.ffi_imports.insert(ffi::PTR2UINT_ARRAY); + self.use_ffi(ffi::PTR2UINT_ARRAY); "uint" } Type::U64 => { - self.interface_gen.ffi_imports.insert(ffi::PTR2UINT64_ARRAY); + self.use_ffi(ffi::PTR2UINT64_ARRAY); "uint64" } Type::S32 => { - self.interface_gen.ffi_imports.insert(ffi::PTR2INT_ARRAY); + self.use_ffi(ffi::PTR2INT_ARRAY); "int" } Type::S64 => { - self.interface_gen.ffi_imports.insert(ffi::PTR2INT64_ARRAY); + self.use_ffi(ffi::PTR2INT64_ARRAY); "int64" } Type::F32 => { - self.interface_gen.ffi_imports.insert(ffi::PTR2FLOAT_ARRAY); + self.use_ffi(ffi::PTR2FLOAT_ARRAY); "float" } Type::F64 => { - self.interface_gen.ffi_imports.insert(ffi::PTR2DOUBLE_ARRAY); + self.use_ffi(ffi::PTR2DOUBLE_ARRAY); "double" } _ => unreachable!(), @@ -2159,7 +2144,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; let ptr = self.locals.tmp("ptr"); - self.interface_gen.ffi_imports.insert(ffi::STR2PTR); + self.use_ffi(ffi::STR2PTR); uwrite!( self.src, " @@ -2179,7 +2164,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; - self.interface_gen.ffi_imports.insert(ffi::PTR2STR); + self.use_ffi(ffi::PTR2STR); uwrite!( self.src, " @@ -2211,14 +2196,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { .align(element) .align_wasm32(); let address = self.locals.tmp("address"); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, element); + let ty = self.resolve_type_name(element); let index = self.locals.tmp("index"); - self.interface_gen.ffi_imports.insert(ffi::MALLOC); + self.use_ffi(ffi::MALLOC); uwrite!( self.src, " @@ -2247,11 +2228,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; let array = self.locals.tmp("array"); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, element); + let ty = self.resolve_type_name(element); let size = self .interface_gen .world_gen @@ -2266,7 +2243,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { _ => todo!("result count == {}", results.len()), }; - self.interface_gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwrite!( self.src, " @@ -2323,14 +2300,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { match func.result { Some(ty) => { let res = self.locals.tmp("return_result"); - ( - res.clone(), - res, - self.interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &ty), - ) + (res.clone(), res, self.resolve_type_name(&ty)) } None => ("_ignore".into(), "".into(), "Unit".into()), }; @@ -2338,11 +2308,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if func.result.is_some() { results.push(async_func_result.clone()); } - let ffi = self - .interface_gen - .world_gen - .pkg_resolver - .qualify_package(self.interface_gen.name, FFI_DIR); + let ffi = self.resolve_pkg(FFI_DIR); uwrite!( self.src, r#" @@ -2389,13 +2355,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { - let ty = format!( - "({})", - self.interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &ty) - ); + let ty = format!("({})", self.resolve_type_name(&ty)); let result = self.locals.tmp("result"); if func.result.is_some() { results.push(result.clone()); @@ -2429,15 +2389,15 @@ impl Bindgen for FunctionBindgen<'_, '_> { } else { Vec::new() }; - + if !self.cleanup.is_empty() || self.needs_cleanup_list { + self.use_ffi(ffi::FREE); + } for clean in &self.cleanup { let address = &clean.address; - self.interface_gen.ffi_imports.insert(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({address})",); } if self.needs_cleanup_list { - self.interface_gen.ffi_imports.insert(ffi::FREE); uwrite!( self.src, " @@ -2459,7 +2419,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Load { offset } | Instruction::PointerLoad { offset } | Instruction::LengthLoad { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOAD32); + self.use_ffi(ffi::LOAD32); results.push(format!( "mbt_ffi_load32(({}) + {offset})", operands[0], @@ -2468,7 +2428,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load8U { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOAD8_U); + self.use_ffi(ffi::LOAD8_U); results.push(format!( "mbt_ffi_load8_u(({}) + {offset})", operands[0], @@ -2477,7 +2437,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load8S { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOAD8); + self.use_ffi(ffi::LOAD8); results.push(format!( "mbt_ffi_load8(({}) + {offset})", operands[0], @@ -2486,7 +2446,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load16U { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOAD16_U); + self.use_ffi(ffi::LOAD16_U); results.push(format!( "mbt_ffi_load16_u(({}) + {offset})", operands[0], @@ -2495,7 +2455,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load16S { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOAD16); + self.use_ffi(ffi::LOAD16); results.push(format!( "mbt_ffi_load16(({}) + {offset})", operands[0], @@ -2504,7 +2464,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I64Load { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOAD64); + self.use_ffi(ffi::LOAD64); results.push(format!( "mbt_ffi_load64(({}) + {offset})", operands[0], @@ -2513,7 +2473,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F32Load { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOADF32); + self.use_ffi(ffi::LOADF32); results.push(format!( "mbt_ffi_loadf32(({}) + {offset})", operands[0], @@ -2522,7 +2482,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F64Load { offset } => { - self.interface_gen.ffi_imports.insert(ffi::LOADF64); + self.use_ffi(ffi::LOADF64); results.push(format!( "mbt_ffi_loadf64(({}) + {offset})", operands[0], @@ -2533,7 +2493,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Store { offset } | Instruction::PointerStore { offset } | Instruction::LengthStore { offset } => { - self.interface_gen.ffi_imports.insert(ffi::STORE32); + self.use_ffi(ffi::STORE32); uwriteln!( self.src, "mbt_ffi_store32(({}) + {offset}, {})", @@ -2544,7 +2504,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Store8 { offset } => { - self.interface_gen.ffi_imports.insert(ffi::STORE8); + self.use_ffi(ffi::STORE8); uwriteln!( self.src, "mbt_ffi_store8(({}) + {offset}, {})", @@ -2555,7 +2515,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Store16 { offset } => { - self.interface_gen.ffi_imports.insert(ffi::STORE16); + self.use_ffi(ffi::STORE16); uwriteln!( self.src, "mbt_ffi_store16(({}) + {offset}, {})", @@ -2566,7 +2526,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I64Store { offset } => { - self.interface_gen.ffi_imports.insert(ffi::STORE64); + self.use_ffi(ffi::STORE64); uwriteln!( self.src, "mbt_ffi_store64(({}) + {offset}, {})", @@ -2577,7 +2537,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F32Store { offset } => { - self.interface_gen.ffi_imports.insert(ffi::STOREF32); + self.use_ffi(ffi::STOREF32); uwriteln!( self.src, "mbt_ffi_storef32(({}) + {offset}, {})", @@ -2588,7 +2548,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F64Store { offset } => { - self.interface_gen.ffi_imports.insert(ffi::STOREF64); + self.use_ffi(ffi::STOREF64); uwriteln!( self.src, "mbt_ffi_storef64(({}) + {offset}, {})", @@ -2599,17 +2559,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { } // TODO: see what we can do with align Instruction::Malloc { size, .. } => { - self.interface_gen.ffi_imports.insert(ffi::MALLOC); + self.use_ffi(ffi::MALLOC); uwriteln!(self.src, "mbt_ffi_malloc({})", size.size_wasm32()) } Instruction::GuestDeallocate { .. } => { - self.interface_gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({})", operands[0]) } Instruction::GuestDeallocateString => { - self.interface_gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({})", operands[0]) } @@ -2675,7 +2635,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - self.interface_gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({address})",); } @@ -2687,11 +2647,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = self.locals.tmp("result"); let op = &operands[0]; // let qualifier = self.r#gen.qualify_package(self.func_interface); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &Type::Id(*ty)); + let ty = self.resolve_type_name(&Type::Id(*ty)); let ffi = self .interface_gen .world_gen @@ -2744,21 +2700,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::StreamLift { ty, .. } => { let result = self.locals.tmp("result"); let op = &operands[0]; - let qualifier = self - .interface_gen - .world_gen - .pkg_resolver - .qualify_package(self.interface_gen.name, self.func_interface); - let ty = self - .interface_gen - .world_gen - .pkg_resolver - .type_name(self.interface_gen.name, &Type::Id(*ty)); - let ffi = self - .interface_gen - .world_gen - .pkg_resolver - .qualify_package(self.interface_gen.name, FFI_DIR); + let qualifier = self.resolve_pkg(self.func_interface); + let ty = self.resolve_type_name(&Type::Id(*ty)); + let ffi = self.resolve_pkg(FFI_DIR); let snake_name = format!( "static_{}_stream_table", ty.replace(&qualifier, "").to_snake_case(), @@ -2867,7 +2811,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { if self.interface_gen.direction == Direction::Import { - self.interface_gen.ffi_imports.insert(ffi::MALLOC); + self.use_ffi(ffi::MALLOC); let address = self.locals.tmp("return_area"); uwriteln!( self.src, @@ -2899,7 +2843,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if !self.cleanup.is_empty() { self.needs_cleanup_list = true; - self.interface_gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); for cleanup in &self.cleanup { let address = &cleanup.address; From 890cdb65fd1dd24f22e11d5cb4756cb85f07e8d0 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 17:37:58 +0800 Subject: [PATCH 08/61] refactor: use resolve for name mangling --- crates/moonbit/src/lib.rs | 210 ++++++++++++++++++++++++++------------ 1 file changed, 146 insertions(+), 64 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 316cfe51c..14aab2323 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -14,7 +14,8 @@ use wit_bindgen_core::{ uwrite, uwriteln, wit_parser::{ Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Int, InterfaceId, - Param, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeId, Variant, WorldId, + LiftLowerAbi, ManglingAndAbi, Param, Record, Resolve, ResourceIntrinsic, Result_, + SizeAlign, Tuple, Type, TypeId, Variant, WasmExport, WasmExportKind, WasmImport, WorldId, WorldKey, }, }; @@ -33,6 +34,10 @@ mod pkg; // Organization: // - one package per interface (export and import are treated as different interfaces) // - ffi utils are under `./ffi`, and the project entrance (package as link target) is under `./gen` + +// We use Legacy mangling for MoonBit (no specific reason, just because we haven't switched yet) +// We use AsyncCallback ABI for async functions + // TODO: Export will share the type signatures with the import by using a newtype alias pub(crate) const FFI_DIR: &str = "ffi"; @@ -145,6 +150,7 @@ impl MoonBit { name: &'a str, module: &'a str, direction: Direction, + interface: Option<&'a WorldKey>, ) -> InterfaceGenerator<'a> { let derive_opts = self.opts.derive.clone(); InterfaceGenerator { @@ -158,6 +164,7 @@ impl MoonBit { direction, ffi_imports: HashSet::new(), derive_opts, + interface, } } @@ -187,8 +194,12 @@ impl MoonBit { } // Link target if link { + let memory_name = self.pkg_resolver.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::Memory, + ); moon_pkg.push_str(",\n\"link\": {\n\"wasm\": {\n"); - moon_pkg.push_str("\"export-memory-name\": \"memory\",\n"); + moon_pkg.push_str(&format!("\"export-memory-name\": \"{memory_name}\",\n")); moon_pkg.push_str("\"heap-start-address\": 16,\n"); moon_pkg.push_str("\"exports\": [\n"); moon_pkg.indent(1); @@ -260,11 +271,11 @@ impl WorldGenerator for MoonBit { .insert(id, name.clone()); let module = &resolve.name_world_key(key); - let mut r#gen = self.interface(resolve, &name, module, Direction::Import); + let mut r#gen = self.interface(resolve, &name, module, Direction::Import, Some(key)); r#gen.types(id); for (_, func) in resolve.interfaces[id].functions.iter() { - r#gen.import(Some(key), func); + r#gen.import(func); } let fragment = r#gen.finish(); @@ -316,10 +327,10 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) { let name = PkgResolver::world_name(resolve, world); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); + let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import, None); for (_, func) in funcs { - r#gen.import(None, func); // None is "$root" + r#gen.import(func); } let result = r#gen.finish(); @@ -334,7 +345,7 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) { let name = PkgResolver::world_name(resolve, world); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); + let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import, None); for (ty_name, ty) in types { r#gen.define_type(ty_name, *ty); @@ -402,11 +413,11 @@ impl WorldGenerator for MoonBit { .insert(id, name.clone()); let module = &resolve.name_world_key(key); - let mut r#gen = self.interface(resolve, &name, module, Direction::Export); + let mut r#gen = self.interface(resolve, &name, module, Direction::Export, Some(key)); r#gen.types(id); for (_, func) in resolve.interfaces[id].functions.iter() { - r#gen.export(Some(key), func); + r#gen.export(func); } let fragment = r#gen.finish(); @@ -478,10 +489,10 @@ impl WorldGenerator for MoonBit { self.opts.r#gen_dir, PkgResolver::world_name(resolve, world) ); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Export); + let mut r#gen = self.interface(resolve, &name, "$root", Direction::Export, None); for (_, func) in funcs { - r#gen.export(None, func); + r#gen.export(func); } let fragment = r#gen.finish(); @@ -569,8 +580,13 @@ impl WorldGenerator for MoonBit { indent(&body).as_bytes(), ); - self.export - .insert("mbt_ffi_cabi_realloc".into(), "cabi_realloc".into()); + self.export.insert( + "mbt_ffi_cabi_realloc".into(), + self.pkg_resolver.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::Realloc, + ), + ); let mut moon_pkg = Source::default(); self.write_moon_pkg( @@ -600,6 +616,7 @@ struct InterfaceGenerator<'a> { name: &'a str, module: &'a str, direction: Direction, + interface: Option<&'a WorldKey>, // Options for deriving traits derive_opts: DeriveOpts, @@ -615,20 +632,16 @@ impl InterfaceGenerator<'_> { } } - fn import(&mut self, module: Option<&WorldKey>, func: &Function) { + fn import(&mut self, func: &Function) { let async_ = self .world_gen .opts .async_ - .is_async(self.resolve, module, func, false); + .is_async(self.resolve, self.interface, func, false); if async_ { self.world_gen.async_support.mark_async(); } - let interface_name = match module { - Some(key) => &self.resolve.name_world_key(key), - None => "$root", - }; let mut bindgen = FunctionBindgen::new( self, &func.name, @@ -639,12 +652,6 @@ impl InterfaceGenerator<'_> { .collect(), ); - let (variant, async_prefix) = if async_ { - (AbiVariant::GuestImportAsync, "[async-lower]") - } else { - (AbiVariant::GuestImport, "") - }; - abi::call( bindgen.interface_gen.resolve, AbiVariant::GuestImport, @@ -665,9 +672,14 @@ impl InterfaceGenerator<'_> { String::new() }; - let name = &func.name; - - let wasm_sig = self.resolve.wasm_signature(variant, func); + let wasm_sig = self.resolve.wasm_signature( + if async_ { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + func, + ); let result_type = match &wasm_sig.results[..] { [] => "".into(), @@ -691,16 +703,23 @@ impl InterfaceGenerator<'_> { let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); let sig = self.sig_string(&mbt_sig, async_); - let module = match module { - Some(key) => self.resolve.name_world_key(key), - None => "$root".into(), - }; + let (import_module, import_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(if async_ { + LiftLowerAbi::AsyncStackful + } else { + LiftLowerAbi::Sync + }), + WasmImport::Func { + interface: self.interface, + func, + }, + ); - self.r#generation_futures_and_streams_import("", func, interface_name); + self.r#generation_futures_and_streams_import("", func, &import_module); uwriteln!( self.ffi, - r#"fn wasmImport{camel_name}({params}) {result_type} = "{module}" "{async_prefix}{name}""# + r#"fn wasmImport{camel_name}({params}) {result_type} = "{import_module}" "{import_name}""# ); print_docs(&mut self.src, &func.docs); @@ -720,12 +739,12 @@ impl InterfaceGenerator<'_> { ); } - fn export(&mut self, interface: Option<&WorldKey>, func: &Function) { + fn export(&mut self, func: &Function) { let async_ = self .world_gen .opts .async_ - .is_async(self.resolve, interface, func, false); + .is_async(self.resolve, self.interface, func, false); if async_ { self.world_gen.async_support.mark_async(); } @@ -747,6 +766,7 @@ impl InterfaceGenerator<'_> { export_dir.as_str(), self.module, Direction::Export, + None, ); let mut bindgen = FunctionBindgen::new( @@ -803,15 +823,12 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); - // Async export prefix for FFI - let async_export_prefix = if async_ { "[async-lift]" } else { "" }; // Async functions return type - let interface_name = match interface { + let interface_name = match self.interface { Some(key) => Some(self.resolve.name_world_key(key)), None => None, }; - let export_name = func.legacy_core_export_name(interface_name.as_deref()); let module_name = interface_name.as_deref().unwrap_or("$root"); self.r#generation_futures_and_streams_import("[export]", func, module_name); @@ -823,10 +840,20 @@ impl InterfaceGenerator<'_> { }} "#, ); + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(if async_ { + LiftLowerAbi::AsyncCallback + } else { + LiftLowerAbi::Sync + }), + WasmExport::Func { + interface: self.interface, + func, + kind: WasmExportKind::Normal, + }, + ); - self.world_gen - .export - .insert(func_name, format!("{async_export_prefix}{export_name}")); + self.world_gen.export.insert(func_name, export_name); if async_ { let export_func_name = self @@ -841,12 +868,17 @@ impl InterfaceGenerator<'_> { else { unreachable!() }; - let func_name = func.name.clone(); - let import_module = self.resolve.name_world_key(interface.unwrap()); - self.world_gen.export.insert( - export_func_name.clone(), - format!("[callback]{async_export_prefix}{export_name}"), + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback), + WasmExport::Func { + interface: self.interface, + func, + kind: WasmExportKind::Callback, + }, ); + self.world_gen + .export + .insert(export_func_name.clone(), export_name); let task_return_param_tys = task_return_params .iter() .enumerate() @@ -876,10 +908,18 @@ impl InterfaceGenerator<'_> { .pkg_resolver .qualify_package(self.name, FFI_DIR); + let (task_return_module, task_return_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::AsyncStackful), + WasmImport::Func { + interface: self.interface, + func, + }, + ); + uwriteln!( self.src, r#" - fn {export_func_name}TaskReturn({task_return_param_tys}) = "[export]{import_module}" "[task-return]{func_name}" + fn {export_func_name}TaskReturn({task_return_param_tys}) = "{task_return_module}" "{task_return_name}" pub fn {snake_func_name}_task_return({return_expr}) -> Unit {{ {task_return_body} @@ -896,7 +936,8 @@ impl InterfaceGenerator<'_> { }} "# ); - } else if abi::guest_export_needs_post_return(self.resolve, func) { + } + if abi::guest_export_needs_post_return(self.resolve, func) { let params = sig .results .iter() @@ -932,9 +973,15 @@ impl InterfaceGenerator<'_> { }} "# ); - self.world_gen - .export - .insert(func_name, format!("cabi_post_{export_name}")); + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::Func { + interface: self.interface, + func, + kind: WasmExportKind::PostReturn, + }, + ); + self.world_gen.export.insert(func_name, export_name); } print_docs(&mut self.stub, &func.docs); @@ -1013,9 +1060,8 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { ); } - fn type_resource(&mut self, _id: TypeId, name: &str, docs: &Docs) { + fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) { print_docs(&mut self.src, docs); - let type_name = name; let name = name.to_moonbit_type_ident(); let mut deriviation: Vec<_> = Vec::new(); @@ -1039,9 +1085,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { deriviation.join(", "), ); - let module = self.module; - if self.direction == Direction::Import { + let (drop_module, drop_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ImportedDrop, + }, + ); uwrite!( &mut self.src, r#" @@ -1056,10 +1108,34 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { uwrite!( &mut self.ffi, r#" - fn wasmImportResourceDrop{name}(resource : Int) = "{module}" "[resource-drop]{type_name}" + fn wasmImportResourceDrop{name}(resource : Int) = "{drop_module}" "{drop_name}" "#, ) } else { + let (drop_module, drop_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ExportedDrop, + }, + ); + let (new_module, new_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ExportedNew, + }, + ); + let (rep_module, rep_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ExportedRep, + }, + ); uwrite!( &mut self.src, r#" @@ -1067,21 +1143,21 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { pub fn {name}::new(rep : Int) -> {name} {{ {name}::{name}(wasmExportResourceNew{name}(rep)) }} - fn wasmExportResourceNew{name}(rep : Int) -> Int = "[export]{module}" "[resource-new]{type_name}" + fn wasmExportResourceNew{name}(rep : Int) -> Int = "{new_module}" "{new_name}" /// Drops a resource handle. pub fn {name}::drop(self : Self) -> Unit {{ let {name}(resource) = self wasmExportResourceDrop{name}(resource) }} - fn wasmExportResourceDrop{name}(resource : Int) = "[export]{module}" "[resource-drop]{type_name}" + fn wasmExportResourceDrop{name}(resource : Int) = "{drop_module}" "{drop_name}" /// Gets the `Int` representation of the resource pointed to the given handle. pub fn {name}::rep(self : Self) -> Int {{ let {name}(resource) = self wasmExportResourceRep{name}(resource) }} - fn wasmExportResourceRep{name}(resource : Int) -> Int = "[export]{module}" "[resource-rep]{type_name}" + fn wasmExportResourceRep{name}(resource : Int) -> Int = "{rep_module}" "{rep_name}" "#, ); @@ -1112,9 +1188,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .qualify_package(self.world_gen.opts.r#gen_dir.as_str(), self.name) ); - self.world_gen - .export - .insert(func_name, format!("{module}#[dtor]{type_name}")); + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::ResourceDtor { + interface: self.interface.unwrap(), + resource: id, + }, + ); + + self.world_gen.export.insert(func_name, export_name); } } From b8be1867cefd9fa8d4164f1c0c75f29c108a6680 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 18:22:32 +0800 Subject: [PATCH 09/61] refactor: organize code better --- crates/moonbit/src/lib.rs | 199 ++++++++++++++++++++------------------ 1 file changed, 106 insertions(+), 93 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 14aab2323..e9b9d08eb 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -633,6 +633,7 @@ impl InterfaceGenerator<'_> { } fn import(&mut self, func: &Function) { + // Determine if the function is async let async_ = self .world_gen .opts @@ -642,104 +643,105 @@ impl InterfaceGenerator<'_> { self.world_gen.async_support.mark_async(); } - let mut bindgen = FunctionBindgen::new( - self, - &func.name, - self.name, - func.params - .iter() - .map(|Param { name, .. }| name.to_moonbit_ident()) - .collect(), - ); - - abi::call( - bindgen.interface_gen.resolve, - AbiVariant::GuestImport, - LiftLower::LowerArgsLiftResults, - func, - &mut bindgen, - false, - ); - - let mut src = bindgen.src.clone(); + let ffi_import_name = format!("wasmImport{}", func.name.to_upper_camel_case()); - let cleanup_list = if bindgen.needs_cleanup_list { - " - let cleanup_list : Array[Int] = [] - " - .into() - } else { - String::new() - }; + // Generate the core wasm abi + { + let wasm_sig = self.resolve.wasm_signature( + if async_ { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + func, + ); - let wasm_sig = self.resolve.wasm_signature( - if async_ { - AbiVariant::GuestImportAsync - } else { - AbiVariant::GuestImport - }, - func, - ); + let result_type = match &wasm_sig.results[..] { + [] => "".into(), + [result] => format!("-> {}", wasm_type(*result)), + _ => unimplemented!("multi-value results are not supported yet"), + }; - let result_type = match &wasm_sig.results[..] { - [] => "".into(), - [result] => format!("-> {}", wasm_type(*result)), - _ => unreachable!(), - }; + let params = wasm_sig + .params + .iter() + .enumerate() + .map(|(i, param)| format!("p{i} : {}", wasm_type(*param))) + .collect::>() + .join(", "); - let camel_name = func.name.to_upper_camel_case(); + let (import_module, import_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(if async_ { + LiftLowerAbi::AsyncStackful + } else { + LiftLowerAbi::Sync + }), + WasmImport::Func { + interface: self.interface, + func, + }, + ); - let params = wasm_sig - .params - .iter() - .enumerate() - .map(|(i, param)| { - let ty = wasm_type(*param); - format!("p{i} : {ty}") - }) - .collect::>() - .join(", "); + uwriteln!( + self.ffi, + r#"fn {ffi_import_name}({params}) {result_type} = "{import_module}" "{import_name}""# + ); + } - let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); - let sig = self.sig_string(&mbt_sig, async_); + // Generate the MoonBit wrapper + if async_ { + // self.r#generation_futures_and_streams_import("", func, &import_module); + // if async_ { + // src = self.r#generate_async_import_function(func, mbt_sig, &wasm_sig); + // } + todo!("async import not supported yet"); + } else { + let mut bindgen = FunctionBindgen::new( + self, + &func.name, + self.name, + func.params + .iter() + .map(|Param { name, .. }| name.to_moonbit_ident()) + .collect(), + ); - let (import_module, import_name) = self.resolve.wasm_import_name( - ManglingAndAbi::Legacy(if async_ { - LiftLowerAbi::AsyncStackful - } else { - LiftLowerAbi::Sync - }), - WasmImport::Func { - interface: self.interface, + abi::call( + bindgen.interface_gen.resolve, + AbiVariant::GuestImport, + LiftLower::LowerArgsLiftResults, func, - }, - ); + &mut bindgen, + false, + ); - self.r#generation_futures_and_streams_import("", func, &import_module); + let src = bindgen.src.clone(); - uwriteln!( - self.ffi, - r#"fn wasmImport{camel_name}({params}) {result_type} = "{import_module}" "{import_name}""# - ); + let cleanup_list = if bindgen.needs_cleanup_list { + "let cleanup_list : Array[Int] = []" + } else { + "" + }; - print_docs(&mut self.src, &func.docs); + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); + let sig = self.sig_string(&mbt_sig, async_); - if async_ { - src = self.r#generate_async_import_function(func, mbt_sig, &wasm_sig); - } + print_docs(&mut self.src, &func.docs); - uwrite!( - self.src, - r#" - {sig} {{ + uwrite!( + self.src, + r#" + {sig} {{ {cleanup_list} {src} }} "# - ); + ); + } } fn export(&mut self, func: &Function) { + // Determine if is async let async_ = self .world_gen .opts @@ -749,6 +751,25 @@ impl InterfaceGenerator<'_> { self.world_gen.async_support.mark_async(); } + // Generate stub for user + { + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); + let func_sig = self.sig_string(&mbt_sig, async_); + + print_docs(&mut self.stub, &func.docs); + uwrite!( + self.stub, + r#" + {func_sig} {{ + ... + }} + "# + ); + } + + // Generate the caller function + // But it is located in the export module (GEN_DIR) + let variant = if async_ { AbiVariant::GuestExportAsync } else { @@ -756,9 +777,7 @@ impl InterfaceGenerator<'_> { }; let sig = self.resolve.wasm_signature(variant, func); - let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); - let func_sig = self.sig_string(&mbt_sig, async_); let export_dir = self.world_gen.opts.r#gen_dir.clone(); let mut toplevel_generator = self.world_gen.interface( @@ -855,6 +874,7 @@ impl InterfaceGenerator<'_> { self.world_gen.export.insert(func_name, export_name); + // If async, we also need a callback function and a task_return intrinsic if async_ { let export_func_name = self .world_gen @@ -937,6 +957,8 @@ impl InterfaceGenerator<'_> { "# ); } + + // If post return is needed, generate it if abi::guest_export_needs_post_return(self.resolve, func) { let params = sig .results @@ -983,16 +1005,6 @@ impl InterfaceGenerator<'_> { ); self.world_gen.export.insert(func_name, export_name); } - - print_docs(&mut self.stub, &func.docs); - uwrite!( - self.stub, - r#" - {func_sig} {{ - ... - }} - "# - ); } fn sig_string(&mut self, sig: &MoonbitSignature, async_: bool) -> String { @@ -1006,14 +1018,15 @@ impl InterfaceGenerator<'_> { .collect::>(); let params = params.join(", "); - let (async_prefix, async_suffix) = if async_ { ("async ", "") } else { ("", "") }; let result_type = match &sig.result_type { None => "Unit".into(), Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), }; format!( - "pub {async_prefix}fn {}({params}) -> {}{async_suffix}", - sig.name, result_type + "pub {}fn {}({params}) -> {}", + if async_ { "async " } else { "" }, + sig.name, + result_type ) } } From 6c6132c10be645bfc6128a9b81cc3b3e8cb3abf5 Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 18:25:16 +0800 Subject: [PATCH 10/61] fix: use callback instead of stackful --- crates/moonbit/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index e9b9d08eb..2167c506b 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -672,7 +672,7 @@ impl InterfaceGenerator<'_> { let (import_module, import_name) = self.resolve.wasm_import_name( ManglingAndAbi::Legacy(if async_ { - LiftLowerAbi::AsyncStackful + LiftLowerAbi::AsyncCallback } else { LiftLowerAbi::Sync }), @@ -929,7 +929,7 @@ impl InterfaceGenerator<'_> { .qualify_package(self.name, FFI_DIR); let (task_return_module, task_return_name) = self.resolve.wasm_import_name( - ManglingAndAbi::Legacy(LiftLowerAbi::AsyncStackful), + ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback), WasmImport::Func { interface: self.interface, func, From 850151e02b0cbaaf617a746bdfc78783e386a5e0 Mon Sep 17 00:00:00 2001 From: zihang Date: Wed, 24 Dec 2025 10:24:25 +0800 Subject: [PATCH 11/61] refactor: simplify optionlift --- crates/moonbit/src/lib.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 2167c506b..a7838612b 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -2045,7 +2045,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - Instruction::OptionLift { payload, ty } => { + Instruction::OptionLift { ty, .. } => { let some = self.blocks.pop().unwrap(); let _none = self.blocks.pop().unwrap(); @@ -2053,17 +2053,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let lifted = self.locals.tmp("lifted"); let op = &operands[0]; - let payload = if self - .interface_gen - .world_gen - .pkg_resolver - .non_empty_type(Some(*payload)) - .is_some() - { - some.results.into_iter().next().unwrap() - } else { - "None".into() - }; + let assignment = some.results.first().unwrap(); let some = some.body; @@ -2074,7 +2064,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { 0 => Option::None 1 => {{ {some} - Option::Some({payload}) + Option::Some({assignment}) }} _ => panic() }} From 2761e08971163da5f61425610120ea96593babb0 Mon Sep 17 00:00:00 2001 From: zihang Date: Wed, 24 Dec 2025 11:53:19 +0800 Subject: [PATCH 12/61] refactor: remove func name passing --- crates/moonbit/src/async_support.rs | 58 ++++++++++++++++++++++------- crates/moonbit/src/lib.rs | 10 +---- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index e22258a95..537639657 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -122,7 +122,10 @@ impl<'a> InterfaceGenerator<'a> { let _lower_ptr : Int = {ffi}malloc({}) "#, elem_info.size.size_wasm32(), - ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR) + ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR) )); for ((offset, ty), name) in offsets.iter().zip( @@ -143,16 +146,24 @@ impl<'a> InterfaceGenerator<'a> { } } } else { - let mut f = FunctionBindgen::new(self, "INVALID", self.name, Box::new([])); + let mut f = FunctionBindgen::new(self, self.name, Box::new([])); for (name, ty) in mbt_sig.params.iter() { - lower_params.extend(abi::lower_flat(f.interface_gen.resolve, &mut f, name.clone(), ty)); + lower_params.extend(abi::lower_flat( + f.interface_gen.resolve, + &mut f, + name.clone(), + ty, + )); } lower_results.push(f.src.clone()); } let func_name = func.name.to_upper_camel_case(); - let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); let call_import = |params: &Vec| { format!( @@ -266,7 +277,10 @@ impl<'a> InterfaceGenerator<'a> { None => "Unit".into(), }; - let type_name = self.world_gen.pkg_resolver.type_name(self.name, &Type::Id(ty)); + let type_name = self + .world_gen + .pkg_resolver + .type_name(self.name, &Type::Id(ty)); let name = result.to_upper_camel_case(); let kind = match payload_for { PayloadFor::Future => "future", @@ -283,7 +297,10 @@ impl<'a> InterfaceGenerator<'a> { PayloadFor::Future => "", PayloadFor::Stream => "List", }; - let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); let mut dealloc_list; let malloc; @@ -414,27 +431,36 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ indirect: bool, module: &str, ) -> String { - let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); + let mut f = FunctionBindgen::new(self, module, Box::new([])); abi::deallocate_lists_in_types(f.interface_gen.resolve, types, operands, indirect, &mut f); f.src } fn lift_from_memory(&mut self, address: &str, ty: &Type, module: &str) -> (String, String) { - let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); + let mut f = FunctionBindgen::new(self, module, Box::new([])); let result = abi::lift_from_memory(f.interface_gen.resolve, &mut f, address.into(), ty); (f.src, result) } fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { - let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - abi::lower_to_memory(f.interface_gen.resolve, &mut f, address.into(), value.into(), ty); + let mut f = FunctionBindgen::new(self, module, Box::new([])); + abi::lower_to_memory( + f.interface_gen.resolve, + &mut f, + address.into(), + value.into(), + ty, + ); f.src } fn malloc_memory(&mut self, address: &str, length: &str, ty: &Type) -> String { let size = self.world_gen.sizes.size(ty).size_wasm32(); - let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); format!("let {address} = {ffi}malloc({size} * {length});") } @@ -452,7 +478,10 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ lift_func: &str, ty: &Type, ) -> String { - let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); if self.is_list_canonical(self.resolve, ty) { if ty == &Type::U8 { return format!("{ffi}ptr2bytes({address}, {length})"); @@ -485,7 +514,10 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ fn list_lower_to_memory(&mut self, lower_func: &str, value: &str, ty: &Type) -> String { // Align the address, moonbit only supports wasm32 for now - let ffi = self.world_gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); if self.is_list_canonical(self.resolve, ty) { if ty == &Type::U8 { return format!("{ffi}bytes2ptr({value})"); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index a7838612b..35e456f9b 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -698,7 +698,6 @@ impl InterfaceGenerator<'_> { } else { let mut bindgen = FunctionBindgen::new( self, - &func.name, self.name, func.params .iter() @@ -790,7 +789,6 @@ impl InterfaceGenerator<'_> { let mut bindgen = FunctionBindgen::new( &mut toplevel_generator, - &func.name, self.name, (0..sig.params.len()).map(|i| format!("p{i}")).collect(), ); @@ -973,7 +971,6 @@ impl InterfaceGenerator<'_> { let mut bindgen = FunctionBindgen::new( self, - "INVALID", self.name, (0..sig.results.len()).map(|i| format!("p{i}")).collect(), ); @@ -1498,7 +1495,6 @@ enum DeferredTaskReturn { struct FunctionBindgen<'a, 'b> { interface_gen: &'b mut InterfaceGenerator<'a>, - func_name: &'b str, func_interface: &'b str, params: Box<[String]>, src: String, @@ -1514,7 +1510,6 @@ struct FunctionBindgen<'a, 'b> { impl<'a, 'b> FunctionBindgen<'a, 'b> { fn new( r#gen: &'b mut InterfaceGenerator<'a>, - func_name: &'b str, func_interface: &'b str, params: Box<[String]>, ) -> FunctionBindgen<'a, 'b> { @@ -1524,7 +1519,6 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { }); Self { interface_gen: r#gen, - func_name, func_interface, params, src: String::new(), @@ -2349,7 +2343,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::IterBasePointer => results.push("iter_base".into()), - Instruction::CallWasm { sig, .. } => { + Instruction::CallWasm { sig, name } => { let assignment = match &sig.results[..] { [result] => { let ty = wasm_type(*result); @@ -2364,7 +2358,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { _ => unreachable!(), }; - let func_name = self.func_name.to_upper_camel_case(); + let func_name = name.to_upper_camel_case(); let operands = operands.join(", "); // TODO: handle this to support async functions From c9f4d61e38f2f5e74b8481133d95f048e44fcd01 Mon Sep 17 00:00:00 2001 From: zihang Date: Wed, 24 Dec 2025 13:46:22 +0800 Subject: [PATCH 13/61] refactor: move LiftArgsLowerResults to each pkg Previously we had them all in the pkg that does the exportation, but it made the code generator too complex. Now we move them away to simplify this As a side effect, there's no unified return_area. The return_area will be freed with each post_return --- crates/moonbit/src/async_support.rs | 8 +- crates/moonbit/src/lib.rs | 229 +++++++++++++------------- tests/runtime/resources/resources.mbt | 2 +- 3 files changed, 118 insertions(+), 121 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 537639657..d7a71e3a5 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -146,7 +146,7 @@ impl<'a> InterfaceGenerator<'a> { } } } else { - let mut f = FunctionBindgen::new(self, self.name, Box::new([])); + let mut f = FunctionBindgen::new(self, Box::new([])); for (name, ty) in mbt_sig.params.iter() { lower_params.extend(abi::lower_flat( f.interface_gen.resolve, @@ -431,20 +431,20 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ indirect: bool, module: &str, ) -> String { - let mut f = FunctionBindgen::new(self, module, Box::new([])); + let mut f = FunctionBindgen::new(self, Box::new([])); abi::deallocate_lists_in_types(f.interface_gen.resolve, types, operands, indirect, &mut f); f.src } fn lift_from_memory(&mut self, address: &str, ty: &Type, module: &str) -> (String, String) { - let mut f = FunctionBindgen::new(self, module, Box::new([])); + let mut f = FunctionBindgen::new(self, Box::new([])); let result = abi::lift_from_memory(f.interface_gen.resolve, &mut f, address.into(), ty); (f.src, result) } fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { - let mut f = FunctionBindgen::new(self, module, Box::new([])); + let mut f = FunctionBindgen::new(self, Box::new([])); abi::lower_to_memory( f.interface_gen.resolve, &mut f, diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 35e456f9b..67c754fe6 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -130,15 +130,10 @@ pub struct MoonBit { interface_ns: Ns, // dependencies between packages pkg_resolver: PkgResolver, - export: HashMap, + // Wasm export name -> (exported function name, func) + export: HashMap, export_ns: Ns, - // return area allocation - return_area_size: ArchitectureSize, - return_area_align: Alignment, - - // Collected inline ffi functions used in the final export directory - builtins: HashSet<&'static str>, async_support: AsyncSupport, } @@ -148,7 +143,6 @@ impl MoonBit { &'a mut self, resolve: &'a Resolve, name: &'a str, - module: &'a str, direction: Direction, interface: Option<&'a WorldKey>, ) -> InterfaceGenerator<'a> { @@ -160,7 +154,6 @@ impl MoonBit { world_gen: self, resolve, name, - module, direction, ffi_imports: HashSet::new(), derive_opts, @@ -206,8 +199,15 @@ impl MoonBit { let mut exports = self .export .iter() - .map(|(k, v)| format!("\"{k}:{v}\"")) + .map(|(export_name, (func_name, _))| format!("\"{func_name}:{export_name}\"")) .collect::>(); + exports.push(format!( + "\"mbt_ffi_cabi_realloc:{}\"", + self.pkg_resolver.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::Realloc, + ), + )); exports.sort(); uwrite!(moon_pkg, "{}", exports.join(",\n")); moon_pkg.deindent(1); @@ -270,8 +270,7 @@ impl WorldGenerator for MoonBit { .import_interface_names .insert(id, name.clone()); - let module = &resolve.name_world_key(key); - let mut r#gen = self.interface(resolve, &name, module, Direction::Import, Some(key)); + let mut r#gen = self.interface(resolve, &name, Direction::Import, Some(key)); r#gen.types(id); for (_, func) in resolve.interfaces[id].functions.iter() { @@ -327,7 +326,7 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) { let name = PkgResolver::world_name(resolve, world); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import, None); + let mut r#gen = self.interface(resolve, &name, Direction::Import, None); for (_, func) in funcs { r#gen.import(func); @@ -345,7 +344,7 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) { let name = PkgResolver::world_name(resolve, world); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import, None); + let mut r#gen = self.interface(resolve, &name, Direction::Import, None); for (ty_name, ty) in types { r#gen.define_type(ty_name, *ty); @@ -412,8 +411,7 @@ impl WorldGenerator for MoonBit { .export_interface_names .insert(id, name.clone()); - let module = &resolve.name_world_key(key); - let mut r#gen = self.interface(resolve, &name, module, Direction::Export, Some(key)); + let mut r#gen = self.interface(resolve, &name, Direction::Export, Some(key)); r#gen.types(id); for (_, func) in resolve.interfaces[id].functions.iter() { @@ -458,20 +456,15 @@ impl WorldGenerator for MoonBit { files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); } - let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, VERSION); - - uwriteln!(&mut body, "{}", fragment.ffi); - files.push( - &format!( - "{}/{}_export.mbt", - self.opts.r#gen_dir, - directory.to_snake_case() - ), - indent(&body).as_bytes(), - ); + // FFI + let mut ffi = Source::default(); + wit_bindgen_core::generated_preamble(&mut ffi, VERSION); - self.builtins.extend(fragment.builtins.iter()); + uwriteln!(&mut ffi, "{}", fragment.ffi); + for b in fragment.builtins.iter() { + uwriteln!(ffi, "{}", b); + } + files.push(&format!("{directory}/ffi.mbt",), indent(&ffi).as_bytes()); } Ok(()) @@ -489,7 +482,7 @@ impl WorldGenerator for MoonBit { self.opts.r#gen_dir, PkgResolver::world_name(resolve, world) ); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Export, None); + let mut r#gen = self.interface(resolve, &name, Direction::Export, None); for (_, func) in funcs { r#gen.export(func); @@ -526,15 +519,10 @@ impl WorldGenerator for MoonBit { let mut export = Source::default(); wit_bindgen_core::generated_preamble(&mut export, VERSION); uwriteln!(&mut export, "{}", fragment.ffi); - self.builtins.extend(fragment.builtins.iter()); - files.push( - &format!( - "{}/{}_export.mbt", - self.opts.r#gen_dir, - directory.to_snake_case() - ), - indent(&export).as_bytes(), - ); + for b in fragment.builtins.iter() { + uwriteln!(&mut export, "{}", b); + } + files.push(&format!("{directory}/ffi.mbt",), indent(&export).as_bytes()); } Ok(()) @@ -558,36 +546,20 @@ impl WorldGenerator for MoonBit { // Export project entry point let mut body = Source::default(); wit_bindgen_core::generated_preamble(&mut body, VERSION); - uwriteln!(&mut body, "{}", ffi::CABI_REALLOC); - self.builtins.insert(ffi::MALLOC); - self.builtins.insert(ffi::FREE); - - if !self.return_area_size.is_empty() { - uwriteln!( - &mut body, - " - let return_area : Int = mbt_ffi_malloc({}) - ", - self.return_area_size.size_wasm32(), - ); - self.builtins.insert(ffi::MALLOC); - } - for builtin in &self.builtins { + // CABI Realloc + for builtin in [ffi::CABI_REALLOC, ffi::MALLOC, ffi::FREE] { uwriteln!(&mut body, "{}", builtin); } + // Import all exported interfaces + for (_, (_, impl_)) in self.export.iter() { + uwriteln!(&mut body, "{impl_}"); + } + files.push( &format!("{}/ffi.mbt", self.opts.r#gen_dir), indent(&body).as_bytes(), ); - self.export.insert( - "mbt_ffi_cabi_realloc".into(), - self.pkg_resolver.resolve.wasm_export_name( - ManglingAndAbi::Legacy(LiftLowerAbi::Sync), - WasmExport::Realloc, - ), - ); - let mut moon_pkg = Source::default(); self.write_moon_pkg( &mut moon_pkg, @@ -595,7 +567,7 @@ impl WorldGenerator for MoonBit { true, ); files.push( - &format!("{}/moon.pkg.json", self.opts.r#gen_dir,), + &format!("{}/moon.pkg.json", self.opts.r#gen_dir), indent(&moon_pkg).as_bytes(), ); @@ -614,7 +586,6 @@ struct InterfaceGenerator<'a> { resolve: &'a Resolve, // The current interface getting generated name: &'a str, - module: &'a str, direction: Direction, interface: Option<&'a WorldKey>, @@ -698,7 +669,6 @@ impl InterfaceGenerator<'_> { } else { let mut bindgen = FunctionBindgen::new( self, - self.name, func.params .iter() .map(|Param { name, .. }| name.to_moonbit_ident()) @@ -767,8 +737,6 @@ impl InterfaceGenerator<'_> { } // Generate the caller function - // But it is located in the export module (GEN_DIR) - let variant = if async_ { AbiVariant::GuestExportAsync } else { @@ -777,19 +745,8 @@ impl InterfaceGenerator<'_> { let sig = self.resolve.wasm_signature(variant, func); - let export_dir = self.world_gen.opts.r#gen_dir.clone(); - - let mut toplevel_generator = self.world_gen.interface( - self.resolve, - export_dir.as_str(), - self.module, - Direction::Export, - None, - ); - let mut bindgen = FunctionBindgen::new( - &mut toplevel_generator, - self.name, + self, (0..sig.params.len()).map(|i| format!("p{i}")).collect(), ); @@ -809,12 +766,6 @@ impl InterfaceGenerator<'_> { let deferred_task_return = bindgen.deferred_task_return.clone(); let src = bindgen.src; - assert!(toplevel_generator.src.is_empty()); - assert!(toplevel_generator.ffi.is_empty()); - - // Transfer ffi_imports from toplevel_generator to self - self.ffi_imports - .extend(toplevel_generator.ffi_imports.iter()); let result_type = match &sig.results[..] { [] => "Unit", @@ -870,7 +821,24 @@ impl InterfaceGenerator<'_> { }, ); - self.world_gen.export.insert(func_name, export_name); + let export = format!( + r#" + pub fn {func_name}({params}) -> {result_type} {{ + {}{func_name}({}) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + (0..sig.params.len()) + .map(|i| format!("p{i}")) + .collect::>() + .join(", "), + ); + + self.world_gen + .export + .insert(export_name, (func_name, export)); // If async, we also need a callback function and a task_return intrinsic if async_ { @@ -894,9 +862,7 @@ impl InterfaceGenerator<'_> { kind: WasmExportKind::Callback, }, ); - self.world_gen - .export - .insert(export_func_name.clone(), export_name); + let task_return_param_tys = task_return_params .iter() .enumerate() @@ -954,6 +920,20 @@ impl InterfaceGenerator<'_> { }} "# ); + let export = format!( + r#" + pub fn {export_func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ + {}{snake_func_name}_callback(event_raw, waitable, code) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + ); + + self.world_gen + .export + .insert(export_name, (export_func_name.clone(), export)); } // If post return is needed, generate it @@ -971,7 +951,6 @@ impl InterfaceGenerator<'_> { let mut bindgen = FunctionBindgen::new( self, - self.name, (0..sig.results.len()).map(|i| format!("p{i}")).collect(), ); @@ -1000,7 +979,23 @@ impl InterfaceGenerator<'_> { kind: WasmExportKind::PostReturn, }, ); - self.world_gen.export.insert(func_name, export_name); + let export = format!( + r#" + pub fn {func_name}({params}) -> Unit {{ + {}{func_name}({}) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + (0..sig.results.len()) + .map(|i| format!("p{i}")) + .collect::>() + .join(", "), + ); + self.world_gen + .export + .insert(export_name, (func_name, export)); } } @@ -1190,12 +1185,9 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { self.ffi, r#" pub fn {func_name}(handle : Int) -> Unit {{ - {}{name}::dtor(handle) + {name}::dtor(handle) }} "#, - self.world_gen - .pkg_resolver - .qualify_package(self.world_gen.opts.r#gen_dir.as_str(), self.name) ); let export_name = self.resolve.wasm_export_name( @@ -1206,7 +1198,19 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { }, ); - self.world_gen.export.insert(func_name, export_name); + let export = format!( + r#" + pub fn {func_name}(handle : Int) -> Unit {{ + {}{func_name}(handle) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + ); + self.world_gen + .export + .insert(export_name, (func_name, export)); } } @@ -1495,7 +1499,6 @@ enum DeferredTaskReturn { struct FunctionBindgen<'a, 'b> { interface_gen: &'b mut InterfaceGenerator<'a>, - func_interface: &'b str, params: Box<[String]>, src: String, locals: Ns, @@ -1510,7 +1513,6 @@ struct FunctionBindgen<'a, 'b> { impl<'a, 'b> FunctionBindgen<'a, 'b> { fn new( r#gen: &'b mut InterfaceGenerator<'a>, - func_interface: &'b str, params: Box<[String]>, ) -> FunctionBindgen<'a, 'b> { let mut locals = Ns::default(); @@ -1519,7 +1521,6 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { }); Self { interface_gen: r#gen, - func_interface, params, src: String::new(), locals, @@ -2369,7 +2370,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let name = self.interface_gen.world_gen.pkg_resolver.func_call( self.interface_gen.name, func, - self.func_interface, + self.interface_gen.name, ); let args = operands.join(", "); @@ -2779,7 +2780,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::StreamLift { ty, .. } => { let result = self.locals.tmp("result"); let op = &operands[0]; - let qualifier = self.resolve_pkg(self.func_interface); + let qualifier = self.resolve_pkg(self.interface_gen.name); let ty = self.resolve_type_name(&Type::Id(*ty)); let ffi = self.resolve_pkg(FFI_DIR); let snake_name = format!( @@ -2888,26 +2889,22 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { + fn return_pointer(&mut self, size: ArchitectureSize, _align: Alignment) -> String { + self.use_ffi(ffi::MALLOC); + let address = self.locals.tmp("return_area"); + uwriteln!( + self.src, + "let {address} = mbt_ffi_malloc({})", + size.size_wasm32(), + ); + // If the interface is an import, we need to track this for cleanup + // Otherwise, the caller is responsible for cleaning up in post_return if self.interface_gen.direction == Direction::Import { - self.use_ffi(ffi::MALLOC); - let address = self.locals.tmp("return_area"); - uwriteln!( - self.src, - "let {address} = mbt_ffi_malloc({})", - size.size_wasm32(), - ); self.cleanup.push(Cleanup { address: address.clone(), }); - address - } else { - self.interface_gen.world_gen.return_area_size = - self.interface_gen.world_gen.return_area_size.max(size); - self.interface_gen.world_gen.return_area_align = - self.interface_gen.world_gen.return_area_align.max(align); - "return_area".into() } + address } fn push_block(&mut self) { diff --git a/tests/runtime/resources/resources.mbt b/tests/runtime/resources/resources.mbt index 001535071..247fd8690 100644 --- a/tests/runtime/resources/resources.mbt +++ b/tests/runtime/resources/resources.mbt @@ -1,6 +1,6 @@ //@ [lang] //@ path = 'gen/interface/exports/stub.mbt' -//@ pkg_config = """{ "import": ["test/resources/interface/imports"] }""" +//@ pkg_config = """{ "warn-list": "-44", "import": ["test/resources/interface/imports"] }""" ///| let x : Map[Int, Int] = {} From c2ba35217d68d9ef48d93d90ebc3431b7c87533d Mon Sep 17 00:00:00 2001 From: zihang Date: Thu, 25 Dec 2025 13:50:20 +0800 Subject: [PATCH 14/61] feat: hide generated functions from interface --- crates/moonbit/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 67c754fe6..4f9a42997 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -655,7 +655,9 @@ impl InterfaceGenerator<'_> { uwriteln!( self.ffi, - r#"fn {ffi_import_name}({params}) {result_type} = "{import_module}" "{import_name}""# + r#" + fn {ffi_import_name}({params}) {result_type} = "{import_module}" "{import_name}" + "# ); } @@ -803,6 +805,7 @@ impl InterfaceGenerator<'_> { uwrite!( self.ffi, r#" + #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ {src} }} @@ -823,6 +826,7 @@ impl InterfaceGenerator<'_> { let export = format!( r#" + #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ {}{func_name}({}) }} @@ -966,6 +970,7 @@ impl InterfaceGenerator<'_> { uwrite!( self.ffi, r#" + #doc(hidden) pub fn {func_name}({params}) -> Unit {{ {src} }} @@ -981,6 +986,7 @@ impl InterfaceGenerator<'_> { ); let export = format!( r#" + #doc(hidden) pub fn {func_name}({params}) -> Unit {{ {}{func_name}({}) }} @@ -1184,6 +1190,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { uwrite!( self.ffi, r#" + #doc(hidden) pub fn {func_name}(handle : Int) -> Unit {{ {name}::dtor(handle) }} @@ -1200,6 +1207,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { let export = format!( r#" + #doc(hidden) pub fn {func_name}(handle : Int) -> Unit {{ {}{func_name}(handle) }} From f150ef834c97377bd7999a0faca845c97b200157 Mon Sep 17 00:00:00 2001 From: zihang Date: Mon, 22 Dec 2025 13:38:45 +0800 Subject: [PATCH 15/61] feat: add new async pkg --- crates/moonbit/src/async/async_abi.mbt | 266 +++++++++++++++++++ crates/moonbit/src/async/async_primitive.mbt | 21 ++ crates/moonbit/src/async/coroutine.mbt | 180 +++++++++++++ crates/moonbit/src/async/ev.mbt | 264 ++++++++++++++++++ crates/moonbit/src/async/moon.pkg.json | 1 + crates/moonbit/src/async/scheduler.mbt | 61 +++++ crates/moonbit/src/async/task.mbt | 50 ++++ crates/moonbit/src/async/task_group.mbt | 249 +++++++++++++++++ crates/moonbit/src/async/trait.mbt | 50 ++++ crates/moonbit/src/async_support.rs | 58 ++++ 10 files changed, 1200 insertions(+) create mode 100644 crates/moonbit/src/async/async_abi.mbt create mode 100644 crates/moonbit/src/async/async_primitive.mbt create mode 100644 crates/moonbit/src/async/coroutine.mbt create mode 100644 crates/moonbit/src/async/ev.mbt create mode 100644 crates/moonbit/src/async/moon.pkg.json create mode 100644 crates/moonbit/src/async/scheduler.mbt create mode 100644 crates/moonbit/src/async/task.mbt create mode 100644 crates/moonbit/src/async/task_group.mbt create mode 100644 crates/moonbit/src/async/trait.mbt diff --git a/crates/moonbit/src/async/async_abi.mbt b/crates/moonbit/src/async/async_abi.mbt new file mode 100644 index 000000000..f4491a0a5 --- /dev/null +++ b/crates/moonbit/src/async/async_abi.mbt @@ -0,0 +1,266 @@ +// #region subtask + +///| +priv struct SubTask { + handle : Int + state : SubTaskState +} + +///| +priv enum SubTaskState { + Starting = 0 + Started = 1 + Returned = 2 + Cancelled_before_started = 3 + Cancelled_before_returned = 4 +} + +///| +fn SubTaskState::from(int : Int) -> SubTaskState { + match int { + 0 => Starting + 1 => Started + 2 => Returned + 3 => Cancelled_before_started + 4 => Cancelled_before_returned + _ => panic() + } +} + +///| +fn SubTask::from(code : Int) -> SubTask { + { handle: code >> 4, state: SubTaskState::from(code & 0xf) } +} + +///| +/// None : the subtask is blocked +fn SubTask::cancel(self : SubTask) -> SubTaskState? { + let result = subtask_cancel(self.handle) + if result == -1 { + None + } else { + Some(SubTaskState::from(subtask_cancel(self.handle))) + } +} + +// #endregion + +// #region events + +///| +priv enum EventCode { + None = 0 + SubTask = 1 + StreamRead = 2 + StreamWrite = 3 + FutureRead = 4 + FutureWrite = 5 + TaskCancelled = 6 +} + +///| +fn EventCode::from(int : Int) -> EventCode { + match int { + 0 => EventCode::None + 1 => EventCode::SubTask + 2 => EventCode::StreamRead + 3 => EventCode::StreamWrite + 4 => EventCode::FutureRead + 5 => EventCode::FutureWrite + 6 => EventCode::TaskCancelled + _ => panic() + } +} + +///| +priv enum Events { + None + Subtask(Int, SubTaskState) + StreamRead(Int, StreamResult) + StreamWrite(Int, StreamResult) + FutureRead(Int, FutureReadResult) + FutureWrite(Int, FutureWriteResult) + TaskCancelled +} + +///| +fn Events::new(code : EventCode, i : Int, j : Int) -> Events { + match code { + None => None + SubTask => Subtask(i, SubTaskState::from(j)) + StreamRead => StreamRead(i, StreamResult::from(j)) + StreamWrite => StreamWrite(i, StreamResult::from(j)) + FutureRead => FutureRead(i, FutureReadResult::from(j)) + FutureWrite => FutureWrite(i, FutureWriteResult::from(j)) + TaskCancelled => TaskCancelled + } +} + +// #endregion + +// #region waitable set + +///| +struct WaitableSet(Int) derive(Eq, Show, Hash) + +///| +fn WaitableSet::new() -> WaitableSet { + WaitableSet(waitable_set_new()) +} + +///| +fn WaitableSet::drop(self : Self) -> Unit { + waitable_set_drop(self.0) +} + +// #endregion + +// #region Future + +///| +priv enum FutureReadResult { + Completed = 0 + Cancelled = 1 +} + +///| +fn FutureReadResult::from(int : Int) -> FutureReadResult { + match int { + 0 => Completed + 1 => Cancelled + _ => panic() + } +} + +///| +priv enum FutureWriteResult { + Completed = 0 + Dropped = 1 + Cancelled = 2 +} + +///| +fn FutureWriteResult::from(int : Int) -> FutureWriteResult { + match int { + 0 => Completed + 1 => Dropped + 2 => Cancelled + _ => panic() + } +} + +// #endregion + +// #region Stream + +///| +priv struct StreamResult { + progress : Int + copy_result : CopyResult +} + +///| +fn StreamResult::from(int : Int) -> StreamResult { + let progress = int >> 4 + let copy_result = CopyResult::from(int & 0xf) + { progress, copy_result } +} + +///| +priv enum CopyResult { + Completed = 0 + Dropped = 1 + Cancelled = 2 +} + +///| +fn CopyResult::from(int : Int) -> CopyResult { + match int { + 0 => Completed + 1 => Dropped + 2 => Cancelled + _ => panic() + } +} + +// #endregion + +// #region callback code + +///| +/// Code to let the runtime know what to do in the callback +priv enum CallbackCode { + Completed + Yield + Wait(WaitableSet) + Poll(WaitableSet) +} + +///| +fn CallbackCode::encode(self : Self) -> Int { + match self { + Completed => 0 + Yield => 1 + Wait(id) => 2 | (id.0 << 4) + Poll(id) => 3 | (id.0 << 4) + } +} + +///| +fn CallbackCode::decode(int : Int) -> CallbackCode { + let id = int >> 4 + match int & 0xf { + 0 => Completed + 1 => Yield + 2 => Wait(id) + 3 => Poll(id) + _ => panic() + } +} + +// #endregion + +// #region Component async primitives + +///| +/// Return whether is cancelled. +/// Use for non-callback implementation. +fn _yield() -> Bool = "$root" "[cancellable][yield]" + +///| +fn _backpressure_inc() = "$root" "[backpressure-inc]" + +///| +fn _backpressure_dec() = "$root" "[backpressure-dec]" + +///| +fn subtask_cancel(id : Int) -> Int = "$root" "[subtask-cancel]" + +///| +fn subtask_drop(id : Int) = "$root" "[subtask-drop]" + +///| +pub fn context_set(task : Int) = "$root" "[context-set-0]" + +///| +pub fn context_get() -> Int = "$root" "[context-get-0]" + +///| +fn tls_set(tls : Int) = "$root" "[context-set-0]" + +///| +fn tls_get() -> Int = "$root" "[context-get-0]" + +///| +pub fn task_cancel() = "[export]$root" "[task-cancel]" + +///| +fn waitable_set_new() -> Int = "$root" "[waitable-set-new]" + +///| +fn waitable_set_drop(set : Int) = "$root" "[waitable-set-drop]" + +///| +fn waitable_join(waitable : Int, set : Int) = "$root" "[waitable-join]" + +// #endregion diff --git a/crates/moonbit/src/async/async_primitive.mbt b/crates/moonbit/src/async/async_primitive.mbt new file mode 100644 index 000000000..b603b1cc9 --- /dev/null +++ b/crates/moonbit/src/async/async_primitive.mbt @@ -0,0 +1,21 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +async fn[T] async_suspend( + cb : ((T) -> Unit, (Cancelled) -> Unit) -> Unit, +) -> T raise Cancelled = "%async.suspend" + +///| +fn run_async(f : async () -> Unit noraise) = "%async.run" diff --git a/crates/moonbit/src/async/coroutine.mbt b/crates/moonbit/src/async/coroutine.mbt new file mode 100644 index 000000000..cfd962695 --- /dev/null +++ b/crates/moonbit/src/async/coroutine.mbt @@ -0,0 +1,180 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv enum State { + Done + Fail(Error) + Running + Suspend(ok_cont~ : (Unit) -> Unit, err_cont~ : (Cancelled) -> Unit) +} + +///| +struct Coroutine { + coro_id : Int + mut state : State + mut shielded : Bool + mut cancelled : Bool + mut ready : Bool + downstream : Set[Coroutine] +} + +///| +impl Eq for Coroutine with equal(c1, c2) { + c1.coro_id == c2.coro_id +} + +///| +impl Hash for Coroutine with hash_combine(self, hasher) { + self.coro_id.hash_combine(hasher) +} + +///| +fn Coroutine::wake(self : Coroutine) -> Unit { + self.ready = true + scheduler.run_later.push_back(self) +} + +///| +pub fn is_being_cancelled() -> Bool { + let coro = current_coroutine() + coro.cancelled && not(coro.shielded) +} + +///| +pub(all) suberror Cancelled derive(Show) + +///| +fn Coroutine::cancel(self : Coroutine) -> Unit { + self.cancelled = true + if not(self.shielded || self.ready) { + self.wake() + } +} + +///| +pub async fn pause() -> Unit raise Cancelled { + guard scheduler.curr_coro is Some(coro) + if coro.cancelled && not(coro.shielded) { + raise Cancelled::Cancelled + } + async_suspend(fn(ok_cont, err_cont) { + guard coro.state is Running + coro.state = Suspend(ok_cont~, err_cont~) + coro.ready = true + scheduler.run_later.push_back(coro) + }) +} + +///| +pub async fn suspend() -> Unit raise Cancelled { + guard scheduler.curr_coro is Some(coro) + if coro.cancelled && not(coro.shielded) { + raise Cancelled::Cancelled + } + scheduler.blocking += 1 + defer { + scheduler.blocking -= 1 + } + async_suspend(fn(ok_cont, err_cont) { + guard coro.state is Running + coro.state = Suspend(ok_cont~, err_cont~) + }) +} + +///| +fn spawn(f : async () -> Unit) -> Coroutine { + scheduler.coro_id += 1 + let coro = { + state: Running, + ready: true, + shielded: true, + downstream: Set::new(), + coro_id: scheduler.coro_id, + cancelled: false, + } + fn run(_) { + run_async(fn() { + coro.shielded = false + try f() catch { + err => coro.state = Fail(err) + } noraise { + _ => coro.state = Done + } + for coro in coro.downstream { + coro.wake() + } + coro.downstream.clear() + }) + } + + coro.state = Suspend(ok_cont=run, err_cont=_ => ()) + scheduler.run_later.push_back(coro) + coro +} + +///| +fn Coroutine::unwrap(self : Coroutine) -> Unit raise { + match self.state { + Done => () + Fail(err) => raise err + Running | Suspend(_) => panic() + } +} + +///| +async fn Coroutine::wait(target : Coroutine) -> Unit { + guard scheduler.curr_coro is Some(coro) + guard not(physical_equal(coro, target)) + match target.state { + Done => return + Fail(err) => raise err + Running | Suspend(_) => () + } + target.downstream.add(coro) + try suspend() catch { + err => { + target.downstream.remove(coro) + raise err + } + } noraise { + _ => target.unwrap() + } +} + +///| +fn Coroutine::check_error(coro : Coroutine) -> Unit raise { + match coro.state { + Fail(err) => raise err + Done | Running | Suspend(_) => () + } +} + +///| +pub async fn protect_from_cancel(f : async () -> Unit) -> Unit { + guard scheduler.curr_coro is Some(coro) + if coro.shielded { + // already in a shield, do nothing + f() + } else { + coro.shielded = true + defer { + coro.shielded = false + } + f() + if coro.cancelled { + raise Cancelled::Cancelled + } + } +} diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt new file mode 100644 index 000000000..23c642cdd --- /dev/null +++ b/crates/moonbit/src/async/ev.mbt @@ -0,0 +1,264 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv struct EventLoop { + subscribes : Map[Int, Subscriber] + tasks : Map[WaitableSet, Coroutine] + finished : Map[WaitableSet, Bool] +} + +///| +priv struct Subscriber { + mut event : Events? + coro : Coroutine +} + +///| +fn current_waitableset() -> WaitableSet { + WaitableSet(tls_get()) +} + +///| +pub fn with_waitableset(f : async () -> Unit) -> Int { + let waitable_set = WaitableSet::new() + tls_set(waitable_set.0) + let coro = spawn(async fn() -> Unit { + defer ev.finished.set(waitable_set, true) + f() + }) + ev.tasks.set(waitable_set, coro) + ev.finished.set(waitable_set, false) + reschedule() + if ev.finished.get(waitable_set) is Some(true) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + return CallbackCode::Completed.encode() + } else { + return CallbackCode::Wait(waitable_set.0).encode() + } +} + +///| +pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { + let waitable_set = current_waitableset() + let events = Events::new(EventCode::from(event), waitable_id, code) + match events { + TaskCancelled => { + guard ev.tasks.get(waitable_set) is Some(coro) + coro.cancel() + reschedule() + if ev.finished.get(waitable_set) is Some(true) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + task_cancel() + return CallbackCode::Completed.encode() + } else { + // Unlikely to reach here + return CallbackCode::Wait(waitable_set.0).encode() + } + } + _ => { + let sub = ev.subscribes.get(waitable_id) + guard sub is Some(subscriber) + subscriber.event = Some(events) + subscriber.coro.wake() + reschedule() + if ev.finished.get(waitable_set) is Some(true) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + return CallbackCode::Completed.encode() + } else { + return CallbackCode::Wait(waitable_set.0).encode() + } + } + } +} + +///| +let ev : EventLoop = { subscribes: {}, tasks: {}, finished: {} } + +///| +pub async fn suspend_for_subtask( + val : Int, + cleanup_after_started : () -> Unit, +) -> Unit { + let task = SubTask::from(val) + defer subtask_drop(task.handle) + let mut cleaned = false + + // Helper: ensure cleanup is called once we've moved past Starting state + fn ensure_cleanup(state : SubTaskState) -> Unit { + if not(cleaned) && !(state is Starting) { + cleanup_after_started() + cleaned = true + } + } + + // Initial state, return if finished + ensure_cleanup(task.state) + match task.state { + Returned => return + Cancelled_before_started => raise SubTaskCancelled(before_started=true) + Cancelled_before_returned => raise SubTaskCancelled(before_started=false) + _ => () + } + + // Create subscriber to wait for events + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(task.handle, subscriber) + defer ev.subscribes.remove(task.handle) + waitable_join(task.handle, current_waitableset().0) + defer waitable_join(task.handle, 0) + for { + suspend() catch { + Cancelled::Cancelled => + // Cancel the subtask + return protect_from_cancel(() => { + subscriber.event = task + .cancel() + .map(state => Subtask(task.handle, state)) + while subscriber.event is None { + suspend() + } + guard subscriber.event is Some(Subtask(i, state)) && i == task.handle + ensure_cleanup(state) + match state { + Returned => return + Cancelled_before_started => + raise SubTaskCancelled(before_started=true) + Cancelled_before_returned => + raise SubTaskCancelled(before_started=false) + _ => panic() // should not happen + } + }) + } + + // Subsequent state, return if finished + if subscriber.event is Some(Subtask(i, state)) { + guard i == task.handle + ensure_cleanup(state) + match state { + Returned => return + Cancelled_before_started => raise SubTaskCancelled(before_started=true) + Cancelled_before_returned => + raise SubTaskCancelled(before_started=false) + _ => subscriber.event = None + } + } + } +} + +///| +pub async fn suspend_for_future_read(idx : Int, val : Int) -> Unit { + let result = if val == -1 { + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(FutureRead(i, result)) && i == idx + result + } else { + FutureReadResult::from(val) + } + match result { + Completed => return + Cancelled => raise FutureReadCancelled + } +} + +///| +pub async fn suspend_for_future_write(idx : Int) -> Bool { + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(FutureWrite(i, result)) && i == idx + match result { + Completed => true + Dropped => false + Cancelled => raise FutureWriteCancelled + } +} + +///| +pub async fn suspend_for_stream_read(idx : Int, val : Int) -> (Int, Bool) { + let { progress, copy_result } = if val == -1 { + // Blocked, wait for event + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(StreamRead(i, result)) && i == idx + result + } else { + StreamResult::from(val) + } + match copy_result { + Completed => return (progress, false) + Dropped => return (progress, true) + Cancelled => + if progress > 0 { + return (progress, false) + } else { + raise StreamReadCancelled + } + } +} + +///| +pub async fn suspend_for_stream_write(idx : Int, val : Int) -> (Int, Bool) { + let { progress, copy_result } = if val != -1 { + // Not blocked + StreamResult::from(val) + } else { + // Blocked, wait for event + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(StreamWrite(i, result)) && i == idx + result + } + match copy_result { + Completed => return (progress, false) + Dropped => return (progress, true) + Cancelled => + if progress > 0 { + return (progress, false) + } else { + raise StreamWriteCancelled + } + } +} + +///| +pub suberror OpCancelled { + SubTaskCancelled(before_started~ : Bool) + StreamWriteCancelled + StreamReadCancelled + FutureWriteCancelled + FutureReadCancelled +} diff --git a/crates/moonbit/src/async/moon.pkg.json b/crates/moonbit/src/async/moon.pkg.json new file mode 100644 index 000000000..b7a5c45c3 --- /dev/null +++ b/crates/moonbit/src/async/moon.pkg.json @@ -0,0 +1 @@ +{ "warn-list": "-44", "supported-targets": ["wasm"] } \ No newline at end of file diff --git a/crates/moonbit/src/async/scheduler.mbt b/crates/moonbit/src/async/scheduler.mbt new file mode 100644 index 000000000..c99947b54 --- /dev/null +++ b/crates/moonbit/src/async/scheduler.mbt @@ -0,0 +1,61 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv struct Scheduler { + mut coro_id : Int + mut curr_coro : Coroutine? + mut blocking : Int + run_later : @deque.Deque[Coroutine] +} + +///| +let scheduler : Scheduler = { + coro_id: 0, + curr_coro: None, + blocking: 0, + run_later: @deque.new(), +} + +///| +pub fn current_coroutine() -> Coroutine { + scheduler.curr_coro.unwrap() +} + +///| +pub fn has_immediately_ready_task() -> Bool { + !scheduler.run_later.is_empty() +} + +///| +pub fn no_more_work() -> Bool { + scheduler.blocking == 0 && scheduler.run_later.is_empty() +} + +///| +pub fn reschedule() -> Unit { + while scheduler.run_later.pop_front() is Some(coro) { + coro.ready = false + guard coro.state is Suspend(ok_cont~, err_cont~) else { } + coro.state = Running + let last_coro = scheduler.curr_coro + scheduler.curr_coro = Some(coro) + if coro.cancelled && not(coro.shielded) { + err_cont(Cancelled::Cancelled) + } else { + ok_cont(()) + } + scheduler.curr_coro = last_coro + } +} diff --git a/crates/moonbit/src/async/task.mbt b/crates/moonbit/src/async/task.mbt new file mode 100644 index 000000000..676936293 --- /dev/null +++ b/crates/moonbit/src/async/task.mbt @@ -0,0 +1,50 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +/// `Task[X]` represents a running task with result type `X`, +/// it can be used to wait and retrieve the result value of the task. +struct Task_[X] { + value : Ref[X?] + coro : Coroutine +} + +///| +/// Wait for a task and retrieve its result value. +/// If the task fails, `wait` will also fail with the same error. +/// +/// If the current task is cancelled, `wait` return immediately with error. +pub async fn[X] Task_::wait(self : Task_[X]) -> X { + self.coro.wait() + self.value.val.unwrap() +} + +///| +/// Try to obtain the result of the task. +/// If the task already terminated, its result value will be returned. +/// If the task already failed, `try_wait` will fail immediately. +/// If the task is still running, `try_wait` returns `None`. +/// `try_wait` is a synchoronous function: it never blocks. +pub fn[X] Task_::try_wait(self : Task_[X]) -> X? raise { + self.coro.check_error() + self.value.val +} + +///| +/// Cancel a task. Subsequent attempt to wait for the task will receive error. +/// Note that if the task is *not* spawned with `allow_failure=true`, +/// the whole task group will fail too. +pub fn[X] Task_::cancel(self : Task_[X]) -> Unit { + self.coro.cancel() +} diff --git a/crates/moonbit/src/async/task_group.mbt b/crates/moonbit/src/async/task_group.mbt new file mode 100644 index 000000000..03384b941 --- /dev/null +++ b/crates/moonbit/src/async/task_group.mbt @@ -0,0 +1,249 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv enum TaskGroupState { + Done + Fail(Error) + Running +} + +///| +/// A `TaskGroup` can be used to spawn children tasks that run in parallel. +/// Task groups implements *structured concurrency*: +/// a task group will only return after all its children task terminates. +/// +/// Task groups also handles *error propagation*: +/// by default, if any child task raises error, +/// the whole task group will also raise that error, +/// and all other remaining child tasks will be cancelled. +/// +/// The type parameter `X` in `TaskGroup[X]` is the result type of the group, +/// see `with_task_group` for more detail. +struct TaskGroup[X] { + children : Set[Coroutine] + parent : Coroutine + mut waiting : Int + mut state : TaskGroupState + mut result : X? + group_defer : Array[async () -> Unit] +} + +///| +#deprecated("this error is no longer emitted") +pub suberror AlreadyTerminated derive(Show) + +///| +fn[X] TaskGroup::spawn_coroutine( + self : TaskGroup[X], + f : async () -> Unit, + no_wait~ : Bool, + allow_failure~ : Bool, +) -> Coroutine { + guard self.state is Running else { + abort("trying to spawn from a terminated task group") + } + if not(no_wait) { + self.waiting += 1 + } + async fn worker() { + let coro = current_coroutine() + defer { + self.children.remove(coro) + if not(no_wait) { + self.waiting -= 1 + if self.waiting == 0 && self.state is Running { + for child in self.children { + child.cancel() + } + self.state = Done + } + } + if self.children.is_empty() { + self.parent.wake() + } + } + guard self.state is Running else { } + f() catch { + err if allow_failure => raise err + err => { + if self.state is Running { + for child in self.children { + child.cancel() + } + self.state = Fail(err) + } else if not(err is Cancelled::Cancelled) { + self.state = Fail(err) + } + raise err + } + } + } + + let coro = spawn(worker) + self.children.add(coro) + coro +} + +///| +/// Spawn a child task in a task group, and run it asynchronously in the background. +/// +/// Unless `no_wait` (`false` by default) is `true`, +/// the whole task group will only exit after this child task terminates. +/// +/// Unless `allow_failure` (`false` by default) is `true`, +/// Ithe whole task group will also fail if the spawned task fails, +/// other tasks in the group will be cancelled in this case. +/// +/// If the task group is already cancelled or has been terminated, +/// `spawn_bg` will fail with error and the child task will not be spawned. +/// +/// It is undefined whether the child task will start running immediately +/// before `spawn_bg` returns. +pub fn[X] TaskGroup::spawn_bg( + self : TaskGroup[X], + f : async () -> Unit, + no_wait? : Bool = false, + allow_failure? : Bool = false, +) -> Unit { + ignore(self.spawn_coroutine(f, no_wait~, allow_failure~)) +} + +///| +/// Spawn a child task in a task group, compute a result asynchronously. +/// A task handle will be returned, the result value of the task can be waited +/// and retrieved using `.wait()`, or cancelled using `.cancel()`. +/// +/// Unless `no_wait` (`false` by default) is `true`, +/// the whole task group will only exit after this child task terminates. +/// +/// Unless `allow_failure` (`false` by default) is `true`, +/// Ithe whole task group will also fail if the spawned task fails, +/// other tasks in the group will be cancelled in this case. +/// +/// If the task group is already cancelled or has been terminated, +/// `spawn` will fail with error and the child task will not be spawned. +/// +/// It is undefined whether the child task will start running immediately +/// before `spawn` returns. +pub fn[G, X] TaskGroup::spawn( + self : TaskGroup[G], + f : async () -> X, + no_wait? : Bool = false, + allow_failure? : Bool = false, +) -> Task_[X] { + let value = @ref.new(Option::None) + let coro = self.spawn_coroutine( + () => value.val = Some(f()), + no_wait~, + allow_failure~, + ) + { value, coro } +} + +///| +/// Attach a defer block, represented as a cleanup function, to a task group. +/// The clenaup function will be invoked when the group terminates. +/// Group scoped defer blocks are executed in FILO order, just like normal `defer`. +/// `with_task_group` will only exit after all group defer blocks terminate. +/// +/// Note that if the whole task group is cancelled, +/// async operations in group defer block will be cancelled immediately too. +/// Users can use `protect_from_cancel` to prevent async tasks from being cancelled. +/// It is highly recommended to add a hard timeout to async defer block in this case, +/// to avoid infinite hanging due to blocked operation. +pub fn[X] TaskGroup::add_defer( + self : TaskGroup[X], + block : async () -> Unit, +) -> Unit { + guard self.state is Running else { + abort("trying to attach defer to a terminated task group") + } + self.group_defer.push(block) +} + +///| +/// `with_task_group(f)` creates a new task group and run `f` with the new group. +/// `f` itself will be run in a child task of the new group. +/// `with_task_group` exits after all the whole group terminates, +/// which means all child tasks in the group have terminated, including `f`. +/// +/// If all children task terminate successfully, +/// `with_task_group` will return the result of `f`. +pub async fn[X] with_task_group(f : async (TaskGroup[X]) -> X) -> X { + let tg = { + children: Set::new(), + parent: current_coroutine(), + waiting: 0, + state: Running, + result: None, + group_defer: [], + } + tg.spawn_bg(fn() { + let value = f(tg) + if tg.result is None { + tg.result = Some(value) + } + }) + if not(tg.children.is_empty()) { + suspend() catch { + err => + if tg.state is Running { + tg.state = Fail(err) + for child in tg.children { + child.cancel() + } + } + } + } + if not(tg.children.is_empty()) { + protect_from_cancel(() => suspend()) catch { + _ => () + } + } + tg.children.clear() + while tg.group_defer.pop() is Some(defer_block) { + defer_block() catch { + err => if tg.state is Done { tg.state = Fail(err) } + } + } + match tg.state { + Done => tg.result.unwrap() + Fail(err) => raise err + Running => panic() + } +} + +///| +/// Force a task group to terminate immediately with the given result value. +/// All child tasks in the group, including potentially the current one, +/// will be cancelled. +pub fn[X] TaskGroup::return_immediately( + self : TaskGroup[X], + value : X, +) -> Unit raise { + if self.result is None { + self.result = Some(value) + } + if self.state is Running { + self.state = Done + let curr_coro = current_coroutine() + for child in self.children { + if child != curr_coro { + child.cancel() + } + } + } + raise Cancelled::Cancelled +} diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt new file mode 100644 index 000000000..ad2c20b76 --- /dev/null +++ b/crates/moonbit/src/async/trait.mbt @@ -0,0 +1,50 @@ +///| +pub(all) struct FutureR[X](async () -> X) + +///| +pub(all) struct StreamR[X] { + read : async (Int) -> ArrayView[X]? + close : async () -> Unit +} + +///| +pub(all) struct StreamW[X] { + write : async (ArrayView[X]) -> Int + close : async () -> Unit +} + +///| +struct OutStream[X] { + mut stream : StreamW[X]? + mut coroutine : Coroutine? +} derive(Default) + +///| +pub async fn[X] OutStream::get_stream(self : OutStream[X]) -> StreamW[X] { + if self.stream is Some(s) { + return s + } else { + guard self.coroutine is None + self.coroutine = Some(current_coroutine()) + suspend() catch { + e => { + if self.stream is Some(s) { + (s.close)() + } + raise e + } + } + self.stream.unwrap() + } +} + +///| +pub fn[X] OutStream::put_stream( + self : OutStream[X], + stream : StreamW[X], +) -> Unit { + self.stream = Some(stream) + if self.coroutine is Some(coro) { + coro.wake() + } +} diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index d7a71e3a5..ed300ccae 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -24,6 +24,17 @@ const ASYNC_WASM_PRIMITIVE: &str = include_str!("./ffi/wasm_primitive.mbt"); const ASYNC_WAITABLE_SET: &str = include_str!("./ffi/waitable_task.mbt"); const ASYNC_SUBTASK: &str = include_str!("./ffi/subtask.mbt"); +// NEW Async Impl +const ASYNC_ABI: &str = include_str!("./async/async_abi.mbt"); +const ASYNC_CORO: &str = include_str!("./async/coroutine.mbt"); +const ASYNC_EV: &str = include_str!("./async/ev.mbt"); +const ASYNC_SCHEDULER: &str = include_str!("./async/scheduler.mbt"); +const ASYNC_TASK: &str = include_str!("./async/task.mbt"); +const ASYNC_TASK_GROUP: &str = include_str!("./async/task_group.mbt"); +const ASYNC_TRAIT: &str = include_str!("./async/trait.mbt"); +const ASYNC_PKG_JSON: &str = include_str!("./async/moon.pkg.json"); +const ASYNC_PRIM: &str = include_str!("./async/async_primitive.mbt"); + struct Segment<'a> { name: &'a str, src: &'a str, @@ -52,6 +63,43 @@ const ASYNC_UTILS: [&Segment; 5] = [ }, ]; +const ASYNC_IMPL: [&Segment; 8] = [ + &Segment { + name: "async_abi", + src: ASYNC_ABI, + }, + &Segment { + name: "async_coro", + src: ASYNC_CORO, + }, + &Segment { + name: "async_ev", + src: ASYNC_EV, + }, + &Segment { + name: "async_scheduler", + src: ASYNC_SCHEDULER, + }, + &Segment { + name: "async_task", + src: ASYNC_TASK, + }, + &Segment { + name: "async_task_group", + src: ASYNC_TASK_GROUP, + }, + &Segment { + name: "async_trait", + src: ASYNC_TRAIT, + }, + &Segment { + name: "async_primitive", + src: ASYNC_PRIM, + }, +]; + +pub(crate) const ASYNC_DIR: &str = "async"; + #[derive(Default)] pub(crate) struct AsyncSupport { is_async: bool, @@ -85,6 +133,16 @@ impl AsyncSupport { indent(s.src).as_bytes(), ); }); + ASYNC_IMPL.iter().for_each(|s| { + files.push( + &format!("{ASYNC_DIR}/{}.mbt", s.name), + indent(s.src).as_bytes(), + ); + }); + files.push( + &format!("{ASYNC_DIR}/moon.pkg.json"), + indent(ASYNC_PKG_JSON).as_bytes(), + ); files.push( &format!("{FFI_DIR}/moon.pkg.json"), "{ \"warn-list\": \"-44\", \"supported-targets\": [\"wasm\"] }".as_bytes(), From d80e3902927276068e927501546bfb314d0cae6e Mon Sep 17 00:00:00 2001 From: zihang Date: Tue, 23 Dec 2025 18:49:40 +0800 Subject: [PATCH 16/61] feat(wip): simple-call-import --- crates/moonbit/src/lib.rs | 302 ++++++------------ .../async/simple-call-import/test.mbt | 7 + 2 files changed, 100 insertions(+), 209 deletions(-) create mode 100644 tests/runtime-async/async/simple-call-import/test.mbt diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 4f9a42997..41ee7e5f7 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -14,13 +14,14 @@ use wit_bindgen_core::{ uwrite, uwriteln, wit_parser::{ Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Int, InterfaceId, - LiftLowerAbi, ManglingAndAbi, Param, Record, Resolve, ResourceIntrinsic, Result_, + LiftLowerAbi, Mangling, ManglingAndAbi, Param, Record, Resolve, ResourceIntrinsic, + Result_, SizeAlign, Tuple, Type, TypeId, Variant, WasmExport, WasmExportKind, WasmImport, WorldId, WorldKey, }, }; -use crate::async_support::AsyncSupport; +use crate::async_support::{ASYNC_DIR, AsyncSupport}; use crate::pkg::{Imports, MoonbitSignature, PkgResolver, ToMoonBitIdent, ToMoonBitTypeIdent}; mod async_support; @@ -662,53 +663,49 @@ impl InterfaceGenerator<'_> { } // Generate the MoonBit wrapper - if async_ { - // self.r#generation_futures_and_streams_import("", func, &import_module); - // if async_ { - // src = self.r#generate_async_import_function(func, mbt_sig, &wasm_sig); - // } - todo!("async import not supported yet"); - } else { - let mut bindgen = FunctionBindgen::new( - self, - func.params - .iter() - .map(|Param { name, .. }| name.to_moonbit_ident()) - .collect(), - ); + let mut bindgen = FunctionBindgen::new( + self, + func.params + .iter() + .map(|Param { name, .. }| name.to_moonbit_ident()) + .collect(), + ); - abi::call( - bindgen.interface_gen.resolve, - AbiVariant::GuestImport, - LiftLower::LowerArgsLiftResults, - func, - &mut bindgen, - false, - ); + abi::call( + bindgen.interface_gen.resolve, + if async_ { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + LiftLower::LowerArgsLiftResults, + func, + &mut bindgen, + false, + ); - let src = bindgen.src.clone(); + let src = bindgen.src.clone(); - let cleanup_list = if bindgen.needs_cleanup_list { - "let cleanup_list : Array[Int] = []" - } else { - "" - }; + let cleanup_list = if bindgen.needs_cleanup_list { + "let cleanup_list : Array[Int] = []" + } else { + "" + }; - let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); - let sig = self.sig_string(&mbt_sig, async_); + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); + let sig = self.sig_string(&mbt_sig, async_); - print_docs(&mut self.src, &func.docs); + print_docs(&mut self.src, &func.docs); - uwrite!( - self.src, - r#" + uwrite!( + self.src, + r#" {sig} {{ {cleanup_list} {src} }} "# - ); - } + ); } fn export(&mut self, func: &Function) { @@ -764,9 +761,6 @@ impl InterfaceGenerator<'_> { // TODO: adapt async cleanup assert!(!bindgen.needs_cleanup_list); - // Async functions deferred task return - let deferred_task_return = bindgen.deferred_task_return.clone(); - let src = bindgen.src; let result_type = match &sig.results[..] { @@ -793,24 +787,33 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); - // Async functions return type - let interface_name = match self.interface { - Some(key) => Some(self.resolve.name_world_key(key)), - None => None, - }; - - let module_name = interface_name.as_deref().unwrap_or("$root"); - self.r#generation_futures_and_streams_import("[export]", func, module_name); - - uwrite!( - self.ffi, - r#" + if async_ { + let async_pkg = self + .world_gen + .pkg_resolver + .qualify_package(self.name, ASYNC_DIR); + uwrite!( + self.ffi, + r#" + #doc(hidden) + pub fn {func_name}({params}) -> {result_type} {{ + {async_pkg}with_waitableset(() => {async_pkg}with_task_group(task_group => task_group.spawn_bg(() => {{ + {src} + }}))) + }} + "#, + ); + } else { + uwrite!( + self.ffi, + r#" #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ {src} }} "#, - ); + ); + } let export_name = self.resolve.wasm_export_name( ManglingAndAbi::Legacy(if async_ { LiftLowerAbi::AsyncCallback @@ -846,18 +849,6 @@ impl InterfaceGenerator<'_> { // If async, we also need a callback function and a task_return intrinsic if async_ { - let export_func_name = self - .world_gen - .export_ns - .tmp(&format!("wasmExportAsync{camel_name}")); - let DeferredTaskReturn::Emitted { - body: task_return_body, - params: task_return_params, - return_param, - } = deferred_task_return - else { - unreachable!() - }; let export_name = self.resolve.wasm_export_name( ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback), WasmExport::Func { @@ -866,78 +857,61 @@ impl InterfaceGenerator<'_> { kind: WasmExportKind::Callback, }, ); + let export_dir = self.world_gen.opts.r#gen_dir.clone(); + + let (task_return_module, task_return_name, signature) = + func.task_return_import(self.resolve, self.interface, Mangling::Legacy); - let task_return_param_tys = task_return_params + let params = signature + .results .iter() .enumerate() - .map(|(idx, (ty, _expr))| format!("p{}: {}", idx, wasm_type(*ty))) - .collect::>() - .join(", "); - let task_return_param_exprs = task_return_params - .iter() - .map(|(_ty, expr)| expr.as_str()) + .map(|(i, param)| { + let ty = wasm_type(*param); + format!("p{i} : {ty}") + }) .collect::>() .join(", "); - let return_ty = match &func.result { - Some(result) => self - .world_gen - .pkg_resolver - .type_name(self.name, result) - .to_string(), - None => "Unit".into(), - }; - let return_expr = match return_ty.as_str() { - "Unit" => "".into(), - _ => format!("{return_param}: {return_ty}",), - }; - let snake_func_name = func.name.to_moonbit_ident().to_string(); - let ffi = self + + let async_pkg = self .world_gen .pkg_resolver - .qualify_package(self.name, FFI_DIR); - - let (task_return_module, task_return_name) = self.resolve.wasm_import_name( - ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback), - WasmImport::Func { - interface: self.interface, - func, - }, - ); - + .qualify_package(&export_dir, ASYNC_DIR); uwriteln!( - self.src, + self.ffi, r#" - fn {export_func_name}TaskReturn({task_return_param_tys}) = "{task_return_module}" "{task_return_name}" - - pub fn {snake_func_name}_task_return({return_expr}) -> Unit {{ - {task_return_body} - {export_func_name}TaskReturn({task_return_param_exprs}) - }} - "# + fn wasmExportTaskReturn{}({params}) = "{task_return_module}" "{task_return_name}" + "#, + func.name.to_upper_camel_case() ); + let func_name = self + .world_gen + .export_ns + .tmp(&format!("wasmExport{}CB", func.name.to_upper_camel_case())); + uwriteln!( self.ffi, r#" - pub fn {export_func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ - {ffi}callback(event_raw, waitable, code) + pub fn {func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ + {async_pkg}cb(event_raw, waitable, code) }} - "# + "#, ); let export = format!( r#" - pub fn {export_func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ - {}{snake_func_name}_callback(event_raw, waitable, code) + pub fn {func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ + {}{func_name}(event_raw, waitable, code) }} "#, self.world_gen .pkg_resolver - .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + .qualify_package(&self.world_gen.opts.gen_dir, self.name), ); self.world_gen .export - .insert(export_name, (export_func_name.clone(), export)); + .insert(export_name, (func_name.clone(), export)); } // If post return is needed, generate it @@ -1491,20 +1465,6 @@ struct BlockStorage { cleanup: Vec, } -#[derive(Clone, Debug)] -enum DeferredTaskReturn { - None, - Generating { - prev_src: String, - return_param: String, - }, - Emitted { - params: Vec<(WasmType, String)>, - body: String, - return_param: String, - }, -} - struct FunctionBindgen<'a, 'b> { interface_gen: &'b mut InterfaceGenerator<'a>, params: Box<[String]>, @@ -1515,7 +1475,6 @@ struct FunctionBindgen<'a, 'b> { payloads: Vec, cleanup: Vec, needs_cleanup_list: bool, - deferred_task_return: DeferredTaskReturn, } impl<'a, 'b> FunctionBindgen<'a, 'b> { @@ -1537,7 +1496,6 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { payloads: Vec::new(), cleanup: Vec::new(), needs_cleanup_list: false, - deferred_task_return: DeferredTaskReturn::None, } } @@ -2352,6 +2310,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::IterBasePointer => results.push("iter_base".into()), + Instruction::AsyncTaskReturn { name, .. } => { + let func_name = name.to_upper_camel_case(); + let operands = operands.join(", "); + uwriteln!(self.src, "wasmExport{func_name}({operands});"); + } + Instruction::CallWasm { sig, name } => { let assignment = match &sig.results[..] { [result] => { @@ -2374,7 +2338,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "{assignment} wasmImport{func_name}({operands});"); } - Instruction::CallInterface { func, async_ } => { + Instruction::CallInterface { func, .. } => { let name = self.interface_gen.world_gen.pkg_resolver.func_call( self.interface_gen.name, func, @@ -2383,63 +2347,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { let args = operands.join(", "); - if *async_ { - let (async_func_result, task_return_result, task_return_type) = - match func.result { - Some(ty) => { - let res = self.locals.tmp("return_result"); - (res.clone(), res, self.resolve_type_name(&ty)) - } - None => ("_ignore".into(), "".into(), "Unit".into()), - }; - - if func.result.is_some() { - results.push(async_func_result.clone()); - } - let ffi = self.resolve_pkg(FFI_DIR); - uwrite!( - self.src, - r#" - let task = {ffi}current_task(); - let _ = task.with_waitable_set(fn(task) {{ - let {async_func_result}: Ref[{task_return_type}?] = Ref::new(None) - task.wait(fn() {{ - {async_func_result}.val = Some({name}({args})); - }}) - for {{ - if task.no_wait() && {async_func_result}.val is Some({async_func_result}){{ - {name}_task_return({task_return_result}); - break; - }} else {{ - {ffi}suspend() catch {{ - _ => {{ - {ffi}task_cancel(); - }} - }} - }} - }} - }}) - if task.is_fail() is Some({ffi}Cancelled::Cancelled) {{ - {ffi}task_cancel(); - return {ffi}CallbackCode::Exit.encode() - }} - if task.is_done() {{ - return {ffi}CallbackCode::Exit.encode() - }} - return {ffi}CallbackCode::Wait(task.handle()).encode() - "#, - ); - assert!(matches!( - self.deferred_task_return, - DeferredTaskReturn::None - )); - self.deferred_task_return = DeferredTaskReturn::Generating { - prev_src: mem::take(&mut self.src), - return_param: async_func_result.to_string(), - }; - return; - } - let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { @@ -2757,29 +2664,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("{op}.handle")); } - Instruction::AsyncTaskReturn { params, .. } => { - let (body, return_param) = match &mut self.deferred_task_return { - DeferredTaskReturn::Generating { - prev_src, - return_param, - } => { - mem::swap(&mut self.src, prev_src); - (mem::take(prev_src), return_param.clone()) - } - _ => unreachable!(), - }; - assert_eq!(params.len(), operands.len()); - self.deferred_task_return = DeferredTaskReturn::Emitted { - body, - params: params - .iter() - .zip(operands) - .map(|(a, b)| (*a, b.clone())) - .collect(), - return_param, - }; - } - Instruction::StreamLower { .. } => { let op = &operands[0]; results.push(format!("{op}.handle")); diff --git a/tests/runtime-async/async/simple-call-import/test.mbt b/tests/runtime-async/async/simple-call-import/test.mbt new file mode 100644 index 000000000..6ba286794 --- /dev/null +++ b/tests/runtime-async/async/simple-call-import/test.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/i/stub.mbt' + +///| +pub async fn f() -> Unit { + () +} From 354b57ba67f1452437c862b34a982ee051a13058 Mon Sep 17 00:00:00 2001 From: zihang Date: Wed, 24 Dec 2025 14:46:46 +0800 Subject: [PATCH 17/61] wip: add runner --- tests/runtime-async/async/simple-call-import/runner.mbt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/runtime-async/async/simple-call-import/runner.mbt diff --git a/tests/runtime-async/async/simple-call-import/runner.mbt b/tests/runtime-async/async/simple-call-import/runner.mbt new file mode 100644 index 000000000..6ba286794 --- /dev/null +++ b/tests/runtime-async/async/simple-call-import/runner.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/i/stub.mbt' + +///| +pub async fn f() -> Unit { + () +} From e4aad8ce638458214de165568c35d311db89458d Mon Sep 17 00:00:00 2001 From: zihang Date: Thu, 25 Dec 2025 15:40:05 +0800 Subject: [PATCH 18/61] feat: hide generated functions --- crates/moonbit/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 41ee7e5f7..8c35d5a63 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -893,6 +893,7 @@ impl InterfaceGenerator<'_> { uwriteln!( self.ffi, r#" + #doc(hidden) pub fn {func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ {async_pkg}cb(event_raw, waitable, code) }} @@ -900,6 +901,7 @@ impl InterfaceGenerator<'_> { ); let export = format!( r#" + #doc(hidden) pub fn {func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ {}{func_name}(event_raw, waitable, code) }} From 6a77c85e5dfee6bbc761bbbc04d03fc8b62f6862 Mon Sep 17 00:00:00 2001 From: zihang Date: Thu, 25 Dec 2025 17:42:36 +0800 Subject: [PATCH 19/61] feat(wip): simple future --- crates/moonbit/src/async/ev.mbt | 8 +- crates/moonbit/src/async/trait.mbt | 15 +- crates/moonbit/src/async_support.rs | 686 ++++++------------ crates/moonbit/src/lib.rs | 64 +- crates/moonbit/src/pkg.rs | 10 +- .../async/simple-future/test.mbt | 17 +- 6 files changed, 273 insertions(+), 527 deletions(-) diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt index 23c642cdd..d187d4cf3 100644 --- a/crates/moonbit/src/async/ev.mbt +++ b/crates/moonbit/src/async/ev.mbt @@ -179,7 +179,7 @@ pub async fn suspend_for_future_read(idx : Int, val : Int) -> Unit { } match result { Completed => return - Cancelled => raise FutureReadCancelled + Cancelled => raise FutureReadError::Cancelled } } @@ -260,5 +260,9 @@ pub suberror OpCancelled { StreamWriteCancelled StreamReadCancelled FutureWriteCancelled - FutureReadCancelled } + +pub(all) suberror FutureReadError { + Cancelled + Dropped +} \ No newline at end of file diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index ad2c20b76..e7fc83a8a 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -1,5 +1,18 @@ ///| -pub(all) struct FutureR[X](async () -> X) +pub(all) struct FutureR[X] { + get : async () -> X + drop : async () -> Unit +} + +///| +pub async fn[X] FutureR::get(self : FutureR[X]) -> X { + (self.get)() +} + +///| +pub async fn[X] FutureR::drop(self : FutureR[X]) -> Unit { + (self.drop)() +} ///| pub(all) struct StreamR[X] { diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index ed300ccae..0db3b82be 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -6,23 +6,16 @@ use std::{ use heck::{ToSnakeCase, ToUpperCamelCase}; use wit_bindgen_core::{ Files, Source, - abi::{self, WasmSignature}, - uwriteln, - wit_parser::{Function, Param, Resolve, Type, TypeDefKind, TypeId}, -}; - -use crate::{ - FFI, FFI_DIR, indent, - pkg::{MoonbitSignature, ToMoonBitIdent}, + abi::{self, WasmSignature, deallocate_lists_and_own_in_types}, + dealias, uwriteln, + wit_parser::{ + Function, LiftLowerAbi, ManglingAndAbi, Resolve, Type, TypeDefKind, TypeId, WasmImport, + }, }; -use super::{FunctionBindgen, InterfaceGenerator, PayloadFor}; +use crate::indent; -const ASYNC_PRIMITIVE: &str = include_str!("./ffi/async_primitive.mbt"); -const ASYNC_FUTURE: &str = include_str!("./ffi/future.mbt"); -const ASYNC_WASM_PRIMITIVE: &str = include_str!("./ffi/wasm_primitive.mbt"); -const ASYNC_WAITABLE_SET: &str = include_str!("./ffi/waitable_task.mbt"); -const ASYNC_SUBTASK: &str = include_str!("./ffi/subtask.mbt"); +use super::InterfaceGenerator; // NEW Async Impl const ASYNC_ABI: &str = include_str!("./async/async_abi.mbt"); @@ -40,29 +33,6 @@ struct Segment<'a> { src: &'a str, } -const ASYNC_UTILS: [&Segment; 5] = [ - &Segment { - name: "async_primitive", - src: ASYNC_PRIMITIVE, - }, - &Segment { - name: "async_future", - src: ASYNC_FUTURE, - }, - &Segment { - name: "async_wasm_primitive", - src: ASYNC_WASM_PRIMITIVE, - }, - &Segment { - name: "async_waitable_set", - src: ASYNC_WAITABLE_SET, - }, - &Segment { - name: "async_subtask", - src: ASYNC_SUBTASK, - }, -]; - const ASYNC_IMPL: [&Segment; 8] = [ &Segment { name: "async_abi", @@ -111,28 +81,11 @@ impl AsyncSupport { self.is_async = true; } - pub(crate) fn register_future_or_stream(&mut self, module: &str, ty: TypeId) -> bool { - self.futures - .entry(module.to_string()) - .or_default() - .insert(ty) - } - - pub(crate) fn emit_utils(&self, files: &mut Files, version: &str) { + pub(crate) fn emit_utils(&self, files: &mut Files) { if !self.is_async && self.futures.is_empty() { return; } - let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, version); - body.push_str(FFI); - files.push(&format!("{FFI_DIR}/top.mbt"), indent(&body).as_bytes()); - ASYNC_UTILS.iter().for_each(|s| { - files.push( - &format!("{FFI_DIR}/{}.mbt", s.name), - indent(s.src).as_bytes(), - ); - }); ASYNC_IMPL.iter().for_each(|s| { files.push( &format!("{ASYNC_DIR}/{}.mbt", s.name), @@ -143,466 +96,261 @@ impl AsyncSupport { &format!("{ASYNC_DIR}/moon.pkg.json"), indent(ASYNC_PKG_JSON).as_bytes(), ); - files.push( - &format!("{FFI_DIR}/moon.pkg.json"), - "{ \"warn-list\": \"-44\", \"supported-targets\": [\"wasm\"] }".as_bytes(), - ); } } +/// lift func name, lift, lower func name, lower +pub(crate) struct AsyncBinding(pub HashMap); + /// Async-specific helpers used by `InterfaceGenerator` to keep the main /// visitor implementation focused on shared lowering/lifting logic. impl<'a> InterfaceGenerator<'a> { - /// Builds the MoonBit body for async imports, wiring wasm subtasks into the - /// runtime and lowering/lifting payloads as needed. - pub(super) fn generate_async_import_function( - &mut self, - func: &Function, - mbt_sig: MoonbitSignature, - sig: &WasmSignature, - ) -> String { - let mut body = String::default(); - let mut lower_params = Vec::new(); - let mut lower_results = Vec::new(); - - if sig.indirect_params { - match &func.params[..] { - [] => {} - [_] => { - lower_params.push("_lower_ptr".into()); - } - multiple_params => { - let params = multiple_params.iter().map(|Param { ty, .. }| ty); - let offsets = self.world_gen.sizes.field_offsets(params.clone()); - let elem_info = self.world_gen.sizes.params(params); - body.push_str(&format!( - r#" - let _lower_ptr : Int = {ffi}malloc({}) - "#, - elem_info.size.size_wasm32(), - ffi = self - .world_gen - .pkg_resolver - .qualify_package(self.name, FFI_DIR) - )); - - for ((offset, ty), name) in offsets.iter().zip( - multiple_params - .iter() - .map(|Param { name, .. }| name.to_moonbit_ident()), - ) { - let result = self.lower_to_memory( - &format!("_lower_ptr + {}", offset.size_wasm32()), - &name, - ty, - self.name, - ); - body.push_str(&result); - } - - lower_params.push("_lower_ptr".into()); - } - } - } else { - let mut f = FunctionBindgen::new(self, Box::new([])); - for (name, ty) in mbt_sig.params.iter() { - lower_params.extend(abi::lower_flat( - f.interface_gen.resolve, - &mut f, - name.clone(), - ty, - )); - } - lower_results.push(f.src.clone()); - } - - let func_name = func.name.to_upper_camel_case(); - - let ffi = self - .world_gen - .pkg_resolver - .qualify_package(self.name, FFI_DIR); - - let call_import = |params: &Vec| { - format!( - r#" - let _subtask_code = wasmImport{func_name}({}) - let _subtask_status = {ffi}SubtaskStatus::decode(_subtask_code) - let _subtask = @ffi.Subtask::from_handle(_subtask_status.handle(), code=_subtask_code) - - let task = @ffi.current_task() - task.add_waitable(_subtask, @ffi.current_coroutine()) - defer task.remove_waitable(_subtask) - - for {{ - if _subtask.done() || _subtask_status is Returned(_) {{ - break - }} else {{ - @ffi.suspend() - }} - }} - - "#, - params.join(", ") - ) - }; - match &func.result { - Some(ty) => { - lower_params.push("_result_ptr".into()); - let call_import = call_import(&lower_params); - let (lift, lift_result) = &self.lift_from_memory("_result_ptr", ty, self.name); - body.push_str(&format!( - r#" - {} - {} - {call_import} - {lift} - {lift_result} - "#, - lower_results.join("\n"), - &self.malloc_memory("_result_ptr", "1", ty) - )); - } - None => { - let call_import = call_import(&lower_params); - body.push_str(&call_import); - } - } - - body.to_string() - } - - /// Ensures async futures and streams referenced by `func` have their helper - /// import tables generated for the given module prefix. - pub(super) fn generation_futures_and_streams_import( - &mut self, - prefix: &str, - func: &Function, - module: &str, - ) { - let module = format!("{prefix}{module}"); - for (index, ty) in func - .find_futures_and_streams(self.resolve) - .into_iter() - .enumerate() - { - let func_name = &func.name; - - match &self.resolve.types[ty].kind { - TypeDefKind::Future(payload_type) => { - self.r#generate_async_future_or_stream_import( - PayloadFor::Future, - &module, - index, - func_name, + /// Generate the async bindings for this function + pub(crate) fn generate_async_binding(&mut self, func: &Function) -> AsyncBinding { + let mut map = HashMap::new(); + let futures_and_streams = func.find_futures_and_streams(self.resolve); + let (module, func_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::Func { + interface: self.interface, + func, + }, + ); + for (idx, type_) in futures_and_streams.iter().enumerate() { + let ty = dealias(self.resolve, *type_); + match self.resolve.types[ty].kind { + TypeDefKind::Future(_) => { + map.insert( ty, - payload_type.as_ref(), + self.generate_future_binding(ty, idx, &module, &func_name), ); } - TypeDefKind::Stream(payload_type) => { - self.r#generate_async_future_or_stream_import( - PayloadFor::Stream, - &module, - index, - func_name, + TypeDefKind::Stream(_) => { + map.insert( ty, - payload_type.as_ref(), + self.generate_stream_binding(ty, idx, &module, &func_name), ); } - _ => unreachable!(), + _ => unreachable!("Expected future and stream"), } } + AsyncBinding(map) } - fn generate_async_future_or_stream_import( + pub(crate) fn generate_future_binding( &mut self, - payload_for: PayloadFor, - module: &str, + ty: TypeId, index: usize, + module: &str, func_name: &str, - ty: TypeId, - result_type: Option<&Type>, - ) { - if !self - .world_gen - .async_support - .register_future_or_stream(module, ty) - { - return; - } - let result = match result_type { - Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), - None => "Unit".into(), - }; - - let type_name = self + ) -> (String, String, String, String) { + let mut lift = Source::default(); + let mut lower = Source::default(); + + let camel_name = func_name.to_upper_camel_case(); + let lifted_func_name = format!("wasmLift{camel_name}{index}"); + let lowered_func_name = format!("wasmLower{camel_name}{index}"); + let async_qualifier = self .world_gen .pkg_resolver - .type_name(self.name, &Type::Id(ty)); - let name = result.to_upper_camel_case(); - let kind = match payload_for { - PayloadFor::Future => "future", - PayloadFor::Stream => "stream", - }; - let table_name = format!("{}_{}_table", type_name.to_snake_case(), kind); - let camel_kind = kind.to_upper_camel_case(); - let payload_len_arg = match payload_for { - PayloadFor::Future => "", - PayloadFor::Stream => " ,length : Int", - }; - - let payload_lift_func = match payload_for { - PayloadFor::Future => "", - PayloadFor::Stream => "List", - }; - let ffi = self + .qualify_package(self.name, ASYNC_DIR); + let lifted = self .world_gen .pkg_resolver - .qualify_package(self.name, FFI_DIR); + .type_name(self.name, &Type::Id(ty)); + let lowered = lifted.replace("FutureR", "OutFuture"); - let mut dealloc_list; - let malloc; - let lift; - let lower; - let lift_result; - let lift_list: String; - let lower_list: String; - if let Some(result_type) = result_type { - (lift, lift_result) = self.lift_from_memory("ptr", result_type, module); - lower = self.lower_to_memory("ptr", "value", result_type, module); - dealloc_list = self.deallocate_lists( - std::slice::from_ref(result_type), - &[String::from("ptr")], - true, - module, + // The unit case is specially handled and serves as an example + if let TypeDefKind::Future(Option::None) = self.resolve.types[ty].kind { + // write intrinsics + uwriteln!( + lift, + r#" +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "[export]{module}" "[async-lower][future-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "[export]{module}" "[future-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[future-drop-readable-{index}]{func_name}" + "#, ); - lift_list = self.list_lift_from_memory( - "ptr", - "length", - &format!("wasm{name}{kind}Lift"), - result_type, + uwriteln!( + lower, + r#" +fn wasmLower{camel_name}{index}New -> UInt64 = "{module}" "[future-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[future-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" + "# ); - lower_list = - self.list_lower_to_memory(&format!("wasm{name}{kind}Lower"), "value", result_type); - - malloc = self.malloc_memory("ptr", "length", result_type); - if dealloc_list.is_empty() { - dealloc_list = "let _ = ptr".to_string(); - } - } else { - lift = "let _ = ptr".to_string(); - lower = "let _ = (ptr, value)".to_string(); - dealloc_list = "let _ = ptr".to_string(); - malloc = "let ptr = 0;".into(); - lift_result = "".into(); - lift_list = "FixedArray::make(length, Unit::default())".into(); - lower_list = "0".into(); - } - - let (mut lift_func, mut lower_func) = if result_type - .is_some_and(|ty| self.is_list_canonical(self.resolve, ty)) - && matches!(payload_for, PayloadFor::Stream) - { - ("".into(), "".into()) - } else { - ( - format!( - r#" - fn wasm{name}{kind}Lift(ptr: Int) -> {result} {{ - {lift} - {lift_result} - }} - "# - ), - format!( - r#" - fn wasm{name}{kind}Lower(value: {result}, ptr: Int) -> Unit {{ - {lower} - }} - "# - ), - ) - }; - - if matches!(payload_for, PayloadFor::Stream) { - lift_func.push_str(&format!( + // generate function + let size = self.world_gen.sizes.size(&Type::Id(ty)).size_wasm32(); + uwriteln!( + lift, r#" - fn wasm{name}{kind}ListLift(ptr: Int, length: Int) -> FixedArray[{result}] {{ - {lift_list} - }} - "# - )); +fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ + let mut result = None + let mut dropped = false + let mut reading = 0 + async fn drop() {{ + if reading > 0 {{ + {async_qualifier}suspend_for_future_read( + future_handle, + wasmLift{camel_name}{index}CancelRead(future_handle) + ) catch {{ _ => () }} + }} + if !dropped {{ + dropped = true + wasmLift{camel_name}{index}DropReadable(future_handle) + }} + }} + {async_qualifier}FutureR::{{ + get: fn () {{ + if result is Some(r) {{ + return r + }} + if dropped {{ + raise {async_qualifier}FutureReadError::Dropped + }} + let ptr = mbt_ffi_malloc({size}) + defer mbt_ffi_free(ptr) + {{ + reading += 1 + defer {{ reading -= 1 }} + {async_qualifier}suspend_for_future_read( + future_handle, + wasmLift{camel_name}{index}Read(future_handle, ptr), + ) + }} + "# + ); + // lift from memory if it were actual data + uwriteln!( + lift, + r#" + result = Some(()) + drop() + result.unwrap() + }}, + drop + }} +}} +"# + ); - lower_func.push_str(&format!( + uwriteln!( + lower, r#" - fn wasm{name}{kind}ListLower(value: FixedArray[{result}]) -> Int {{ - {lower_list} - }} - "# - )); - }; +fn wasmLower{camel_name}{index}(future : {lowered}) -> Int {{ + ... +}} + "# + ); + return ( + lifted_func_name, + lift.to_string(), + lowered_func_name, + lower.to_string(), + ); + } + // write intrinsics uwriteln!( - self.ffi, + lift, r#" -fn wasmImport{name}{kind}New() -> UInt64 = "{module}" "[{kind}-new-{index}]{func_name}" -fn wasmImport{name}{kind}Read(handle : Int, buffer_ptr : Int{payload_len_arg}) -> Int = "{module}" "[async-lower][{kind}-read-{index}]{func_name}" -fn wasmImport{name}{kind}Write(handle : Int, buffer_ptr : Int{payload_len_arg}) -> Int = "{module}" "[async-lower][{kind}-write-{index}]{func_name}" -fn wasmImport{name}{kind}CancelRead(handle : Int) -> Int = "{module}" "[{kind}-cancel-read-{index}]{func_name}" -fn wasmImport{name}{kind}CancelWrite(handle : Int) -> Int = "{module}" "[{kind}-cancel-write-{index}]{func_name}" -fn wasmImport{name}{kind}DropReadable(handle : Int) = "{module}" "[{kind}-drop-readable-{index}]{func_name}" -fn wasmImport{name}{kind}DropWritable(handle : Int) = "{module}" "[{kind}-drop-writable-{index}]{func_name}" -fn wasm{name}{kind}Deallocate(ptr: Int) -> Unit {{ - {dealloc_list} -}} -fn wasm{name}{kind}Malloc(length: Int) -> Int {{ - {malloc} - ptr -}} +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "{module}" "[future-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[future-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[future-drop-readable-{index}]{func_name}" + "#, + ); + uwriteln!( + lower, + r#" +fn wasmLower{camel_name}{index}New -> UInt64 = "{module}" "[future-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[future-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" + "# + ); -fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ - {ffi}{camel_kind}VTable::new( - wasmImport{name}{kind}New, - wasmImport{name}{kind}Read, - wasmImport{name}{kind}Write, - wasmImport{name}{kind}CancelRead, - wasmImport{name}{kind}CancelWrite, - wasmImport{name}{kind}DropReadable, - wasmImport{name}{kind}DropWritable, - wasm{name}{kind}Malloc, - wasm{name}{kind}Deallocate, - wasm{name}{kind}{payload_lift_func}Lift, - wasm{name}{kind}{payload_lift_func}Lower, + // Generate function + uwriteln!( + lift, + r#" +fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ + let mut result = None + (() => {{ + if result is Some(r) {{ + return r + }} + let ptr = wasmResultUnitTypesErrorCodefutureMalloc(1) + defer mbt_ffi_free(ptr) + {async_qualifier}suspend_for_future_read( + future_handle, + wasmLift{camel_name}{index}Read(future_handle, ptr), ) + // lift + let lifted = wasmResultUnitTypesErrorCodefutureLift(ptr) + result = Some(lifted) + wasmLift{camel_name}{index}DropReadable(future_handle) + result.unwrap() + }}) }} -{lift_func} -{lower_func} -"# + "# ); - } - - fn deallocate_lists( - &mut self, - types: &[Type], - operands: &[String], - indirect: bool, - module: &str, - ) -> String { - let mut f = FunctionBindgen::new(self, Box::new([])); - abi::deallocate_lists_in_types(f.interface_gen.resolve, types, operands, indirect, &mut f); - f.src - } - - fn lift_from_memory(&mut self, address: &str, ty: &Type, module: &str) -> (String, String) { - let mut f = FunctionBindgen::new(self, Box::new([])); - - let result = abi::lift_from_memory(f.interface_gen.resolve, &mut f, address.into(), ty); - (f.src, result) - } - - fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { - let mut f = FunctionBindgen::new(self, Box::new([])); - abi::lower_to_memory( - f.interface_gen.resolve, - &mut f, - address.into(), - value.into(), - ty, + uwriteln!( + lower, + r#" +fn wasmLower{camel_name}{index}(future : {lowered}) -> Int {{ + ... +}} + "# ); - f.src - } - - fn malloc_memory(&mut self, address: &str, length: &str, ty: &Type) -> String { - let size = self.world_gen.sizes.size(ty).size_wasm32(); - let ffi = self - .world_gen - .pkg_resolver - .qualify_package(self.name, FFI_DIR); - format!("let {address} = {ffi}malloc({size} * {length});") - } - fn is_list_canonical(&self, _resolve: &Resolve, element: &Type) -> bool { - matches!( - element, - Type::U8 | Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64 + ( + lifted_func_name, + lift.to_string(), + lowered_func_name, + lower.to_string(), ) } - fn list_lift_from_memory( + pub(crate) fn generate_stream_binding( &mut self, - address: &str, - length: &str, - lift_func: &str, - ty: &Type, - ) -> String { - let ffi = self - .world_gen - .pkg_resolver - .qualify_package(self.name, FFI_DIR); - if self.is_list_canonical(self.resolve, ty) { - if ty == &Type::U8 { - return format!("{ffi}ptr2bytes({address}, {length})"); - } - let ty = match ty { - Type::U32 => "uint", - Type::U64 => "uint64", - Type::S32 => "int", - Type::S64 => "int64", - Type::F32 => "float", - Type::F64 => "double", - _ => unreachable!(), - }; - - return format!("{ffi}ptr2{ty}_array({address}, {length})"); - } - let size = self.world_gen.sizes.size(ty).size_wasm32(); - format!( - r#" - FixedArray::makei( - {length}, - (index) => {{ - let ptr = ({address}) + (index * {size}); - {lift_func}(ptr) - }} - ) - "# - ) - } + ty: TypeId, + index: usize, + module: &str, + func_name: &str, + ) -> (String, String, String, String) { + let mut lift = Source::default(); + let mut lower = Source::default(); - fn list_lower_to_memory(&mut self, lower_func: &str, value: &str, ty: &Type) -> String { - // Align the address, moonbit only supports wasm32 for now - let ffi = self - .world_gen - .pkg_resolver - .qualify_package(self.name, FFI_DIR); - if self.is_list_canonical(self.resolve, ty) { - if ty == &Type::U8 { - return format!("{ffi}bytes2ptr({value})"); - } + let camel_name = func_name.to_upper_camel_case(); + let lifted_func_name = format!("wasmLift{camel_name}{index}"); + let lowered_func_name = format!("wasmLower{camel_name}{index}"); - let ty = match ty { - Type::U32 => "uint", - Type::U64 => "uint64", - Type::S32 => "int", - Type::S64 => "int64", - Type::F32 => "float", - Type::F64 => "double", - _ => unreachable!(), - }; - return format!("{ffi}{ty}_array2ptr({value})"); - } - let size = self.world_gen.sizes.size(ty).size_wasm32(); - format!( + // write intrinsics + uwriteln!( + lift, r#" - let address = {ffi}malloc(({value}).length() * {size}); - for index = 0; index < ({value}).length(); index = index + 1 {{ - let ptr = (address) + (index * {size}); - let value = {value}[index]; - {lower_func}(value, ptr); - }} - address - "# +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[stream-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[stream-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[stream-drop-readable-{index}]{func_name}" + "#, + ); + uwriteln!( + lower, + r#" +fn wasmLower{camel_name}{index}New -> UInt64 = "{module}" "[stream-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[stream-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[stream-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop-writable-{index}]{func_name}" + "# + ); + + ( + lifted_func_name, + lift.to_string(), + lowered_func_name, + lower.to_string(), ) } } diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 8c35d5a63..8d0ba9a21 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -21,7 +21,7 @@ use wit_bindgen_core::{ }, }; -use crate::async_support::{ASYNC_DIR, AsyncSupport}; +use crate::async_support::{ASYNC_DIR, AsyncBinding, AsyncSupport}; use crate::pkg::{Imports, MoonbitSignature, PkgResolver, ToMoonBitIdent, ToMoonBitTypeIdent}; mod async_support; @@ -159,6 +159,7 @@ impl MoonBit { ffi_imports: HashSet::new(), derive_opts, interface, + bindings: AsyncBinding(HashMap::new()), } } @@ -531,7 +532,7 @@ impl WorldGenerator for MoonBit { fn finish(&mut self, _resolve: &Resolve, _id: WorldId, files: &mut Files) -> Result<()> { // If async is used, export async utils - self.async_support.emit_utils(files, VERSION); + self.async_support.emit_utils(files); // Export project files if !self.opts.ignore_stub && !self.opts.ignore_module_file { @@ -592,6 +593,9 @@ struct InterfaceGenerator<'a> { // Options for deriving traits derive_opts: DeriveOpts, + + // Generated lift and lower + bindings: AsyncBinding, } impl InterfaceGenerator<'_> { @@ -613,6 +617,7 @@ impl InterfaceGenerator<'_> { .is_async(self.resolve, self.interface, func, false); if async_ { self.world_gen.async_support.mark_async(); + self.bindings = self.generate_async_binding(func); } let ffi_import_name = format!("wasmImport{}", func.name.to_upper_camel_case()); @@ -717,6 +722,7 @@ impl InterfaceGenerator<'_> { .is_async(self.resolve, self.interface, func, false); if async_ { self.world_gen.async_support.mark_async(); + self.bindings = self.generate_async_binding(func); } // Generate stub for user @@ -2641,53 +2647,35 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLift { ty, .. } => { - let result = self.locals.tmp("result"); + self.use_ffi(ffi::MALLOC); + self.use_ffi(ffi::FREE); + let (lifted_func_name, lift, _, _) = self.interface_gen.bindings.0.get(ty).unwrap(); let op = &operands[0]; - // let qualifier = self.r#gen.qualify_package(self.func_interface); - let ty = self.resolve_type_name(&Type::Id(*ty)); - let ffi = self - .interface_gen - .world_gen - .pkg_resolver - .qualify_package(self.interface_gen.name, FFI_DIR); - - let snake_name = format!("static_{}_future_table", ty.to_snake_case(),); - - uwriteln!( - self.src, - r#"let {result} = {ffi}FutureReader::new({op}, {snake_name});"#, - ); - - results.push(result); + results.push(format!("{lifted_func_name}({op})")); + uwriteln!(self.interface_gen.ffi, "{}", lift); } - Instruction::FutureLower { .. } => { + Instruction::FutureLower { ty, .. } => { + let (_, _, lowered_func_name, lower) = + self.interface_gen.bindings.0.get(ty).unwrap(); let op = &operands[0]; - results.push(format!("{op}.handle")); + results.push(format!("{lowered_func_name}({op})")); + uwriteln!(self.interface_gen.ffi, "{}", lower); } - Instruction::StreamLower { .. } => { + Instruction::StreamLower { ty, .. } => { + let (_, _, lowered_func_name, lower) = + self.interface_gen.bindings.0.get(ty).unwrap(); let op = &operands[0]; - results.push(format!("{op}.handle")); + results.push(format!("{lowered_func_name}({op})")); + uwriteln!(self.interface_gen.ffi, "{}", lower); } Instruction::StreamLift { ty, .. } => { - let result = self.locals.tmp("result"); + let (lifted_func_name, lift, _, _) = self.interface_gen.bindings.0.get(ty).unwrap(); let op = &operands[0]; - let qualifier = self.resolve_pkg(self.interface_gen.name); - let ty = self.resolve_type_name(&Type::Id(*ty)); - let ffi = self.resolve_pkg(FFI_DIR); - let snake_name = format!( - "static_{}_stream_table", - ty.replace(&qualifier, "").to_snake_case(), - ); - - uwriteln!( - self.src, - r#"let {result} = {ffi}StreamReader::new({op}, {snake_name});"#, - ); - - results.push(result); + results.push(format!("{lifted_func_name}({op})")); + uwriteln!(self.interface_gen.ffi, "{}", lift); } Instruction::ErrorContextLower { .. } | Instruction::ErrorContextLift { .. } diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index e7c1a1057..a972f9b68 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -9,6 +9,8 @@ use wit_bindgen_core::{ }, }; +use crate::async_support::ASYNC_DIR; + pub(crate) const FFI_DIR: &str = "ffi"; #[derive(Default)] @@ -249,9 +251,9 @@ impl PkgResolver { } TypeDefKind::Future(ty) => { - let qualifier = self.qualify_package(this, FFI_DIR); + let qualifier = self.qualify_package(this, ASYNC_DIR); format!( - "{}FutureReader[{}]", + "{}FutureR[{}]", qualifier, ty.as_ref() .map(|t| self.type_name(this, t)) @@ -260,9 +262,9 @@ impl PkgResolver { } TypeDefKind::Stream(ty) => { - let qualifier = self.qualify_package(this, FFI_DIR); + let qualifier = self.qualify_package(this, ASYNC_DIR); format!( - "{}StreamReader[{}]", + "{}StreamR[{}]", qualifier, ty.as_ref() .map(|t| self.type_name(this, t)) diff --git a/tests/runtime-async/async/simple-future/test.mbt b/tests/runtime-async/async/simple-future/test.mbt index f4a0e49f0..32dedcf90 100644 --- a/tests/runtime-async/async/simple-future/test.mbt +++ b/tests/runtime-async/async/simple-future/test.mbt @@ -2,20 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn read_future( - x : @ffi.FutureReader[Unit], -) -> Unit noraise { - let task = @ffi.current_task() - task.spawn(fn() { - let _ = x.read() catch { _ => raise @ffi.Cancelled::Cancelled } - - }) +pub async fn read_future(x : @async.FutureR[Unit]) -> Unit { + x.get() } ///| -pub async fn drop_future( - x : @ffi.FutureReader[Unit], -) -> Unit noraise { - let task = @ffi.current_task() - let _ = x.drop() +pub async fn drop_future(x : @async.FutureR[Unit]) -> Unit { + x.drop() } From 0a7ab4422c4ce13bf009805cb5a7c37c5ed6e74d Mon Sep 17 00:00:00 2001 From: zihang Date: Thu, 25 Dec 2025 19:15:08 +0800 Subject: [PATCH 20/61] feat: future-cancel-read --- crates/moonbit/src/async/async_abi.mbt | 4 +- crates/moonbit/src/async/ev.mbt | 150 ++++++++++++++---- crates/moonbit/src/async_support.rs | 129 +++++---------- .../async/future-cancel-read/test.mbt | 47 +++--- 4 files changed, 179 insertions(+), 151 deletions(-) diff --git a/crates/moonbit/src/async/async_abi.mbt b/crates/moonbit/src/async/async_abi.mbt index f4491a0a5..0dd25fccf 100644 --- a/crates/moonbit/src/async/async_abi.mbt +++ b/crates/moonbit/src/async/async_abi.mbt @@ -120,14 +120,14 @@ fn WaitableSet::drop(self : Self) -> Unit { ///| priv enum FutureReadResult { Completed = 0 - Cancelled = 1 + Cancelled = 2 } ///| fn FutureReadResult::from(int : Int) -> FutureReadResult { match int { 0 => Completed - 1 => Cancelled + 2 => Cancelled _ => panic() } } diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt index d187d4cf3..310c7f7a3 100644 --- a/crates/moonbit/src/async/ev.mbt +++ b/crates/moonbit/src/async/ev.mbt @@ -22,7 +22,7 @@ priv struct EventLoop { ///| priv struct Subscriber { mut event : Events? - coro : Coroutine + coro : @set.Set[Coroutine] } ///| @@ -56,6 +56,17 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { let waitable_set = current_waitableset() let events = Events::new(EventCode::from(event), waitable_id, code) match events { + None => { + reschedule() + if ev.finished.get(waitable_set) is Some(true) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + return CallbackCode::Completed.encode() + } else { + return CallbackCode::Wait(waitable_set.0).encode() + } + } TaskCancelled => { guard ev.tasks.get(waitable_set) is Some(coro) coro.cancel() @@ -75,7 +86,10 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { let sub = ev.subscribes.get(waitable_id) guard sub is Some(subscriber) subscriber.event = Some(events) - subscriber.coro.wake() + subscriber.coro.each(Coroutine::wake) + subscriber.coro.clear() + ev.subscribes.remove(waitable_id) + waitable_join(waitable_id, 0) reschedule() if ev.finished.get(waitable_set) is Some(true) { ev.tasks.remove(waitable_set) @@ -119,12 +133,15 @@ pub async fn suspend_for_subtask( } // Create subscriber to wait for events - let subscriber = { event: None, coro: current_coroutine() } + // The task has unique subscriber + guard ev.subscribes.get(task.handle) is None + let set = @set.Set::new() + let subscriber = { event: None, coro: set } ev.subscribes.set(task.handle, subscriber) - defer ev.subscribes.remove(task.handle) - waitable_join(task.handle, current_waitableset().0) - defer waitable_join(task.handle, 0) for { + set.add(current_coroutine()) + waitable_join(task.handle, current_waitableset().0) + defer subscriber.coro.remove(current_coroutine()) suspend() catch { Cancelled::Cancelled => // Cancel the subtask @@ -166,16 +183,30 @@ pub async fn suspend_for_subtask( ///| pub async fn suspend_for_future_read(idx : Int, val : Int) -> Unit { let result = if val == -1 { - let subscriber = { event: None, coro: current_coroutine() } - ev.subscribes.set(idx, subscriber) - defer ev.subscribes.remove(idx) + let subscriber = if ev.subscribes.get(idx) is Some(subscriber) { + subscriber + } else { + let set = @set.Set::new() + let subscriber = { event: None, coro: set } + ev.subscribes.set(idx, subscriber) + subscriber + } waitable_join(idx, current_waitableset().0) - defer waitable_join(idx, 0) + subscriber.coro.add(current_coroutine()) + defer subscriber.coro.remove(current_coroutine()) suspend() guard subscriber.event is Some(FutureRead(i, result)) && i == idx result } else { - FutureReadResult::from(val) + let result = FutureReadResult::from(val) + if ev.subscribes.get(idx) is Some(subscriber) { + subscriber.event = Some(FutureRead(idx, result)) + subscriber.coro.each(Coroutine::wake) + subscriber.coro.clear() + ev.subscribes.remove(idx) + waitable_join(idx, 0) + } + result } match result { Completed => return @@ -184,14 +215,33 @@ pub async fn suspend_for_future_read(idx : Int, val : Int) -> Unit { } ///| -pub async fn suspend_for_future_write(idx : Int) -> Bool { - let subscriber = { event: None, coro: current_coroutine() } - ev.subscribes.set(idx, subscriber) - defer ev.subscribes.remove(idx) - waitable_join(idx, current_waitableset().0) - defer waitable_join(idx, 0) - suspend() - guard subscriber.event is Some(FutureWrite(i, result)) && i == idx +pub async fn suspend_for_future_write(idx : Int, val : Int) -> Bool { + let result = if val == -1 { + let subscriber = if ev.subscribes.get(idx) is Some(subscriber) { + subscriber + } else { + let set = @set.Set::new() + let subscriber = { event: None, coro: set } + ev.subscribes.set(idx, subscriber) + subscriber + } + waitable_join(idx, current_waitableset().0) + subscriber.coro.add(current_coroutine()) + defer subscriber.coro.remove(current_coroutine()) + suspend() + guard subscriber.event is Some(FutureWrite(i, result)) && i == idx + result + } else { + let result = FutureWriteResult::from(val) + if ev.subscribes.get(idx) is Some(subscriber) { + subscriber.event = Some(FutureWrite(idx, result)) + subscriber.coro.each(Coroutine::wake) + subscriber.coro.clear() + ev.subscribes.remove(idx) + waitable_join(idx, 0) + } + result + } match result { Completed => true Dropped => false @@ -203,16 +253,31 @@ pub async fn suspend_for_future_write(idx : Int) -> Bool { pub async fn suspend_for_stream_read(idx : Int, val : Int) -> (Int, Bool) { let { progress, copy_result } = if val == -1 { // Blocked, wait for event - let subscriber = { event: None, coro: current_coroutine() } - ev.subscribes.set(idx, subscriber) - defer ev.subscribes.remove(idx) - waitable_join(idx, current_waitableset().0) - defer waitable_join(idx, 0) + let subscriber = if ev.subscribes.get(idx) is Some(subscriber) { + subscriber.coro.add(current_coroutine()) + subscriber + } else { + waitable_join(idx, current_waitableset().0) + let set = @set.Set::new() + set.add(current_coroutine()) + let subscriber = { event: None, coro: set } + ev.subscribes.set(idx, subscriber) + subscriber + } + defer subscriber.coro.remove(current_coroutine()) suspend() guard subscriber.event is Some(StreamRead(i, result)) && i == idx result } else { - StreamResult::from(val) + let result = StreamResult::from(val) + if ev.subscribes.get(idx) is Some(subscriber) { + subscriber.event = Some(StreamRead(idx, result)) + subscriber.coro.each(Coroutine::wake) + subscriber.coro.clear() + ev.subscribes.remove(idx) + waitable_join(idx, 0) + } + result } match copy_result { Completed => return (progress, false) @@ -228,19 +293,33 @@ pub async fn suspend_for_stream_read(idx : Int, val : Int) -> (Int, Bool) { ///| pub async fn suspend_for_stream_write(idx : Int, val : Int) -> (Int, Bool) { - let { progress, copy_result } = if val != -1 { - // Not blocked - StreamResult::from(val) - } else { + let { progress, copy_result } = if val == -1 { // Blocked, wait for event - let subscriber = { event: None, coro: current_coroutine() } - ev.subscribes.set(idx, subscriber) - defer ev.subscribes.remove(idx) - waitable_join(idx, current_waitableset().0) - defer waitable_join(idx, 0) + let subscriber = if ev.subscribes.get(idx) is Some(subscriber) { + subscriber.coro.add(current_coroutine()) + subscriber + } else { + waitable_join(idx, current_waitableset().0) + let set = @set.Set::new() + set.add(current_coroutine()) + let subscriber = { event: None, coro: set } + ev.subscribes.set(idx, subscriber) + subscriber + } + defer subscriber.coro.remove(current_coroutine()) suspend() guard subscriber.event is Some(StreamWrite(i, result)) && i == idx result + } else { + let result = StreamResult::from(val) + if ev.subscribes.get(idx) is Some(subscriber) { + subscriber.event = Some(StreamWrite(idx, result)) + subscriber.coro.each(Coroutine::wake) + subscriber.coro.clear() + ev.subscribes.remove(idx) + waitable_join(idx, 0) + } + result } match copy_result { Completed => return (progress, false) @@ -262,7 +341,8 @@ pub suberror OpCancelled { FutureWriteCancelled } +///| pub(all) suberror FutureReadError { Cancelled Dropped -} \ No newline at end of file +} diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 0db3b82be..c4ad55430 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -6,14 +6,14 @@ use std::{ use heck::{ToSnakeCase, ToUpperCamelCase}; use wit_bindgen_core::{ Files, Source, - abi::{self, WasmSignature, deallocate_lists_and_own_in_types}, + abi::{self, WasmSignature, deallocate_lists_and_own_in_types, lift_from_memory}, dealias, uwriteln, wit_parser::{ Function, LiftLowerAbi, ManglingAndAbi, Resolve, Type, TypeDefKind, TypeId, WasmImport, }, }; -use crate::indent; +use crate::{FunctionBindgen, indent}; use super::InterfaceGenerator; @@ -160,46 +160,47 @@ impl<'a> InterfaceGenerator<'a> { .type_name(self.name, &Type::Id(ty)); let lowered = lifted.replace("FutureR", "OutFuture"); - // The unit case is specially handled and serves as an example - if let TypeDefKind::Future(Option::None) = self.resolve.types[ty].kind { - // write intrinsics - uwriteln!( - lift, - r#" + // write intrinsics + uwriteln!( + lift, + r#" fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "[export]{module}" "[async-lower][future-read-{index}]{func_name}" fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "[export]{module}" "[future-cancel-read-{index}]{func_name}" fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[future-drop-readable-{index}]{func_name}" "#, - ); - uwriteln!( - lower, - r#" + ); + uwriteln!( + lower, + r#" fn wasmLower{camel_name}{index}New -> UInt64 = "{module}" "[future-new-{index}]{func_name}" fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[future-write-{index}]{func_name}" fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" "# - ); + ); - // generate function - let size = self.world_gen.sizes.size(&Type::Id(ty)).size_wasm32(); - uwriteln!( - lift, - r#" + // generate function + let size = self.world_gen.sizes.size(&Type::Id(ty)).size_wasm32(); + uwriteln!( + lift, + r#" fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ let mut result = None let mut dropped = false let mut reading = 0 async fn drop() {{ - if reading > 0 {{ + if !dropped && reading > 0 {{ {async_qualifier}suspend_for_future_read( future_handle, wasmLift{camel_name}{index}CancelRead(future_handle) - ) catch {{ _ => () }} + ) catch {{ + {async_qualifier}FutureReadError::Cancelled => () + _ => panic() + }} }} if !dropped {{ - dropped = true - wasmLift{camel_name}{index}DropReadable(future_handle) + dropped = true + wasmLift{camel_name}{index}DropReadable(future_handle) }} }} {async_qualifier}FutureR::{{ @@ -221,12 +222,23 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ ) }} "# - ); - // lift from memory if it were actual data - uwriteln!( - lift, - r#" - result = Some(()) + ); + let operand = if let TypeDefKind::Future(Some(ty)) = self.resolve.types[ty].kind { + // TODO : solve ownership + let resolve = self.resolve.clone(); + let mut bindgen = FunctionBindgen::new(self, Box::new([])); + let operand = lift_from_memory(&resolve, &mut bindgen, "ptr".to_string(), &ty); + uwriteln!(lift, "{}", bindgen.src); + operand + } else { + "()".into() + }; + + // lift from memory if it were actual data + uwriteln!( + lift, + r#" + result = Some({operand}) drop() result.unwrap() }}, @@ -234,68 +246,8 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ }} }} "# - ); - - uwriteln!( - lower, - r#" -fn wasmLower{camel_name}{index}(future : {lowered}) -> Int {{ - ... -}} - "# - ); - return ( - lifted_func_name, - lift.to_string(), - lowered_func_name, - lower.to_string(), - ); - } - - // write intrinsics - uwriteln!( - lift, - r#" -fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "{module}" "[future-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[future-cancel-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[future-drop-readable-{index}]{func_name}" - "#, - ); - uwriteln!( - lower, - r#" -fn wasmLower{camel_name}{index}New -> UInt64 = "{module}" "[future-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[future-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" - "# ); - // Generate function - uwriteln!( - lift, - r#" -fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ - let mut result = None - (() => {{ - if result is Some(r) {{ - return r - }} - let ptr = wasmResultUnitTypesErrorCodefutureMalloc(1) - defer mbt_ffi_free(ptr) - {async_qualifier}suspend_for_future_read( - future_handle, - wasmLift{camel_name}{index}Read(future_handle, ptr), - ) - // lift - let lifted = wasmResultUnitTypesErrorCodefutureLift(ptr) - result = Some(lifted) - wasmLift{camel_name}{index}DropReadable(future_handle) - result.unwrap() - }}) -}} - "# - ); uwriteln!( lower, r#" @@ -304,7 +256,6 @@ fn wasmLower{camel_name}{index}(future : {lowered}) -> Int {{ }} "# ); - ( lifted_func_name, lift.to_string(), diff --git a/tests/runtime-async/async/future-cancel-read/test.mbt b/tests/runtime-async/async/future-cancel-read/test.mbt index bdb83de7b..3cfd9d843 100644 --- a/tests/runtime-async/async/future-cancel-read/test.mbt +++ b/tests/runtime-async/async/future-cancel-read/test.mbt @@ -2,37 +2,34 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn cancel_before_read( - x : @ffi.FutureReader[UInt], -) -> Unit noraise { - let task = @ffi.current_task() - let _ = x.drop() +pub async fn cancel_before_read(x : @async.FutureR[UInt]) -> Unit { + x.drop() } ///| -pub async fn cancel_after_read( - x : @ffi.FutureReader[UInt], -) -> Unit noraise { - let task = @ffi.current_task() - task.spawn( - fn() { - let _ = x.read() catch { _ => raise @ffi.Cancelled::Cancelled } +pub async fn cancel_after_read(x : @async.FutureR[UInt]) -> Unit { + @async.with_task_group(tg => { + tg.spawn_bg(() => x.drop()) + let _ = x.get() catch { + @async.FutureReadError::Cancelled => return + _ => panic() } - ) - task.cancel_waitable(x) + panic() + }) } ///| pub async fn start_read_then_cancel( - data : @ffi.FutureReader[UInt], - signal : @ffi.FutureReader[Unit], -) -> Unit noraise { - let task = @ffi.current_task() - task.spawn( - fn() { let _ = data.read() catch { _ => raise @ffi.Cancelled::Cancelled } }, - ) - task.spawn( - fn() { signal.read() catch { _ => raise @ffi.Cancelled::Cancelled } }, - ) + data : @async.FutureR[UInt], + signal : @async.FutureR[Unit], +) -> Unit { + @async.with_task_group(tg => { + tg.spawn_bg(() => { + guard 4U == (try! data.get()) + }) + tg.spawn_bg(() => { + signal.get() + data.drop() + }) + }) } - From 9d702f42579538549772f2a4d2d64c0f55697e58 Mon Sep 17 00:00:00 2001 From: zihang Date: Thu, 25 Dec 2025 19:29:26 +0800 Subject: [PATCH 21/61] feat: future-cancel-write --- crates/moonbit/src/async_support.rs | 4 +++- crates/moonbit/src/lib.rs | 6 ++++-- .../async/future-cancel-write/test.mbt | 14 +++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index c4ad55430..4c3125dcb 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -221,6 +221,7 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ wasmLift{camel_name}{index}Read(future_handle, ptr), ) }} + result = {{ "# ); let operand = if let TypeDefKind::Future(Some(ty)) = self.resolve.types[ty].kind { @@ -238,7 +239,8 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ uwriteln!( lift, r#" - result = Some({operand}) + Some({operand}) + }} drop() result.unwrap() }}, diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 8d0ba9a21..e1a13c374 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -614,7 +614,8 @@ impl InterfaceGenerator<'_> { .world_gen .opts .async_ - .is_async(self.resolve, self.interface, func, false); + .is_async(self.resolve, self.interface, func, false) + || !func.find_futures_and_streams(self.resolve).is_empty(); if async_ { self.world_gen.async_support.mark_async(); self.bindings = self.generate_async_binding(func); @@ -719,7 +720,8 @@ impl InterfaceGenerator<'_> { .world_gen .opts .async_ - .is_async(self.resolve, self.interface, func, false); + .is_async(self.resolve, self.interface, func, false) + || !func.find_futures_and_streams(self.resolve).is_empty(); if async_ { self.world_gen.async_support.mark_async(); self.bindings = self.generate_async_binding(func); diff --git a/tests/runtime-async/async/future-cancel-write/test.mbt b/tests/runtime-async/async/future-cancel-write/test.mbt index 2b1f21e8d..1e4bd72e1 100644 --- a/tests/runtime-async/async/future-cancel-write/test.mbt +++ b/tests/runtime-async/async/future-cancel-write/test.mbt @@ -2,16 +2,12 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub fn take_then_drop(x : @ffi.FutureReader[String]) -> Unit { - let _ = x.drop() +pub async fn take_then_drop(x : @async.FutureR[String]) -> Unit { + x.drop() } ///| -pub async fn read_and_drop( - x : @ffi.FutureReader[String], -) -> Unit noraise { - let task = @ffi.current_task() - let _ = task.spawn(fn() { - let _ = x.read() catch { _ => raise @ffi.Cancelled::Cancelled } - }) +pub async fn read_and_drop(x : @async.FutureR[String]) -> Unit { + let _ = x.get() + } From d23a3db72500e47c5f11f0fc117034c543933df5 Mon Sep 17 00:00:00 2001 From: zihang Date: Wed, 7 Jan 2026 13:39:13 +0800 Subject: [PATCH 22/61] fix: use async correctly --- crates/moonbit/src/lib.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index e1a13c374..4663bac7e 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -687,7 +687,7 @@ impl InterfaceGenerator<'_> { LiftLower::LowerArgsLiftResults, func, &mut bindgen, - false, + async_, ); let src = bindgen.src.clone(); @@ -1690,13 +1690,6 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .type_name(self.interface_gen.name, ty) } - fn resolve_pkg(&mut self, pkg: &str) -> String { - self.interface_gen - .world_gen - .pkg_resolver - .qualify_package(self.interface_gen.name, pkg) - } - fn use_ffi(&mut self, str: &'static str) { self.interface_gen.ffi_imports.insert(str); } From 7a433ab16cff6c7b4e646592b6ec3e37ab3d3b27 Mon Sep 17 00:00:00 2001 From: zihang Date: Thu, 15 Jan 2026 11:34:32 +0800 Subject: [PATCH 23/61] feat: call async import --- crates/moonbit/src/async/ev.mbt | 14 +- crates/moonbit/src/async_support.rs | 148 +++++++++++++++++- crates/moonbit/src/lib.rs | 44 ++++-- .../async/simple-call-import/runner.mbt | 7 +- .../simple-import-params-results/runner.mbt | 12 ++ 5 files changed, 199 insertions(+), 26 deletions(-) create mode 100644 tests/runtime-async/async/simple-import-params-results/runner.mbt diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt index 310c7f7a3..30c92f1eb 100644 --- a/crates/moonbit/src/async/ev.mbt +++ b/crates/moonbit/src/async/ev.mbt @@ -112,7 +112,6 @@ pub async fn suspend_for_subtask( cleanup_after_started : () -> Unit, ) -> Unit { let task = SubTask::from(val) - defer subtask_drop(task.handle) let mut cleaned = false // Helper: ensure cleanup is called once we've moved past Starting state @@ -123,6 +122,19 @@ pub async fn suspend_for_subtask( } } + // Immediate completion without a handle. + if task.handle == 0 { + ensure_cleanup(task.state) + match task.state { + Returned => return + Cancelled_before_started => raise SubTaskCancelled(before_started=true) + Cancelled_before_returned => raise SubTaskCancelled(before_started=false) + _ => panic() + } + } + + defer subtask_drop(task.handle) + // Initial state, return if finished ensure_cleanup(task.state) match task.state { diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 4c3125dcb..0d9281533 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -3,17 +3,16 @@ use std::{ fmt::Write, }; -use heck::{ToSnakeCase, ToUpperCamelCase}; +use heck::ToUpperCamelCase; use wit_bindgen_core::{ Files, Source, - abi::{self, WasmSignature, deallocate_lists_and_own_in_types, lift_from_memory}, + abi::{self, WasmSignature, deallocate_lists_in_types, lift_from_memory}, dealias, uwriteln, - wit_parser::{ - Function, LiftLowerAbi, ManglingAndAbi, Resolve, Type, TypeDefKind, TypeId, WasmImport, - }, + wit_parser::{Function, LiftLowerAbi, ManglingAndAbi, Type, TypeDefKind, TypeId, WasmImport}, }; -use crate::{FunctionBindgen, indent}; +use crate::pkg::ToMoonBitIdent; +use crate::{FunctionBindgen, ffi, indent}; use super::InterfaceGenerator; @@ -105,6 +104,143 @@ pub(crate) struct AsyncBinding(pub HashMap InterfaceGenerator<'a> { + pub(crate) fn generate_async_import( + &mut self, + func: &Function, + ffi_import_name: &str, + wasm_sig: &WasmSignature, + ) -> String { + let async_pkg = self + .world_gen + .pkg_resolver + .qualify_package(self.name, ASYNC_DIR); + let param_names = func + .params + .iter() + .map(|(name, _)| name.to_moonbit_ident()) + .collect::>(); + let param_types = func.params.iter().map(|(_, ty)| *ty).collect::>(); + let mut bindgen = FunctionBindgen::new(self, param_names.into_boxed_slice()); + let mut lowered_params = Vec::new(); + + let params_ptr = if wasm_sig.indirect_params { + let params_info = bindgen + .interface_gen + .world_gen + .sizes + .record(param_types.iter()); + let params_ptr = bindgen.locals.tmp("params_ptr"); + bindgen.use_ffi(ffi::MALLOC); + uwriteln!( + bindgen.src, + "let {params_ptr} = mbt_ffi_malloc({});", + params_info.size.size_wasm32() + ); + let offsets = bindgen + .interface_gen + .world_gen + .sizes + .field_offsets(param_types.iter()); + for (i, (offset, ty)) in offsets.into_iter().enumerate() { + let param_ptr = bindgen.locals.tmp("param_ptr"); + let arg = bindgen.params[i].clone(); + uwriteln!( + bindgen.src, + "let {param_ptr} = {params_ptr} + {};", + offset.size_wasm32() + ); + abi::lower_to_memory( + bindgen.interface_gen.resolve, + &mut bindgen, + param_ptr, + arg, + ty, + ); + } + lowered_params.push(params_ptr.clone()); + Some(params_ptr) + } else { + for (i, ty) in param_types.iter().enumerate() { + let arg = bindgen.params[i].clone(); + lowered_params.extend(abi::lower_flat( + bindgen.interface_gen.resolve, + &mut bindgen, + arg, + ty, + )); + } + None + }; + let cleaned = bindgen.locals.tmp("cleaned"); + uwriteln!(bindgen.src, "let {cleaned} : Ref[Bool] = {{ val: false }}"); + + let results_ptr = if func.result.is_some() { + let result_info = bindgen.interface_gen.world_gen.sizes.params(&func.result); + let results_ptr = bindgen.locals.tmp("results_ptr"); + bindgen.use_ffi(ffi::MALLOC); + bindgen.use_ffi(ffi::FREE); + uwriteln!( + bindgen.src, + "let {results_ptr} = mbt_ffi_malloc({});\n\ +defer mbt_ffi_free({results_ptr})", + result_info.size.size_wasm32() + ); + Some(results_ptr) + } else { + None + }; + + let mut call_args = lowered_params.clone(); + if let Some(results_ptr) = &results_ptr { + call_args.push(results_ptr.clone()); + } + let subtask = bindgen.locals.tmp("subtask"); + uwriteln!( + bindgen.src, + "let {subtask} = {ffi_import_name}({});", + call_args.join(", ") + ); + + let cleanup_params = bindgen.locals.tmp("cleanup_params"); + uwriteln!( + bindgen.src, + "fn {cleanup_params}() -> Unit {{\n if {cleaned}.val {{ return }}\n {cleaned}.val = true" + ); + let dealloc_operands = if wasm_sig.indirect_params { + vec![params_ptr.clone().unwrap()] + } else { + lowered_params.clone() + }; + deallocate_lists_in_types( + bindgen.interface_gen.resolve, + ¶m_types, + &dealloc_operands, + wasm_sig.indirect_params, + &mut bindgen, + ); + if let Some(params_ptr) = ¶ms_ptr { + bindgen.use_ffi(ffi::FREE); + uwriteln!(bindgen.src, " mbt_ffi_free({params_ptr})"); + } + uwriteln!( + bindgen.src, + "}}\nfn cleanup_after_started() -> Unit {{ {cleanup_params}() }}\n\ +defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_after_started)", + ); + + if let Some(result) = func.result { + let lifted = lift_from_memory( + bindgen.interface_gen.resolve, + &mut bindgen, + results_ptr.clone().unwrap(), + &result, + ); + uwriteln!(bindgen.src, "return {lifted}"); + } + + bindgen.src + } + /// Generate the async bindings for this function pub(crate) fn generate_async_binding(&mut self, func: &Function) -> AsyncBinding { let mut map = HashMap::new(); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 4663bac7e..81c3d47a8 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -624,16 +624,15 @@ impl InterfaceGenerator<'_> { let ffi_import_name = format!("wasmImport{}", func.name.to_upper_camel_case()); // Generate the core wasm abi + let wasm_sig = self.resolve.wasm_signature( + if async_ { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + func, + ); { - let wasm_sig = self.resolve.wasm_signature( - if async_ { - AbiVariant::GuestImportAsync - } else { - AbiVariant::GuestImport - }, - func, - ); - let result_type = match &wasm_sig.results[..] { [] => "".into(), [result] => format!("-> {}", wasm_type(*result)), @@ -668,6 +667,23 @@ impl InterfaceGenerator<'_> { ); } + if async_ { + let src = self.generate_async_import(func, &ffi_import_name, &wasm_sig); + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); + let sig = self.sig_string(&mbt_sig, async_); + + print_docs(&mut self.src, &func.docs); + uwrite!( + self.src, + r#" + {sig} {{ + {src} + }} + "# + ); + return; + } + // Generate the MoonBit wrapper let mut bindgen = FunctionBindgen::new( self, @@ -679,15 +695,11 @@ impl InterfaceGenerator<'_> { abi::call( bindgen.interface_gen.resolve, - if async_ { - AbiVariant::GuestImportAsync - } else { - AbiVariant::GuestImport - }, + AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, func, &mut bindgen, - async_, + false, ); let src = bindgen.src.clone(); @@ -871,7 +883,7 @@ impl InterfaceGenerator<'_> { func.task_return_import(self.resolve, self.interface, Mangling::Legacy); let params = signature - .results + .params .iter() .enumerate() .map(|(i, param)| { diff --git a/tests/runtime-async/async/simple-call-import/runner.mbt b/tests/runtime-async/async/simple-call-import/runner.mbt index 6ba286794..010a4459c 100644 --- a/tests/runtime-async/async/simple-call-import/runner.mbt +++ b/tests/runtime-async/async/simple-call-import/runner.mbt @@ -1,7 +1,8 @@ //@ [lang] -//@ path = 'gen/interface/a/b/i/stub.mbt' +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["a/b/interface/a/b/i", "a/b/async"] }""" ///| -pub async fn f() -> Unit { - () +pub async fn run() -> Unit { + @i.f() } diff --git a/tests/runtime-async/async/simple-import-params-results/runner.mbt b/tests/runtime-async/async/simple-import-params-results/runner.mbt new file mode 100644 index 000000000..2bf2b0678 --- /dev/null +++ b/tests/runtime-async/async/simple-import-params-results/runner.mbt @@ -0,0 +1,12 @@ +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["a/b/interface/a/b/i", "a/b/async"] }""" + +///| +pub async fn run() -> Unit { + @i.one_argument(1) + assert_eq(@i.one_result(), 2) + assert_eq(@i.one_argument_and_result(3), 4) + @i.two_arguments(5, 6) + assert_eq(@i.two_arguments_and_result(7, 8), 9) +} From e2f6c831f1ca97a515a91fe30a8cbffba65add75 Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 20 Jan 2026 11:04:26 +0800 Subject: [PATCH 24/61] feat: implement future/stream lowering and async cleanup --- crates/moonbit/src/async/trait.mbt | 26 +++ crates/moonbit/src/async_support.rs | 246 +++++++++++++++++++++++++++- crates/moonbit/src/lib.rs | 12 +- 3 files changed, 277 insertions(+), 7 deletions(-) diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index e7fc83a8a..3818afdaf 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -61,3 +61,29 @@ pub fn[X] OutStream::put_stream( coro.wake() } } + +///| +pub(all) struct OutFuture[X] { + mut value : X? + mut coroutine : Coroutine? +} derive(Default) + +///| +pub async fn[X] OutFuture::get(self : OutFuture[X]) -> X { + if self.value is Some(v) { + return v + } else { + guard self.coroutine is None + self.coroutine = Some(current_coroutine()) + suspend() + self.value.unwrap() + } +} + +///| +pub fn[X] OutFuture::put(self : OutFuture[X], value : X) -> Unit { + self.value = Some(value) + if self.coroutine is Some(coro) { + coro.wake() + } +} diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 0d9281533..588a46c0d 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -386,13 +386,59 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ "# ); + // Generate the lower function body + let inner_type = if let TypeDefKind::Future(Some(inner_ty)) = self.resolve.types[ty].kind { + Some(inner_ty) + } else { + None + }; + uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}(future : {lowered}) -> Int {{ - ... -}} - "# +fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ + let handles = wasmLower{camel_name}{index}New() + let readable = (handles & 0xFFFFFFFF).reinterpret_as_int() + let writable = (handles >> 32).reinterpret_as_int() + {async_qualifier}spawn(async fn() {{ + defer wasmLower{camel_name}{index}DropWritable(writable)"# + ); + + if let Some(inner_ty) = inner_type { + let resolve = self.resolve.clone(); + let mut bindgen = FunctionBindgen::new(self, Box::new([])); + bindgen.use_ffi(ffi::MALLOC); + bindgen.use_ffi(ffi::FREE); + uwriteln!( + lower, + r#" + let value = out_future.get() + let ptr = mbt_ffi_malloc({size}) + defer mbt_ffi_free(ptr)"# + ); + abi::lower_to_memory(&resolve, &mut bindgen, "ptr".to_string(), "value".to_string(), &inner_ty); + uwriteln!(lower, "{}", bindgen.src); + uwriteln!( + lower, + r#" + let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, ptr))"# + ); + } else { + // Unit type - no value to write, just complete the future + uwriteln!( + lower, + r#" + let _ = out_future.get() + let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, 0))"# + ); + } + + uwriteln!( + lower, + r#" + }}) + readable +}}"# ); ( lifted_func_name, @@ -415,6 +461,15 @@ fn wasmLower{camel_name}{index}(future : {lowered}) -> Int {{ let camel_name = func_name.to_upper_camel_case(); let lifted_func_name = format!("wasmLift{camel_name}{index}"); let lowered_func_name = format!("wasmLower{camel_name}{index}"); + let async_qualifier = self + .world_gen + .pkg_resolver + .qualify_package(self.name, ASYNC_DIR); + let lifted = self + .world_gen + .pkg_resolver + .type_name(self.name, &Type::Id(ty)); + let lowered = lifted.replace("StreamR", "OutStream"); // write intrinsics uwriteln!( @@ -435,6 +490,189 @@ fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop- "# ); + // Get element type and size + let inner_type = if let TypeDefKind::Stream(Some(inner_ty)) = self.resolve.types[ty].kind { + Some(inner_ty) + } else { + None + }; + let elem_size = inner_type + .map(|t| self.world_gen.sizes.size(&t).size_wasm32()) + .unwrap_or(0); + + // Generate lift function (StreamR from handle) + uwriteln!( + lift, + r#" +fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ + let mut closed = false + let mut reading = 0 + async fn close() {{ + if !closed && reading > 0 {{ + {async_qualifier}suspend_for_stream_read( + stream_handle, + wasmLift{camel_name}{index}CancelRead(stream_handle) + ) catch {{ _ => () }} + }} + if !closed {{ + closed = true + wasmLift{camel_name}{index}DropReadable(stream_handle) + }} + }} + {async_qualifier}StreamR::{{ + read: fn (count : Int) {{ + if closed {{ + return None + }}"# + ); + + if let Some(inner_ty) = inner_type { + let resolve = self.resolve.clone(); + let mut lift_bindgen = FunctionBindgen::new(self, Box::new([])); + lift_bindgen.use_ffi(ffi::MALLOC); + lift_bindgen.use_ffi(ffi::FREE); + + uwriteln!( + lift, + r#" + let ptr = mbt_ffi_malloc(count * {elem_size}) + reading += 1 + let (progress, end) = {{ + defer {{ reading -= 1 }} + {async_qualifier}suspend_for_stream_read( + stream_handle, + wasmLift{camel_name}{index}Read(stream_handle, ptr, count), + ) + }} + if progress == 0 {{ + mbt_ffi_free(ptr) + if end {{ close(); return None }} + return Some([]) + }} + let result = []"# + ); + + // Generate code to lift each element from memory + uwriteln!(lift, " for i = 0; i < progress; i = i + 1 {{"); + uwriteln!(lift, " let elem_ptr = ptr + i * {elem_size}"); + let operand = + lift_from_memory(&resolve, &mut lift_bindgen, "elem_ptr".to_string(), &inner_ty); + uwriteln!(lift, "{}", lift_bindgen.src); + uwriteln!(lift, " result.push({operand})"); + uwriteln!(lift, " }}"); + + uwriteln!( + lift, + r#" + mbt_ffi_free(ptr) + if end {{ close() }} + Some(result[:])"# + ); + } else { + // Unit type stream + uwriteln!( + lift, + r#" + reading += 1 + let (progress, end) = {{ + defer {{ reading -= 1 }} + {async_qualifier}suspend_for_stream_read( + stream_handle, + wasmLift{camel_name}{index}Read(stream_handle, 0, count), + ) + }} + if progress == 0 && end {{ close(); return None }} + let result = FixedArray::make(progress, ()) + if end {{ close() }} + Some(result[:])"# + ); + } + + uwriteln!( + lift, + r#" + }}, + close + }} +}}"# + ); + + // Generate lower function (OutStream to handle) + uwriteln!( + lower, + r#" +fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ + let handles = wasmLower{camel_name}{index}New() + let readable = (handles & 0xFFFFFFFF).reinterpret_as_int() + let writable = (handles >> 32).reinterpret_as_int() + let stream_w = {async_qualifier}StreamW::{{ + write: fn (data : ArrayView[_]) {{ + if data.length() == 0 {{ + return 0 + }}"# + ); + + if let Some(inner_ty) = inner_type { + let resolve = self.resolve.clone(); + let mut lower_bindgen = FunctionBindgen::new(self, Box::new([])); + lower_bindgen.use_ffi(ffi::MALLOC); + lower_bindgen.use_ffi(ffi::FREE); + + uwriteln!( + lower, + r#" + let ptr = mbt_ffi_malloc(data.length() * {elem_size}) + for i = 0; i < data.length(); i = i + 1 {{ + let elem_ptr = ptr + i * {elem_size} + let elem = data[i]"# + ); + + abi::lower_to_memory( + &resolve, + &mut lower_bindgen, + "elem_ptr".to_string(), + "elem".to_string(), + &inner_ty, + ); + uwriteln!(lower, "{}", lower_bindgen.src); + uwriteln!(lower, " }}"); + + uwriteln!( + lower, + r#" + let (progress, _) = {async_qualifier}suspend_for_stream_write( + writable, + wasmLower{camel_name}{index}Write(writable, ptr, data.length()), + ) + mbt_ffi_free(ptr) + progress"# + ); + } else { + // Unit type stream + uwriteln!( + lower, + r#" + let (progress, _) = {async_qualifier}suspend_for_stream_write( + writable, + wasmLower{camel_name}{index}Write(writable, 0, data.length()), + ) + progress"# + ); + } + + uwriteln!( + lower, + r#" + }}, + close: fn () {{ + wasmLower{camel_name}{index}DropWritable(writable) + }} + }} + out_stream.put_stream(stream_w) + readable +}}"# + ); + ( lifted_func_name, lift.to_string(), diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 81c3d47a8..0a5983d65 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -778,11 +778,15 @@ impl InterfaceGenerator<'_> { async_, ); - // TODO: adapt async cleanup - assert!(!bindgen.needs_cleanup_list); - let src = bindgen.src; + // Handle cleanup for both sync and async exports + let cleanup_list = if bindgen.needs_cleanup_list { + "let cleanup_list : Array[Int] = []" + } else { + "" + }; + let result_type = match &sig.results[..] { [] => "Unit", [result] => wasm_type(*result), @@ -818,6 +822,7 @@ impl InterfaceGenerator<'_> { #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ {async_pkg}with_waitableset(() => {async_pkg}with_task_group(task_group => task_group.spawn_bg(() => {{ + {cleanup_list} {src} }}))) }} @@ -829,6 +834,7 @@ impl InterfaceGenerator<'_> { r#" #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ + {cleanup_list} {src} }} "#, From 824ca792f18d46bcc910ff4b8d9094f2497ac48b Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 20 Jan 2026 11:06:27 +0800 Subject: [PATCH 25/61] test: add tests for future/stream lowering --- .../async/moonbit-future-write/runner.rs | 20 +++++++++ .../async/moonbit-future-write/test.mbt | 12 ++++++ .../async/moonbit-future-write/test.wit | 18 ++++++++ .../async/moonbit-stream-write/runner.rs | 42 +++++++++++++++++++ .../async/moonbit-stream-write/test.mbt | 22 ++++++++++ .../async/moonbit-stream-write/test.wit | 18 ++++++++ 6 files changed, 132 insertions(+) create mode 100644 tests/runtime-async/async/moonbit-future-write/runner.rs create mode 100644 tests/runtime-async/async/moonbit-future-write/test.mbt create mode 100644 tests/runtime-async/async/moonbit-future-write/test.wit create mode 100644 tests/runtime-async/async/moonbit-stream-write/runner.rs create mode 100644 tests/runtime-async/async/moonbit-stream-write/test.mbt create mode 100644 tests/runtime-async/async/moonbit-stream-write/test.wit diff --git a/tests/runtime-async/async/moonbit-future-write/runner.rs b/tests/runtime-async/async/moonbit-future-write/runner.rs new file mode 100644 index 000000000..ac3022682 --- /dev/null +++ b/tests/runtime-async/async/moonbit-future-write/runner.rs @@ -0,0 +1,20 @@ +include!(env!("BINDINGS")); + +use crate::my::test::i::*; + +struct Component; + +export!(Component); + +impl Guest for Component { + async fn run() { + // Test creating a future with a value + let rx = create_future_with_value(42).await; + let value = rx.read().await.unwrap(); + assert_eq!(value, 42); + + // Test creating a unit future + let rx = create_unit_future().await; + rx.read().await.unwrap(); + } +} diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt new file mode 100644 index 000000000..f713f8cab --- /dev/null +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -0,0 +1,12 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/i/stub.mbt' + +///| +pub async fn create_future_with_value(value : UInt, out_future : @async.OutFuture[UInt]) -> Unit { + out_future.put(value) +} + +///| +pub async fn create_unit_future(out_future : @async.OutFuture[Unit]) -> Unit { + out_future.put(()) +} diff --git a/tests/runtime-async/async/moonbit-future-write/test.wit b/tests/runtime-async/async/moonbit-future-write/test.wit new file mode 100644 index 000000000..660de8193 --- /dev/null +++ b/tests/runtime-async/async/moonbit-future-write/test.wit @@ -0,0 +1,18 @@ +package my:test; + +interface i { + // MoonBit creates a future, writes a value, and returns it + create-future-with-value: async func(value: u32) -> future; + // MoonBit creates a future without a payload + create-unit-future: async func() -> future; +} + +world test { + export i; +} + +world runner { + import i; + + export run: async func(); +} diff --git a/tests/runtime-async/async/moonbit-stream-write/runner.rs b/tests/runtime-async/async/moonbit-stream-write/runner.rs new file mode 100644 index 000000000..29dbbd90b --- /dev/null +++ b/tests/runtime-async/async/moonbit-stream-write/runner.rs @@ -0,0 +1,42 @@ +include!(env!("BINDINGS")); + +use crate::my::test::i::*; + +struct Component; + +export!(Component); + +impl Guest for Component { + async fn run() { + // Test creating a stream with u32 values + let rx = create_stream_with_values(3).await; + let mut total = 0u32; + let mut count = 0u32; + loop { + match rx.read(10).await { + Some(values) => { + for v in values { + total += v; + count += 1; + } + } + None => break, + } + } + assert_eq!(count, 3); + assert_eq!(total, 0 + 1 + 2); // 0, 1, 2 + + // Test creating a unit stream + let rx = create_unit_stream(5).await; + let mut count = 0u32; + loop { + match rx.read(10).await { + Some(values) => { + count += values.len() as u32; + } + None => break, + } + } + assert_eq!(count, 5); + } +} diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt new file mode 100644 index 000000000..9f2dc8bc5 --- /dev/null +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -0,0 +1,22 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/i/stub.mbt' + +///| +pub async fn create_stream_with_values(count : UInt, out_stream : @async.OutStream[UInt]) -> Unit { + let stream = out_stream.get_stream() + for i = 0; i < count.reinterpret_as_int(); i = i + 1 { + let arr : Array[UInt] = [i.reinterpret_as_uint()] + let _ = (stream.write)(arr[:]) + } + (stream.close)() +} + +///| +pub async fn create_unit_stream(count : UInt, out_stream : @async.OutStream[Unit]) -> Unit { + let stream = out_stream.get_stream() + for i = 0; i < count.reinterpret_as_int(); i = i + 1 { + let arr : Array[Unit] = [()] + let _ = (stream.write)(arr[:]) + } + (stream.close)() +} diff --git a/tests/runtime-async/async/moonbit-stream-write/test.wit b/tests/runtime-async/async/moonbit-stream-write/test.wit new file mode 100644 index 000000000..10c15ec10 --- /dev/null +++ b/tests/runtime-async/async/moonbit-stream-write/test.wit @@ -0,0 +1,18 @@ +package my:test; + +interface i { + // MoonBit creates a stream, writes values, and returns it + create-stream-with-values: async func(count: u32) -> stream; + // MoonBit creates a unit stream + create-unit-stream: async func(count: u32) -> stream; +} + +world test { + export i; +} + +world runner { + import i; + + export run: async func(); +} From 08aa5b5779529148ff0a1d5eefc45ff4b4a3e048 Mon Sep 17 00:00:00 2001 From: yezihang Date: Wed, 21 Jan 2026 15:28:41 +0800 Subject: [PATCH 26/61] feat: add direction-aware type generation for exports - Add direction field to FunctionBindgen for proper type resolution - Add type_name_for_lowering function to use OutFuture/OutStream instead of FutureR/StreamR for exports - Add sig_string_with_direction for export stub generation - Pass Direction::Import/Export to FunctionBindgen::new calls appropriately --- crates/moonbit/src/async_support.rs | 18 ++++++++----- crates/moonbit/src/lib.rs | 40 ++++++++++++++++++++++++++--- crates/moonbit/src/pkg.rs | 9 +++++++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 588a46c0d..2e05a3454 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -5,7 +5,7 @@ use std::{ use heck::ToUpperCamelCase; use wit_bindgen_core::{ - Files, Source, + Direction, Files, Source, abi::{self, WasmSignature, deallocate_lists_in_types, lift_from_memory}, dealias, uwriteln, wit_parser::{Function, LiftLowerAbi, ManglingAndAbi, Type, TypeDefKind, TypeId, WasmImport}, @@ -120,7 +120,7 @@ impl<'a> InterfaceGenerator<'a> { .map(|(name, _)| name.to_moonbit_ident()) .collect::>(); let param_types = func.params.iter().map(|(_, ty)| *ty).collect::>(); - let mut bindgen = FunctionBindgen::new(self, param_names.into_boxed_slice()); + let mut bindgen = FunctionBindgen::new(self, param_names.into_boxed_slice(), Direction::Import); let mut lowered_params = Vec::new(); let params_ptr = if wasm_sig.indirect_params { @@ -363,7 +363,7 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ let operand = if let TypeDefKind::Future(Some(ty)) = self.resolve.types[ty].kind { // TODO : solve ownership let resolve = self.resolve.clone(); - let mut bindgen = FunctionBindgen::new(self, Box::new([])); + let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import); let operand = lift_from_memory(&resolve, &mut bindgen, "ptr".to_string(), &ty); uwriteln!(lift, "{}", bindgen.src); operand @@ -406,7 +406,7 @@ fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut bindgen = FunctionBindgen::new(self, Box::new([])); + let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export); bindgen.use_ffi(ffi::MALLOC); bindgen.use_ffi(ffi::FREE); uwriteln!( @@ -528,7 +528,7 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut lift_bindgen = FunctionBindgen::new(self, Box::new([])); + let mut lift_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import); lift_bindgen.use_ffi(ffi::MALLOC); lift_bindgen.use_ffi(ffi::FREE); @@ -614,7 +614,11 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut lower_bindgen = FunctionBindgen::new(self, Box::new([])); + let elem_type = self + .world_gen + .pkg_resolver + .type_name(self.name, &inner_ty); + let mut lower_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export); lower_bindgen.use_ffi(ffi::MALLOC); lower_bindgen.use_ffi(ffi::FREE); @@ -624,7 +628,7 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ let ptr = mbt_ffi_malloc(data.length() * {elem_size}) for i = 0; i < data.length(); i = i + 1 {{ let elem_ptr = ptr + i * {elem_size} - let elem = data[i]"# + let elem : {elem_type} = data[i]"# ); abi::lower_to_memory( diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 0a5983d65..0e2ae4aa5 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -691,6 +691,7 @@ impl InterfaceGenerator<'_> { .iter() .map(|Param { name, .. }| name.to_moonbit_ident()) .collect(), + Direction::Import, ); abi::call( @@ -742,7 +743,7 @@ impl InterfaceGenerator<'_> { // Generate stub for user { let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); - let func_sig = self.sig_string(&mbt_sig, async_); + let func_sig = self.sig_string_with_direction(&mbt_sig, async_, Direction::Export); print_docs(&mut self.stub, &func.docs); uwrite!( @@ -767,6 +768,7 @@ impl InterfaceGenerator<'_> { let mut bindgen = FunctionBindgen::new( self, (0..sig.params.len()).map(|i| format!("p{i}")).collect(), + Direction::Export, ); abi::call( @@ -958,6 +960,7 @@ impl InterfaceGenerator<'_> { let mut bindgen = FunctionBindgen::new( self, (0..sig.results.len()).map(|i| format!("p{i}")).collect(), + Direction::Export, ); abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen); @@ -1008,6 +1011,15 @@ impl InterfaceGenerator<'_> { } fn sig_string(&mut self, sig: &MoonbitSignature, async_: bool) -> String { + self.sig_string_with_direction(sig, async_, Direction::Import) + } + + fn sig_string_with_direction( + &mut self, + sig: &MoonbitSignature, + async_: bool, + direction: Direction, + ) -> String { let params = sig .params .iter() @@ -1020,7 +1032,12 @@ impl InterfaceGenerator<'_> { let params = params.join(", "); let result_type = match &sig.result_type { None => "Unit".into(), - Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), + Some(ty) => match direction { + Direction::Export => { + self.world_gen.pkg_resolver.type_name_for_lowering(self.name, ty) + } + Direction::Import => self.world_gen.pkg_resolver.type_name(self.name, ty), + }, }; format!( "pub {}fn {}({params}) -> {}", @@ -1503,12 +1520,14 @@ struct FunctionBindgen<'a, 'b> { payloads: Vec, cleanup: Vec, needs_cleanup_list: bool, + direction: Direction, } impl<'a, 'b> FunctionBindgen<'a, 'b> { fn new( r#gen: &'b mut InterfaceGenerator<'a>, params: Box<[String]>, + direction: Direction, ) -> FunctionBindgen<'a, 'b> { let mut locals = Ns::default(); params.iter().for_each(|str| { @@ -1524,6 +1543,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { payloads: Vec::new(), cleanup: Vec::new(), needs_cleanup_list: false, + direction, } } @@ -1708,6 +1728,13 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .type_name(self.interface_gen.name, ty) } + fn resolve_type_name_for_lowering(&mut self, ty: &Type) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .type_name_for_lowering(self.interface_gen.name, ty) + } + fn use_ffi(&mut self, str: &'static str) { self.interface_gen.ffi_imports.insert(str); } @@ -2371,7 +2398,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { - let ty = format!("({})", self.resolve_type_name(&ty)); + // For exports, use lowering type names (OutFuture/OutStream) + // For imports, use lifting type names (FutureR/StreamR) + let ty = match self.direction { + Direction::Export => { + format!("({})", self.resolve_type_name_for_lowering(&ty)) + } + Direction::Import => format!("({})", self.resolve_type_name(&ty)), + }; let result = self.locals.tmp("result"); if func.result.is_some() { results.push(result.clone()); diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index a972f9b68..592ed06cc 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -288,6 +288,15 @@ impl PkgResolver { } } + /// Generate type name for export result types (lowering context). + /// Uses OutFuture/OutStream instead of FutureR/StreamR. + pub(crate) fn type_name_for_lowering(&mut self, this: &str, ty: &Type) -> String { + let name = self.type_name(this, ty); + // Convert FutureR to OutFuture and StreamR to OutStream for export result types + name.replace("FutureR[", "OutFuture[") + .replace("StreamR[", "OutStream[") + } + pub(crate) fn non_empty_type<'a>(&self, ty: Option<&'a Type>) -> Option<&'a Type> { if let Some(ty) = ty { let id = match ty { From e82f76601855553a3bc5a3497f70822dbf59df6c Mon Sep 17 00:00:00 2001 From: yezihang Date: Wed, 21 Jan 2026 15:29:59 +0800 Subject: [PATCH 27/61] fix: FFI function signatures and export module prefix - Add () to FFI function declarations with no parameters - Use [export] prefix for lowering intrinsic module names - Change reinterpret_as_int() to to_int() for handle conversion - Add let _ = before spawn call to suppress unused result warning --- crates/moonbit/src/async_support.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 2e05a3454..ee9ba8be9 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -308,10 +308,10 @@ fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[futur uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New -> UInt64 = "{module}" "[future-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[future-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[future-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "[export]{module}" "[future-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[future-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[future-drop-writable-{index}]{func_name}" "# ); @@ -398,9 +398,9 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ r#" fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ let handles = wasmLower{camel_name}{index}New() - let readable = (handles & 0xFFFFFFFF).reinterpret_as_int() - let writable = (handles >> 32).reinterpret_as_int() - {async_qualifier}spawn(async fn() {{ + let readable = (handles & 0xFFFFFFFF).to_int() + let writable = (handles >> 32).to_int() + let _ = {async_qualifier}spawn(async fn() {{ defer wasmLower{camel_name}{index}DropWritable(writable)"# ); @@ -483,10 +483,10 @@ fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[stream-drop-r uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New -> UInt64 = "{module}" "[stream-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[stream-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[stream-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[stream-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "[export]{module}" "[stream-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[stream-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[stream-drop-writable-{index}]{func_name}" "# ); @@ -603,8 +603,8 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ r#" fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ let handles = wasmLower{camel_name}{index}New() - let readable = (handles & 0xFFFFFFFF).reinterpret_as_int() - let writable = (handles >> 32).reinterpret_as_int() + let readable = (handles & 0xFFFFFFFF).to_int() + let writable = (handles >> 32).to_int() let stream_w = {async_qualifier}StreamW::{{ write: fn (data : ArrayView[_]) {{ if data.length() == 0 {{ From cc19fa96cc23bb59935b4918907e9ecd05bd9c51 Mon Sep 17 00:00:00 2001 From: yezihang Date: Wed, 21 Jan 2026 15:31:03 +0800 Subject: [PATCH 28/61] feat: make write/close async in StreamW and improve visibility - Make write and close functions async in StreamW generation - Make spawn function public in coroutine.mbt for test usage - Make OutStream struct public for test usage --- crates/moonbit/src/async/coroutine.mbt | 2 +- crates/moonbit/src/async/trait.mbt | 2 +- crates/moonbit/src/async_support.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/moonbit/src/async/coroutine.mbt b/crates/moonbit/src/async/coroutine.mbt index cfd962695..110a59945 100644 --- a/crates/moonbit/src/async/coroutine.mbt +++ b/crates/moonbit/src/async/coroutine.mbt @@ -94,7 +94,7 @@ pub async fn suspend() -> Unit raise Cancelled { } ///| -fn spawn(f : async () -> Unit) -> Coroutine { +pub fn spawn(f : async () -> Unit) -> Coroutine { scheduler.coro_id += 1 let coro = { state: Running, diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index 3818afdaf..a8a293e19 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -27,7 +27,7 @@ pub(all) struct StreamW[X] { } ///| -struct OutStream[X] { +pub(all) struct OutStream[X] { mut stream : StreamW[X]? mut coroutine : Coroutine? } derive(Default) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index ee9ba8be9..027e4e299 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -606,7 +606,7 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ let readable = (handles & 0xFFFFFFFF).to_int() let writable = (handles >> 32).to_int() let stream_w = {async_qualifier}StreamW::{{ - write: fn (data : ArrayView[_]) {{ + write: async fn (data : ArrayView[_]) {{ if data.length() == 0 {{ return 0 }}"# @@ -668,7 +668,7 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ lower, r#" }}, - close: fn () {{ + close: async fn () {{ wasmLower{camel_name}{index}DropWritable(writable) }} }} From 8542570e900ddd56799dfa6656059fccc795b886 Mon Sep 17 00:00:00 2001 From: yezihang Date: Wed, 21 Jan 2026 15:31:28 +0800 Subject: [PATCH 29/61] test: update tests for new OutFuture/OutStream return types - Change function signatures from async with out parameter to returning OutFuture/OutStream - Update runner.rs to use new API and handle end-of-stream properly - Fix stream test to use StreamResult::Complete for end-of-stream detection --- .../async/moonbit-future-write/runner.rs | 4 +- .../async/moonbit-future-write/test.mbt | 12 ++++-- .../async/moonbit-stream-write/runner.rs | 30 +++++++++------ .../async/moonbit-stream-write/test.mbt | 38 ++++++++++++------- 4 files changed, 53 insertions(+), 31 deletions(-) diff --git a/tests/runtime-async/async/moonbit-future-write/runner.rs b/tests/runtime-async/async/moonbit-future-write/runner.rs index ac3022682..78e80b844 100644 --- a/tests/runtime-async/async/moonbit-future-write/runner.rs +++ b/tests/runtime-async/async/moonbit-future-write/runner.rs @@ -10,11 +10,11 @@ impl Guest for Component { async fn run() { // Test creating a future with a value let rx = create_future_with_value(42).await; - let value = rx.read().await.unwrap(); + let value = rx.await; assert_eq!(value, 42); // Test creating a unit future let rx = create_unit_future().await; - rx.read().await.unwrap(); + rx.await; } } diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt index f713f8cab..86a194e8f 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.mbt +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -2,11 +2,15 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_future_with_value(value : UInt, out_future : @async.OutFuture[UInt]) -> Unit { - out_future.put(value) +pub fn create_future_with_value(value : UInt) -> @async.OutFuture[UInt] { + let out : @async.OutFuture[UInt] = { value: None, coroutine: None } + out.put(value) + out } ///| -pub async fn create_unit_future(out_future : @async.OutFuture[Unit]) -> Unit { - out_future.put(()) +pub fn create_unit_future() -> @async.OutFuture[Unit] { + let out : @async.OutFuture[Unit] = { value: None, coroutine: None } + out.put(()) + out } diff --git a/tests/runtime-async/async/moonbit-stream-write/runner.rs b/tests/runtime-async/async/moonbit-stream-write/runner.rs index 29dbbd90b..9e4c1d48b 100644 --- a/tests/runtime-async/async/moonbit-stream-write/runner.rs +++ b/tests/runtime-async/async/moonbit-stream-write/runner.rs @@ -1,6 +1,7 @@ include!(env!("BINDINGS")); use crate::my::test::i::*; +use wit_bindgen::StreamResult; struct Component; @@ -9,32 +10,39 @@ export!(Component); impl Guest for Component { async fn run() { // Test creating a stream with u32 values - let rx = create_stream_with_values(3).await; + let mut rx = create_stream_with_values(3).await; let mut total = 0u32; let mut count = 0u32; loop { - match rx.read(10).await { - Some(values) => { - for v in values { - total += v; + let buf = vec![0u32; 10]; + let (result, values) = rx.read(buf).await; + match result { + StreamResult::Complete(n) if n > 0 => { + // Only process the first n items that were actually read + for v in values.iter().take(n) { + total += *v; count += 1; } } - None => break, + // Complete(0) means end of stream, or Dropped/Cancelled + _ => break, } } assert_eq!(count, 3); assert_eq!(total, 0 + 1 + 2); // 0, 1, 2 // Test creating a unit stream - let rx = create_unit_stream(5).await; + let mut rx = create_unit_stream(5).await; let mut count = 0u32; loop { - match rx.read(10).await { - Some(values) => { - count += values.len() as u32; + let buf = vec![(); 10]; + let (result, _values) = rx.read(buf).await; + match result { + StreamResult::Complete(n) if n > 0 => { + count += n as u32; } - None => break, + // Complete(0) means end of stream, or Dropped/Cancelled + _ => break, } } assert_eq!(count, 5); diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt index 9f2dc8bc5..55fce73aa 100644 --- a/tests/runtime-async/async/moonbit-stream-write/test.mbt +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -2,21 +2,31 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_stream_with_values(count : UInt, out_stream : @async.OutStream[UInt]) -> Unit { - let stream = out_stream.get_stream() - for i = 0; i < count.reinterpret_as_int(); i = i + 1 { - let arr : Array[UInt] = [i.reinterpret_as_uint()] - let _ = (stream.write)(arr[:]) - } - (stream.close)() +pub fn create_stream_with_values(count : UInt) -> @async.OutStream[UInt] { + let out : @async.OutStream[UInt] = { stream: None, coroutine: None } + // The stream writing will happen when get_stream is called + // For now, we set up the stream and spawn a task to write values + let _ = @async.spawn(async fn() { + let stream = out.get_stream() + for i = 0; i < count.reinterpret_as_int(); i = i + 1 { + let arr : Array[UInt] = [i.reinterpret_as_uint()] + let _ = (stream.write)(arr[:]) + } + (stream.close)() + }) + out } ///| -pub async fn create_unit_stream(count : UInt, out_stream : @async.OutStream[Unit]) -> Unit { - let stream = out_stream.get_stream() - for i = 0; i < count.reinterpret_as_int(); i = i + 1 { - let arr : Array[Unit] = [()] - let _ = (stream.write)(arr[:]) - } - (stream.close)() +pub fn create_unit_stream(count : UInt) -> @async.OutStream[Unit] { + let out : @async.OutStream[Unit] = { stream: None, coroutine: None } + let _ = @async.spawn(async fn() { + let stream = out.get_stream() + for i = 0; i < count.reinterpret_as_int(); i = i + 1 { + let arr : Array[Unit] = [()] + let _ = (stream.write)(arr[:]) + } + (stream.close)() + }) + out } From 7f2a46e57a15b4c41990c159a4d48b750ca94c22 Mon Sep 17 00:00:00 2001 From: yezihang Date: Wed, 21 Jan 2026 16:41:22 +0800 Subject: [PATCH 30/61] feat: add TaskGroup argument to exported async functions - Add task_group parameter to async export function signatures - Wrap async export calls with @async.with_task_group - Add async_ field to FunctionBindgen for context tracking - Update tests to use task_group.spawn_bg for background tasks This allows exported async functions to spawn background tasks that will be properly awaited before the function returns. --- crates/moonbit/src/async_support.rs | 32 +++++++++------ crates/moonbit/src/lib.rs | 40 +++++++++++++++---- .../async/moonbit-future-write/test.mbt | 4 +- .../async/moonbit-stream-write/test.mbt | 10 ++--- 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 027e4e299..6655163d9 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -120,7 +120,8 @@ impl<'a> InterfaceGenerator<'a> { .map(|(name, _)| name.to_moonbit_ident()) .collect::>(); let param_types = func.params.iter().map(|(_, ty)| *ty).collect::>(); - let mut bindgen = FunctionBindgen::new(self, param_names.into_boxed_slice(), Direction::Import); + let mut bindgen = + FunctionBindgen::new(self, param_names.into_boxed_slice(), Direction::Import, true); let mut lowered_params = Vec::new(); let params_ptr = if wasm_sig.indirect_params { @@ -363,7 +364,7 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ let operand = if let TypeDefKind::Future(Some(ty)) = self.resolve.types[ty].kind { // TODO : solve ownership let resolve = self.resolve.clone(); - let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import); + let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import, true); let operand = lift_from_memory(&resolve, &mut bindgen, "ptr".to_string(), &ty); uwriteln!(lift, "{}", bindgen.src); operand @@ -406,7 +407,7 @@ fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export); + let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export, true); bindgen.use_ffi(ffi::MALLOC); bindgen.use_ffi(ffi::FREE); uwriteln!( @@ -416,7 +417,13 @@ fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ let ptr = mbt_ffi_malloc({size}) defer mbt_ffi_free(ptr)"# ); - abi::lower_to_memory(&resolve, &mut bindgen, "ptr".to_string(), "value".to_string(), &inner_ty); + abi::lower_to_memory( + &resolve, + &mut bindgen, + "ptr".to_string(), + "value".to_string(), + &inner_ty, + ); uwriteln!(lower, "{}", bindgen.src); uwriteln!( lower, @@ -528,7 +535,7 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut lift_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import); + let mut lift_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import, true); lift_bindgen.use_ffi(ffi::MALLOC); lift_bindgen.use_ffi(ffi::FREE); @@ -555,8 +562,12 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ // Generate code to lift each element from memory uwriteln!(lift, " for i = 0; i < progress; i = i + 1 {{"); uwriteln!(lift, " let elem_ptr = ptr + i * {elem_size}"); - let operand = - lift_from_memory(&resolve, &mut lift_bindgen, "elem_ptr".to_string(), &inner_ty); + let operand = lift_from_memory( + &resolve, + &mut lift_bindgen, + "elem_ptr".to_string(), + &inner_ty, + ); uwriteln!(lift, "{}", lift_bindgen.src); uwriteln!(lift, " result.push({operand})"); uwriteln!(lift, " }}"); @@ -614,11 +625,8 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let elem_type = self - .world_gen - .pkg_resolver - .type_name(self.name, &inner_ty); - let mut lower_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export); + let elem_type = self.world_gen.pkg_resolver.type_name(self.name, &inner_ty); + let mut lower_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export, true); lower_bindgen.use_ffi(ffi::MALLOC); lower_bindgen.use_ffi(ffi::FREE); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 0e2ae4aa5..cecf6360a 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -692,6 +692,7 @@ impl InterfaceGenerator<'_> { .map(|Param { name, .. }| name.to_moonbit_ident()) .collect(), Direction::Import, + false, // sync import ); abi::call( @@ -769,6 +770,7 @@ impl InterfaceGenerator<'_> { self, (0..sig.params.len()).map(|i| format!("p{i}")).collect(), Direction::Export, + async_, ); abi::call( @@ -961,6 +963,7 @@ impl InterfaceGenerator<'_> { self, (0..sig.results.len()).map(|i| format!("p{i}")).collect(), Direction::Export, + false, // post-return is not async ); abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen); @@ -1020,7 +1023,7 @@ impl InterfaceGenerator<'_> { async_: bool, direction: Direction, ) -> String { - let params = sig + let mut params = sig .params .iter() .map(|(name, ty)| { @@ -1029,6 +1032,11 @@ impl InterfaceGenerator<'_> { }) .collect::>(); + // For async exports, add taskgroup parameter + if async_ && matches!(direction, Direction::Export) { + params.push("task_group : @async.TaskGroup[Unit]".to_string()); + } + let params = params.join(", "); let result_type = match &sig.result_type { None => "Unit".into(), @@ -1521,6 +1529,7 @@ struct FunctionBindgen<'a, 'b> { cleanup: Vec, needs_cleanup_list: bool, direction: Direction, + async_: bool, } impl<'a, 'b> FunctionBindgen<'a, 'b> { @@ -1528,6 +1537,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { r#gen: &'b mut InterfaceGenerator<'a>, params: Box<[String]>, direction: Direction, + async_: bool, ) -> FunctionBindgen<'a, 'b> { let mut locals = Ns::default(); params.iter().for_each(|str| { @@ -1544,6 +1554,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { cleanup: Vec::new(), needs_cleanup_list: false, direction, + async_, } } @@ -2415,12 +2426,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { } }; - uwrite!( - self.src, - " - {assignment}{name}({args}); - ", - ); + // For async exports, wrap with with_task_group + if self.async_ && matches!(self.direction, Direction::Export) { + let args_with_tg = if args.is_empty() { + "task_group".to_string() + } else { + format!("{args}, task_group") + }; + uwrite!( + self.src, + " + {assignment}@async.with_task_group(fn(task_group) {{ {name}({args_with_tg}) }}); + ", + ); + } else { + uwrite!( + self.src, + " + {assignment}{name}({args}); + ", + ); + } } Instruction::Return { amt, .. } => { diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt index 86a194e8f..2975f6def 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.mbt +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -2,14 +2,14 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub fn create_future_with_value(value : UInt) -> @async.OutFuture[UInt] { +pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[UInt] { let out : @async.OutFuture[UInt] = { value: None, coroutine: None } out.put(value) out } ///| -pub fn create_unit_future() -> @async.OutFuture[Unit] { +pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[Unit] { let out : @async.OutFuture[Unit] = { value: None, coroutine: None } out.put(()) out diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt index 55fce73aa..489c345ef 100644 --- a/tests/runtime-async/async/moonbit-stream-write/test.mbt +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -2,11 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub fn create_stream_with_values(count : UInt) -> @async.OutStream[UInt] { +pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[UInt] { let out : @async.OutStream[UInt] = { stream: None, coroutine: None } // The stream writing will happen when get_stream is called - // For now, we set up the stream and spawn a task to write values - let _ = @async.spawn(async fn() { + // Spawn a background task to write values using the task group + task_group.spawn_bg(async fn() { let stream = out.get_stream() for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[UInt] = [i.reinterpret_as_uint()] @@ -18,9 +18,9 @@ pub fn create_stream_with_values(count : UInt) -> @async.OutStream[UInt] { } ///| -pub fn create_unit_stream(count : UInt) -> @async.OutStream[Unit] { +pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[Unit] { let out : @async.OutStream[Unit] = { stream: None, coroutine: None } - let _ = @async.spawn(async fn() { + task_group.spawn_bg(async fn() { let stream = out.get_stream() for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[Unit] = [()] From 56306cf36e71951d2c23b0b0334eaedb9c40e7e3 Mon Sep 17 00:00:00 2001 From: yezihang Date: Wed, 21 Jan 2026 17:16:03 +0800 Subject: [PATCH 31/61] fix: use correct TaskGroup type parameter for async exports - TaskGroup type parameter should match the return type, not Unit - Update all test stubs and runners to use correct TaskGroup types --- crates/moonbit/src/lib.rs | 25 +++++++------- .../async/future-cancel-read/test.mbt | 33 +++++++++---------- .../async/future-cancel-write/test.mbt | 5 ++- .../async/moonbit-future-write/test.mbt | 4 +-- .../async/moonbit-stream-write/test.mbt | 4 +-- .../async/simple-call-import/runner.mbt | 2 +- .../async/simple-call-import/test.mbt | 2 +- .../async/simple-future/test.mbt | 4 +-- .../simple-import-params-results/runner.mbt | 2 +- .../simple-import-params-results/test.mbt | 10 +++--- 10 files changed, 45 insertions(+), 46 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index cecf6360a..31fb0e859 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1023,6 +1023,18 @@ impl InterfaceGenerator<'_> { async_: bool, direction: Direction, ) -> String { + // Compute result type first (needed for taskgroup parameter type) + let result_type = match &sig.result_type { + None => "Unit".into(), + Some(ty) => match direction { + Direction::Export => self + .world_gen + .pkg_resolver + .type_name_for_lowering(self.name, ty), + Direction::Import => self.world_gen.pkg_resolver.type_name(self.name, ty), + }, + }; + let mut params = sig .params .iter() @@ -1032,21 +1044,12 @@ impl InterfaceGenerator<'_> { }) .collect::>(); - // For async exports, add taskgroup parameter + // For async exports, add taskgroup parameter with result type if async_ && matches!(direction, Direction::Export) { - params.push("task_group : @async.TaskGroup[Unit]".to_string()); + params.push(format!("task_group : @async.TaskGroup[{result_type}]")); } let params = params.join(", "); - let result_type = match &sig.result_type { - None => "Unit".into(), - Some(ty) => match direction { - Direction::Export => { - self.world_gen.pkg_resolver.type_name_for_lowering(self.name, ty) - } - Direction::Import => self.world_gen.pkg_resolver.type_name(self.name, ty), - }, - }; format!( "pub {}fn {}({params}) -> {}", if async_ { "async " } else { "" }, diff --git a/tests/runtime-async/async/future-cancel-read/test.mbt b/tests/runtime-async/async/future-cancel-read/test.mbt index 3cfd9d843..d0e9f1224 100644 --- a/tests/runtime-async/async/future-cancel-read/test.mbt +++ b/tests/runtime-async/async/future-cancel-read/test.mbt @@ -2,34 +2,31 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn cancel_before_read(x : @async.FutureR[UInt]) -> Unit { +pub async fn cancel_before_read(x : @async.FutureR[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } ///| -pub async fn cancel_after_read(x : @async.FutureR[UInt]) -> Unit { - @async.with_task_group(tg => { - tg.spawn_bg(() => x.drop()) - let _ = x.get() catch { - @async.FutureReadError::Cancelled => return - _ => panic() - } - panic() - }) +pub async fn cancel_after_read(x : @async.FutureR[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { + task_group.spawn_bg(async fn() { x.drop() }) + let _ = x.get() catch { + @async.FutureReadError::Cancelled => return + _ => panic() + } + panic() } ///| pub async fn start_read_then_cancel( data : @async.FutureR[UInt], signal : @async.FutureR[Unit], + task_group : @async.TaskGroup[Unit], ) -> Unit { - @async.with_task_group(tg => { - tg.spawn_bg(() => { - guard 4U == (try! data.get()) - }) - tg.spawn_bg(() => { - signal.get() - data.drop() - }) + task_group.spawn_bg(async fn() { + guard 4U == (try! data.get()) + }) + task_group.spawn_bg(async fn() { + signal.get() + data.drop() }) } diff --git a/tests/runtime-async/async/future-cancel-write/test.mbt b/tests/runtime-async/async/future-cancel-write/test.mbt index 1e4bd72e1..3df11646a 100644 --- a/tests/runtime-async/async/future-cancel-write/test.mbt +++ b/tests/runtime-async/async/future-cancel-write/test.mbt @@ -2,12 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn take_then_drop(x : @async.FutureR[String]) -> Unit { +pub async fn take_then_drop(x : @async.FutureR[String], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } ///| -pub async fn read_and_drop(x : @async.FutureR[String]) -> Unit { +pub async fn read_and_drop(x : @async.FutureR[String], task_group : @async.TaskGroup[Unit]) -> Unit { let _ = x.get() - } diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt index 2975f6def..82afb1ab6 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.mbt +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -2,14 +2,14 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[UInt] { +pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[@async.OutFuture[UInt]]) -> @async.OutFuture[UInt] { let out : @async.OutFuture[UInt] = { value: None, coroutine: None } out.put(value) out } ///| -pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[Unit] { +pub async fn create_unit_future(task_group : @async.TaskGroup[@async.OutFuture[Unit]]) -> @async.OutFuture[Unit] { let out : @async.OutFuture[Unit] = { value: None, coroutine: None } out.put(()) out diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt index 489c345ef..c94840fc7 100644 --- a/tests/runtime-async/async/moonbit-stream-write/test.mbt +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -2,7 +2,7 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[UInt] { +pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[@async.OutStream[UInt]]) -> @async.OutStream[UInt] { let out : @async.OutStream[UInt] = { stream: None, coroutine: None } // The stream writing will happen when get_stream is called // Spawn a background task to write values using the task group @@ -18,7 +18,7 @@ pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGro } ///| -pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[Unit] { +pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[@async.OutStream[Unit]]) -> @async.OutStream[Unit] { let out : @async.OutStream[Unit] = { stream: None, coroutine: None } task_group.spawn_bg(async fn() { let stream = out.get_stream() diff --git a/tests/runtime-async/async/simple-call-import/runner.mbt b/tests/runtime-async/async/simple-call-import/runner.mbt index 010a4459c..90cb85e6d 100644 --- a/tests/runtime-async/async/simple-call-import/runner.mbt +++ b/tests/runtime-async/async/simple-call-import/runner.mbt @@ -3,6 +3,6 @@ //@ pkg_config = """{ "warn-list": "-44", "import": ["a/b/interface/a/b/i", "a/b/async"] }""" ///| -pub async fn run() -> Unit { +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { @i.f() } diff --git a/tests/runtime-async/async/simple-call-import/test.mbt b/tests/runtime-async/async/simple-call-import/test.mbt index 6ba286794..04029a350 100644 --- a/tests/runtime-async/async/simple-call-import/test.mbt +++ b/tests/runtime-async/async/simple-call-import/test.mbt @@ -2,6 +2,6 @@ //@ path = 'gen/interface/a/b/i/stub.mbt' ///| -pub async fn f() -> Unit { +pub async fn f(task_group : @async.TaskGroup[Unit]) -> Unit { () } diff --git a/tests/runtime-async/async/simple-future/test.mbt b/tests/runtime-async/async/simple-future/test.mbt index 32dedcf90..91b0fc8df 100644 --- a/tests/runtime-async/async/simple-future/test.mbt +++ b/tests/runtime-async/async/simple-future/test.mbt @@ -2,11 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn read_future(x : @async.FutureR[Unit]) -> Unit { +pub async fn read_future(x : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.get() } ///| -pub async fn drop_future(x : @async.FutureR[Unit]) -> Unit { +pub async fn drop_future(x : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } diff --git a/tests/runtime-async/async/simple-import-params-results/runner.mbt b/tests/runtime-async/async/simple-import-params-results/runner.mbt index 2bf2b0678..cccd6bd67 100644 --- a/tests/runtime-async/async/simple-import-params-results/runner.mbt +++ b/tests/runtime-async/async/simple-import-params-results/runner.mbt @@ -3,7 +3,7 @@ //@ pkg_config = """{ "warn-list": "-44", "import": ["a/b/interface/a/b/i", "a/b/async"] }""" ///| -pub async fn run() -> Unit { +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { @i.one_argument(1) assert_eq(@i.one_result(), 2) assert_eq(@i.one_argument_and_result(3), 4) diff --git a/tests/runtime-async/async/simple-import-params-results/test.mbt b/tests/runtime-async/async/simple-import-params-results/test.mbt index ddaaac29a..c68158fe2 100644 --- a/tests/runtime-async/async/simple-import-params-results/test.mbt +++ b/tests/runtime-async/async/simple-import-params-results/test.mbt @@ -2,29 +2,29 @@ //@ path = 'gen/interface/a/b/i/stub.mbt' ///| -pub async fn one_argument(x : UInt) -> Unit { +pub async fn one_argument(x : UInt, task_group : @async.TaskGroup[Unit]) -> Unit { assert_eq(x, 1) } ///| -pub async fn one_result() -> UInt noraise { +pub async fn one_result(task_group : @async.TaskGroup[UInt]) -> UInt { 2 } ///| -pub async fn one_argument_and_result(x : UInt) -> UInt { +pub async fn one_argument_and_result(x : UInt, task_group : @async.TaskGroup[UInt]) -> UInt { assert_eq(x, 3) 4 } ///| -pub async fn two_arguments(x : UInt, y : UInt) -> Unit { +pub async fn two_arguments(x : UInt, y : UInt, task_group : @async.TaskGroup[Unit]) -> Unit { assert_eq(x, 5) assert_eq(y, 6) } ///| -pub async fn two_arguments_and_result(x : UInt, y : UInt) -> UInt { +pub async fn two_arguments_and_result(x : UInt, y : UInt, task_group : @async.TaskGroup[UInt]) -> UInt { assert_eq(x, 7) assert_eq(y, 8) 9 From 055e1352e40907c8dbe8bc5fc542fb0e4d94245b Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 23 Jan 2026 17:56:59 +0800 Subject: [PATCH 32/61] fix: use correct taskgroup --- crates/moonbit/src/lib.rs | 18 +++++++++-------- .../future-close-after-coming-back/test.mbt | 8 ++++++-- .../async/moonbit-future-write/test.mbt | 4 ++-- .../async/moonbit-stream-write/test.mbt | 4 ++-- .../simple-import-params-results/test.mbt | 6 +++--- .../async/simple-stream-payload/test.mbt | 20 ++++--------------- .../async/simple-stream/test.mbt | 12 +++-------- 7 files changed, 30 insertions(+), 42 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 31fb0e859..8833cf9f1 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -825,10 +825,12 @@ impl InterfaceGenerator<'_> { r#" #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ - {async_pkg}with_waitableset(() => {async_pkg}with_task_group(task_group => task_group.spawn_bg(() => {{ - {cleanup_list} - {src} - }}))) + {async_pkg}with_waitableset(fn() {{ + {async_pkg}with_task_group(fn(task_group) {{ + {cleanup_list} + {src} + }}) + }}) }} "#, ); @@ -1044,9 +1046,9 @@ impl InterfaceGenerator<'_> { }) .collect::>(); - // For async exports, add taskgroup parameter with result type + // For async exports, add taskgroup parameter (always Unit type) if async_ && matches!(direction, Direction::Export) { - params.push(format!("task_group : @async.TaskGroup[{result_type}]")); + params.push("task_group : @async.TaskGroup[Unit]".to_string()); } let params = params.join(", "); @@ -2429,7 +2431,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } }; - // For async exports, wrap with with_task_group + // For async exports, pass the task_group that was already created if self.async_ && matches!(self.direction, Direction::Export) { let args_with_tg = if args.is_empty() { "task_group".to_string() @@ -2439,7 +2441,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwrite!( self.src, " - {assignment}@async.with_task_group(fn(task_group) {{ {name}({args_with_tg}) }}); + {assignment}{name}({args_with_tg}); ", ); } else { diff --git a/tests/runtime-async/async/future-close-after-coming-back/test.mbt b/tests/runtime-async/async/future-close-after-coming-back/test.mbt index 9f6250656..b70aea1af 100644 --- a/tests/runtime-async/async/future-close-after-coming-back/test.mbt +++ b/tests/runtime-async/async/future-close-after-coming-back/test.mbt @@ -1,7 +1,11 @@ //@ [lang] //@ path = 'gen/interface/a/b/theTest/stub.mbt' -pub fn f(_param : @ffi.FutureReader[Unit]) -> @ffi.FutureReader[Unit] { - _param +pub async fn f(param : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[Unit] { + let out : @async.OutFuture[Unit] = { ..Default::default() } + task_group.spawn_bg(async fn() { + out.put(param.get()) + }) + out } diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt index 82afb1ab6..2975f6def 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.mbt +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -2,14 +2,14 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[@async.OutFuture[UInt]]) -> @async.OutFuture[UInt] { +pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[UInt] { let out : @async.OutFuture[UInt] = { value: None, coroutine: None } out.put(value) out } ///| -pub async fn create_unit_future(task_group : @async.TaskGroup[@async.OutFuture[Unit]]) -> @async.OutFuture[Unit] { +pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[Unit] { let out : @async.OutFuture[Unit] = { value: None, coroutine: None } out.put(()) out diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt index c94840fc7..489c345ef 100644 --- a/tests/runtime-async/async/moonbit-stream-write/test.mbt +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -2,7 +2,7 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[@async.OutStream[UInt]]) -> @async.OutStream[UInt] { +pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[UInt] { let out : @async.OutStream[UInt] = { stream: None, coroutine: None } // The stream writing will happen when get_stream is called // Spawn a background task to write values using the task group @@ -18,7 +18,7 @@ pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGro } ///| -pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[@async.OutStream[Unit]]) -> @async.OutStream[Unit] { +pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[Unit] { let out : @async.OutStream[Unit] = { stream: None, coroutine: None } task_group.spawn_bg(async fn() { let stream = out.get_stream() diff --git a/tests/runtime-async/async/simple-import-params-results/test.mbt b/tests/runtime-async/async/simple-import-params-results/test.mbt index c68158fe2..31020ecc2 100644 --- a/tests/runtime-async/async/simple-import-params-results/test.mbt +++ b/tests/runtime-async/async/simple-import-params-results/test.mbt @@ -7,12 +7,12 @@ pub async fn one_argument(x : UInt, task_group : @async.TaskGroup[Unit]) -> Unit } ///| -pub async fn one_result(task_group : @async.TaskGroup[UInt]) -> UInt { +pub async fn one_result(task_group : @async.TaskGroup[Unit]) -> UInt { 2 } ///| -pub async fn one_argument_and_result(x : UInt, task_group : @async.TaskGroup[UInt]) -> UInt { +pub async fn one_argument_and_result(x : UInt, task_group : @async.TaskGroup[Unit]) -> UInt { assert_eq(x, 3) 4 } @@ -24,7 +24,7 @@ pub async fn two_arguments(x : UInt, y : UInt, task_group : @async.TaskGroup[Uni } ///| -pub async fn two_arguments_and_result(x : UInt, y : UInt, task_group : @async.TaskGroup[UInt]) -> UInt { +pub async fn two_arguments_and_result(x : UInt, y : UInt, task_group : @async.TaskGroup[Unit]) -> UInt { assert_eq(x, 7) assert_eq(y, 8) 9 diff --git a/tests/runtime-async/async/simple-stream-payload/test.mbt b/tests/runtime-async/async/simple-stream-payload/test.mbt index 31eb79be8..4b52029f6 100644 --- a/tests/runtime-async/async/simple-stream-payload/test.mbt +++ b/tests/runtime-async/async/simple-stream-payload/test.mbt @@ -1,20 +1,8 @@ -//@ [lang] +//@ [lang] //@ path = 'gen/interface/my/test_/i/stub.mbt' -pub async fn read_stream(x : @ffi.StreamReader[Byte]) -> Unit raise { - let task = @ffi.current_task() - let buffer = FixedArray::make(10, Byte::default()); +pub async fn read_stream(x : @async.StreamR[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { + while (x.read)(1) is Some(_) { - task.wait(fn(){ - let _ = x.read(buffer, 1) catch { _ => raise @ffi.Cancelled::Cancelled } - }) - task.wait(fn(){ - let _ = x.read(buffer, 2, offset=1) catch { _ => raise @ffi.Cancelled::Cancelled } - }) - task.wait(fn(){ - let _ = x.read(buffer, 1, offset=3) catch { _ => raise @ffi.Cancelled::Cancelled } - }) - task.wait(fn(){ - let _ = x.read(buffer, 1, offset=4) catch { _ => raise @ffi.Cancelled::Cancelled } - }) + } } diff --git a/tests/runtime-async/async/simple-stream/test.mbt b/tests/runtime-async/async/simple-stream/test.mbt index c227a69c4..7d541d80d 100644 --- a/tests/runtime-async/async/simple-stream/test.mbt +++ b/tests/runtime-async/async/simple-stream/test.mbt @@ -1,15 +1,9 @@ //@ [lang] //@ path = 'gen/interface/my/test_/i/stub.mbt' -pub async fn read_stream(x : @ffi.StreamReader[Unit]) -> Unit noraise { - let task = @ffi.current_task() - let buffer = FixedArray::make(10, Unit::default()); +pub async fn read_stream(x : @async.StreamR[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { + while (x.read)(1) is Some(_) { - task.spawn(fn(){ - let _ = x.read(buffer, 1) catch { _ => raise @ffi.Cancelled::Cancelled } - }) - task.spawn(fn(){ - let _ = x.read(buffer, 2) catch { _ => raise @ffi.Cancelled::Cancelled } - }) + } } From 1b4d79b39ce68f29e6ae0de0cc83867bc47f61cd Mon Sep 17 00:00:00 2001 From: yezihang Date: Mon, 26 Jan 2026 14:59:58 +0800 Subject: [PATCH 33/61] test: moonbit should_fail_verify on error_context --- crates/test/src/moonbit.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index e22d9489d..8080bc734 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -94,13 +94,11 @@ impl LanguageMethods for MoonBit { fn should_fail_verify( &self, - name: &str, + _name: &str, config: &crate::config::WitConfig, _args: &[String], ) -> bool { - // async-resource-func actually works, but most other async tests - // fail during codegen or verification - config.async_ && name != "async-resource-func.wit" + config.error_context } fn verify(&self, runner: &Runner, verify: &crate::Verify) -> anyhow::Result<()> { From fd2fade3822d87fa11ceaa366a115fe7b55348f7 Mon Sep 17 00:00:00 2001 From: yezihang Date: Mon, 26 Jan 2026 15:00:50 +0800 Subject: [PATCH 34/61] moonbit: implement DropHandle and remove type stubs --- crates/moonbit/src/lib.rs | 156 +++++++++++++++++++++++--------------- 1 file changed, 96 insertions(+), 60 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 8833cf9f1..38ae32f65 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -13,11 +13,10 @@ use wit_bindgen_core::{ abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}, uwrite, uwriteln, wit_parser::{ - Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Int, InterfaceId, - LiftLowerAbi, Mangling, ManglingAndAbi, Param, Record, Resolve, ResourceIntrinsic, - Result_, - SizeAlign, Tuple, Type, TypeId, Variant, WasmExport, WasmExportKind, WasmImport, WorldId, - WorldKey, + Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Handle, Int, + InterfaceId, LiftLowerAbi, Mangling, ManglingAndAbi, Param, Record, Resolve, + ResourceIntrinsic, Result_, SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, + WasmExport, WasmExportKind, WasmImport, WorldId, WorldKey, }, }; @@ -1497,15 +1496,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { } fn type_future(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { - unimplemented!() // Not needed + // Not needed. They will become `FutureR[T]` in MoonBit. } fn type_stream(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { - unimplemented!() // Not needed + // Not needed. They will become `StreamR[T]` in MoonBit. } fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { - unimplemented!(); + // Not needed. } } @@ -2382,6 +2381,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::CallWasm { sig, name } => { let assignment = match &sig.results[..] { + [] => String::new(), [result] => { let ty = wasm_type(*result); let result = self.locals.tmp("result"); @@ -2389,9 +2389,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(result); assignment } - - [] => String::new(), - _ => unreachable!(), }; @@ -2755,35 +2752,59 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!("{lifted_func_name}({op})")); uwriteln!(self.interface_gen.ffi, "{}", lift); } - Instruction::ErrorContextLower { .. } - | Instruction::ErrorContextLift { .. } - | Instruction::DropHandle { .. } => todo!(), + Instruction::ErrorContextLower { .. } | Instruction::ErrorContextLift { .. } => todo!(), + + Instruction::DropHandle { ty } => { + let op = &operands[0]; + match ty { + Type::Id(id) => match &self.interface_gen.resolve.types[*id].kind { + TypeDefKind::Handle(Handle::Own(_)) => { + let constructor = self + .interface_gen + .world_gen + .pkg_resolver + .type_constructor(self.interface_gen.name, ty); + uwriteln!(self.src, "let _ = {constructor}::drop({op});"); + } + TypeDefKind::Future(_) | TypeDefKind::Stream(_) => { + uwriteln!(self.src, "let _ = {op};"); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + } Instruction::FixedLengthListLift { - element: _, + element, size, id: _, } => { - let array = self.locals.tmp("array"); - let mut elements = String::new(); - for a in operands.drain(0..(*size as usize)) { - elements.push_str(&a); - elements.push_str(", "); + let mut lifted = Vec::with_capacity(*size as usize); + for operand in operands.drain(0..(*size as usize)) { + lifted.push(operand); + } + if lifted.is_empty() { + let ty = self.resolve_type_name(element); + results.push(format!("([] : FixedArray[{ty}])")); + } else { + results.push(format!("[{}]", lifted.join(", "))); } - uwriteln!(self.src, "let {array} : FixedArray[_] = [{elements}]"); - results.push(array); } + Instruction::FixedLengthListLower { element: _, size, id: _, } => { + let op = &operands[0]; for i in 0..(*size as usize) { - results.push(format!("({})[{i}]", operands[0])); + results.push(format!("({op})[{i}]")); } } + Instruction::FixedLengthListLowerToMemory { element, - size: _, + size, id: _, } => { let Block { @@ -2792,25 +2813,33 @@ impl Bindgen for FunctionBindgen<'_, '_> { } = self.blocks.pop().unwrap(); assert!(block_results.is_empty()); - let vec = operands[0].clone(); - let target = operands[1].clone(); - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); - let index = self.locals.tmp("index"); + let op = &operands[0]; + let target = &operands[1]; + let ty = self.resolve_type_name(element); + let elem_size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); - uwrite!( - self.src, - " - for {index} = 0; {index} < ({vec}).length(); {index} = {index} + 1 {{ - let iter_elem = ({vec})[{index}] - let iter_base = ({target}) + ({index} * {size}) - {body} - }} - ", - ); + for i in 0..(*size as usize) { + uwrite!( + self.src, + " + {{ + let iter_elem : {ty} = ({op})[{i}] + let iter_base = ({target}) + ({i} * {elem_size}) + {body} + }} + ", + ); + } } + Instruction::FixedLengthListLiftFromMemory { element, - size: fll_size, + size, id: _, } => { let Block { @@ -2818,33 +2847,40 @@ impl Bindgen for FunctionBindgen<'_, '_> { results: block_results, } = self.blocks.pop().unwrap(); let address = &operands[0]; - let array = self.locals.tmp("array"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, element); - let elem_size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); - let index = self.locals.tmp("index"); + let ty = self.resolve_type_name(element); + let elem_size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); - let result = match &block_results[..] { + let element_result = match &block_results[..] { [result] => result, _ => todo!("result count == {}", block_results.len()), }; - uwrite!( - self.src, - " - let {array} : Array[{ty}] = [] - for {index} = 0; {index} < {fll_size}; {index} = {index} + 1 {{ - let iter_base = ({address}) + ({index} * {elem_size}) - {body} - {array}.push({result}) - }} - ", - ); + let mut lifted = Vec::with_capacity(*size as usize); + for i in 0..(*size as usize) { + let value = self.locals.tmp("fixed_elem"); + uwrite!( + self.src, + " + let {value} : {ty} = {{ + let iter_base = ({address}) + ({i} * {elem_size}) + {body} + {element_result} + }} + ", + ); + lifted.push(value); + } - results.push(format!("FixedArray::from_array({array}[:])")); + if lifted.is_empty() { + results.push(format!("([] : FixedArray[{ty}])")); + } else { + results.push(format!("[{}]", lifted.join(", "))); + } } } } From 98e9039e45368cd01f0c619b2bcb0bcbcf2e582d Mon Sep 17 00:00:00 2001 From: yezihang Date: Mon, 26 Jan 2026 16:18:06 +0800 Subject: [PATCH 35/61] moonbit: document test commands --- crates/moonbit/Cargo.toml | 1 + crates/moonbit/README.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 crates/moonbit/README.md diff --git a/crates/moonbit/Cargo.toml b/crates/moonbit/Cargo.toml index fbf246090..4f8739124 100644 --- a/crates/moonbit/Cargo.toml +++ b/crates/moonbit/Cargo.toml @@ -11,6 +11,7 @@ description = """ MoonBit bindings generator for WIT and the component model, typically used through the `wit-bindgen-cli` crate. """ +readme = "README.md" [lints] workspace = true diff --git a/crates/moonbit/README.md b/crates/moonbit/README.md new file mode 100644 index 000000000..d0107a4c2 --- /dev/null +++ b/crates/moonbit/README.md @@ -0,0 +1,28 @@ +# `wit-bindgen` MoonBit Bindings Generator + +This crate implements the MoonBit guest bindings generator for `wit-bindgen`. + +## Testing + +The repository’s `wit-bindgen test` subcommand is the preferred way to run MoonBit +codegen/runtime tests. See `tests/README.md` for full details. + +### Codegen + +```sh +cargo run test \ + --languages rust,moonbit \ + --artifacts target/artifacts \ + --rust-wit-bindgen-path ./crates/guest-rust \ + tests/codegen +``` + +### Runtime (async) + +```sh +cargo run test --languages rust,moonbit tests/runtime-async \ + --artifacts target/artifacts \ + --rust-wit-bindgen-path ./crates/guest-rust \ + --runner "wasmtime -W component-model-async" +``` + From 40bbfee4d350c875d22b8f47d26ffb3f80d5906f Mon Sep 17 00:00:00 2001 From: yezihang Date: Mon, 2 Feb 2026 17:36:47 +0800 Subject: [PATCH 36/61] moonbit: fix async future/stream support - Fix MoonBit codegen/runtime for futures/streams\n- Adjust runtime-async MoonBit tests and Rust runner --- crates/moonbit/src/async/ev.mbt | 18 +- crates/moonbit/src/async/trait.mbt | 66 +++-- crates/moonbit/src/async_support.rs | 238 +++++++++++++----- crates/moonbit/src/lib.rs | 131 ++++++---- crates/moonbit/src/pkg.rs | 9 - .../future-close-after-coming-back/test.mbt | 7 +- .../async/moonbit-future-write/test.mbt | 12 +- .../async/moonbit-stream-write/runner.rs | 4 +- .../async/moonbit-stream-write/test.mbt | 26 +- .../async/simple-stream-payload/test.mbt | 7 +- .../async/simple-stream/test.mbt | 7 +- 11 files changed, 342 insertions(+), 183 deletions(-) diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt index 30c92f1eb..4e7e4b6ac 100644 --- a/crates/moonbit/src/async/ev.mbt +++ b/crates/moonbit/src/async/ev.mbt @@ -41,7 +41,7 @@ pub fn with_waitableset(f : async () -> Unit) -> Int { ev.tasks.set(waitable_set, coro) ev.finished.set(waitable_set, false) reschedule() - if ev.finished.get(waitable_set) is Some(true) { + if ev.finished.get(waitable_set) is Some(true) && no_more_work() { ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) waitable_set.drop() @@ -58,7 +58,7 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { match events { None => { reschedule() - if ev.finished.get(waitable_set) is Some(true) { + if ev.finished.get(waitable_set) is Some(true) && no_more_work() { ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) waitable_set.drop() @@ -71,7 +71,7 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { guard ev.tasks.get(waitable_set) is Some(coro) coro.cancel() reschedule() - if ev.finished.get(waitable_set) is Some(true) { + if ev.finished.get(waitable_set) is Some(true) && no_more_work() { ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) waitable_set.drop() @@ -91,7 +91,7 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { ev.subscribes.remove(waitable_id) waitable_join(waitable_id, 0) reschedule() - if ev.finished.get(waitable_set) is Some(true) { + if ev.finished.get(waitable_set) is Some(true) && no_more_work() { ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) waitable_set.drop() @@ -336,12 +336,10 @@ pub async fn suspend_for_stream_write(idx : Int, val : Int) -> (Int, Bool) { match copy_result { Completed => return (progress, false) Dropped => return (progress, true) - Cancelled => - if progress > 0 { - return (progress, false) - } else { - raise StreamWriteCancelled - } + // FIXME(WebAssembly/component-model#490): cancellation may be observed in + // cases that should otherwise block. Treat this as no-progress so callers + // can retry. + Cancelled => return (progress, false) } } diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index a8a293e19..2a4d02df8 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -2,6 +2,7 @@ pub(all) struct FutureR[X] { get : async () -> X drop : async () -> Unit + take_handle : () -> Int } ///| @@ -14,10 +15,21 @@ pub async fn[X] FutureR::drop(self : FutureR[X]) -> Unit { (self.drop)() } +///| +pub fn[X] FutureR::take_handle(self : FutureR[X]) -> Int { + (self.take_handle)() +} + ///| pub(all) struct StreamR[X] { read : async (Int) -> ArrayView[X]? close : async () -> Unit + take_handle : () -> Int +} + +///| +pub fn[X] StreamR::take_handle(self : StreamR[X]) -> Int { + (self.take_handle)() } ///| @@ -27,27 +39,37 @@ pub(all) struct StreamW[X] { } ///| -pub(all) struct OutStream[X] { +pub(all) struct OutStreamInner[X] { mut stream : StreamW[X]? mut coroutine : Coroutine? -} derive(Default) +} + +///| +pub(all) struct OutStream[X] { + inner : Ref[OutStreamInner[X]] +} + +///| +pub fn[X] OutStream::new() -> OutStream[X] { + { inner: { val: { stream: None, coroutine: None } } } +} ///| pub async fn[X] OutStream::get_stream(self : OutStream[X]) -> StreamW[X] { - if self.stream is Some(s) { + if self.inner.val.stream is Some(s) { return s } else { - guard self.coroutine is None - self.coroutine = Some(current_coroutine()) + guard self.inner.val.coroutine is None + self.inner.val.coroutine = Some(current_coroutine()) suspend() catch { e => { - if self.stream is Some(s) { + if self.inner.val.stream is Some(s) { (s.close)() } raise e } } - self.stream.unwrap() + self.inner.val.stream.unwrap() } } @@ -56,34 +78,44 @@ pub fn[X] OutStream::put_stream( self : OutStream[X], stream : StreamW[X], ) -> Unit { - self.stream = Some(stream) - if self.coroutine is Some(coro) { + self.inner.val.stream = Some(stream) + if self.inner.val.coroutine is Some(coro) { coro.wake() } } ///| -pub(all) struct OutFuture[X] { +pub(all) struct OutFutureInner[X] { mut value : X? mut coroutine : Coroutine? -} derive(Default) +} + +///| +pub(all) struct OutFuture[X] { + inner : Ref[OutFutureInner[X]] +} + +///| +pub fn[X] OutFuture::new() -> OutFuture[X] { + { inner: { val: { value: None, coroutine: None } } } +} ///| pub async fn[X] OutFuture::get(self : OutFuture[X]) -> X { - if self.value is Some(v) { + if self.inner.val.value is Some(v) { return v } else { - guard self.coroutine is None - self.coroutine = Some(current_coroutine()) + guard self.inner.val.coroutine is None + self.inner.val.coroutine = Some(current_coroutine()) suspend() - self.value.unwrap() + self.inner.val.value.unwrap() } } ///| pub fn[X] OutFuture::put(self : OutFuture[X], value : X) -> Unit { - self.value = Some(value) - if self.coroutine is Some(coro) { + self.inner.val.value = Some(value) + if self.inner.val.coroutine is Some(coro) { coro.wake() } } diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 6655163d9..f5fe026d5 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -8,7 +8,7 @@ use wit_bindgen_core::{ Direction, Files, Source, abi::{self, WasmSignature, deallocate_lists_in_types, lift_from_memory}, dealias, uwriteln, - wit_parser::{Function, LiftLowerAbi, ManglingAndAbi, Type, TypeDefKind, TypeId, WasmImport}, + wit_parser::{Function, Type, TypeDefKind, TypeId}, }; use crate::pkg::ToMoonBitIdent; @@ -246,31 +246,52 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte pub(crate) fn generate_async_binding(&mut self, func: &Function) -> AsyncBinding { let mut map = HashMap::new(); let futures_and_streams = func.find_futures_and_streams(self.resolve); - let (module, func_name) = self.resolve.wasm_import_name( - ManglingAndAbi::Legacy(LiftLowerAbi::Sync), - WasmImport::Func { - interface: self.interface, - func, - }, - ); - for (idx, type_) in futures_and_streams.iter().enumerate() { - let ty = dealias(self.resolve, *type_); - match self.resolve.types[ty].kind { - TypeDefKind::Future(_) => { - map.insert( - ty, - self.generate_future_binding(ty, idx, &module, &func_name), - ); - } - TypeDefKind::Stream(_) => { - map.insert( - ty, - self.generate_stream_binding(ty, idx, &module, &func_name), - ); - } - _ => unreachable!("Expected future and stream"), + + let module_prefix = match self.direction { + Direction::Import => "", + Direction::Export => "[export]", + }; + let base_module = self + .interface + .map(|name| self.resolve.name_world_key(name)) + .unwrap_or_else(|| "$root".to_string()); + let module = format!("{module_prefix}{base_module}"); + + // Select a single index per (dealiased) type to avoid emitting + // multiple bindings for the same future/stream helper. + let mut selected = Vec::<(TypeId, usize)>::new(); + let camel_name = func.name.to_upper_camel_case(); + for (idx, ty) in futures_and_streams.iter().enumerate() { + let ty = dealias(self.resolve, *ty); + if map.contains_key(&ty) { + continue; } + map.insert( + ty, + ( + format!("wasmLift{camel_name}{idx}"), + String::new(), + format!("wasmLower{camel_name}{idx}"), + String::new(), + ), + ); + selected.push((ty, idx)); } + + // Make helper names available while generating bodies so nested + // payloads can reference them. + self.bindings = AsyncBinding(map.clone()); + + for (ty, idx) in selected { + let binding = match self.resolve.types[ty].kind { + TypeDefKind::Future(_) => self.generate_future_binding(ty, idx, &module, &func.name), + TypeDefKind::Stream(_) => self.generate_stream_binding(ty, idx, &module, &func.name), + _ => unreachable!("Expected future and stream"), + }; + map.insert(ty, binding); + } + + self.bindings = AsyncBinding(map.clone()); AsyncBinding(map) } @@ -281,6 +302,9 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte module: &str, func_name: &str, ) -> (String, String, String, String) { + self.ffi_imports.insert(ffi::MALLOC); + self.ffi_imports.insert(ffi::FREE); + let mut lift = Source::default(); let mut lower = Source::default(); @@ -301,18 +325,18 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte uwriteln!( lift, r#" -fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "[export]{module}" "[async-lower][future-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "[export]{module}" "[future-cancel-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[future-drop-readable-{index}]{func_name}" +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "{module}" "[async-lower][future-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[future-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[future-drop-readable-{index}]{func_name}" "#, ); uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[future-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "[export]{module}" "[future-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[future-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[future-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[future-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[async-lower][future-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" "# ); @@ -381,7 +405,14 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ drop() result.unwrap() }}, - drop + drop, + take_handle: fn () {{ + if dropped {{ + panic() + }} + dropped = true + future_handle + }} }} }} "# @@ -482,7 +513,7 @@ fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ uwriteln!( lift, r#" -fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[stream-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[async-lower][stream-read-{index}]{func_name}" fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[stream-cancel-read-{index}]{func_name}" fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[stream-drop-readable-{index}]{func_name}" "#, @@ -490,10 +521,10 @@ fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[stream-drop-r uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[stream-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "[export]{module}" "[stream-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[stream-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[stream-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[stream-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[async-lower][stream-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[stream-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop-writable-{index}]{func_name}" "# ); @@ -512,25 +543,26 @@ fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[stre lift, r#" fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ - let mut closed = false + let mut user_closed = false + let mut handle_dropped = false + let mut ended = false let mut reading = 0 async fn close() {{ - if !closed && reading > 0 {{ - {async_qualifier}suspend_for_stream_read( + if user_closed {{ + return + }} + user_closed = true + if !handle_dropped && reading > 0 {{ + let _ = {async_qualifier}suspend_for_stream_read( stream_handle, wasmLift{camel_name}{index}CancelRead(stream_handle) - ) catch {{ _ => () }} + ) catch {{ _ => (0, false) }} }} - if !closed {{ - closed = true + if !handle_dropped {{ + handle_dropped = true wasmLift{camel_name}{index}DropReadable(stream_handle) }} - }} - {async_qualifier}StreamR::{{ - read: fn (count : Int) {{ - if closed {{ - return None - }}"# + }}"# ); if let Some(inner_ty) = inner_type { @@ -542,6 +574,14 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ uwriteln!( lift, r#" + {async_qualifier}StreamR::{{ + read: fn (count : Int) {{ + if user_closed || ended {{ + return None + }} + if count <= 0 {{ + return Some([]) + }} let ptr = mbt_ffi_malloc(count * {elem_size}) reading += 1 let (progress, end) = {{ @@ -553,10 +593,14 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ }} if progress == 0 {{ mbt_ffi_free(ptr) - if end {{ close(); return None }} - return Some([]) + ended = true + if !handle_dropped {{ + handle_dropped = true + wasmLift{camel_name}{index}DropReadable(stream_handle) + }} + return None }} - let result = []"# + let items = []"# ); // Generate code to lift each element from memory @@ -569,21 +613,35 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ &inner_ty, ); uwriteln!(lift, "{}", lift_bindgen.src); - uwriteln!(lift, " result.push({operand})"); + uwriteln!(lift, " items.push({operand})"); uwriteln!(lift, " }}"); uwriteln!( lift, r#" mbt_ffi_free(ptr) - if end {{ close() }} - Some(result[:])"# + if end {{ + ended = true + if !handle_dropped {{ + handle_dropped = true + wasmLift{camel_name}{index}DropReadable(stream_handle) + }} + }} + Some(items[:])"# ); } else { // Unit type stream uwriteln!( lift, r#" + {async_qualifier}StreamR::{{ + read: fn (count : Int) {{ + if user_closed || ended {{ + return None + }} + if count <= 0 {{ + return Some(FixedArray::make(0, ())[:]) + }} reading += 1 let (progress, end) = {{ defer {{ reading -= 1 }} @@ -592,9 +650,22 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ wasmLift{camel_name}{index}Read(stream_handle, 0, count), ) }} - if progress == 0 && end {{ close(); return None }} + if progress == 0 {{ + ended = true + if !handle_dropped {{ + handle_dropped = true + wasmLift{camel_name}{index}DropReadable(stream_handle) + }} + return None + }} let result = FixedArray::make(progress, ()) - if end {{ close() }} + if end {{ + ended = true + if !handle_dropped {{ + handle_dropped = true + wasmLift{camel_name}{index}DropReadable(stream_handle) + }} + }} Some(result[:])"# ); } @@ -603,7 +674,15 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ lift, r#" }}, - close + close, + take_handle: fn () {{ + if user_closed || handle_dropped || reading > 0 {{ + panic() + }} + user_closed = true + handle_dropped = true + stream_handle + }} }} }}"# ); @@ -616,11 +695,13 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ let handles = wasmLower{camel_name}{index}New() let readable = (handles & 0xFFFFFFFF).to_int() let writable = (handles >> 32).to_int() + let mut closed = false let stream_w = {async_qualifier}StreamW::{{ write: async fn (data : ArrayView[_]) {{ if data.length() == 0 {{ return 0 - }}"# + }} + "# ); if let Some(inner_ty) = inner_type { @@ -652,11 +733,19 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ uwriteln!( lower, r#" - let (progress, _) = {async_qualifier}suspend_for_stream_write( - writable, - wasmLower{camel_name}{index}Write(writable, ptr, data.length()), - ) - mbt_ffi_free(ptr) + let mut progress = 0 + let mut dropped = false + while progress == 0 && !dropped {{ + let (p, d) = {async_qualifier}suspend_for_stream_write( + writable, + wasmLower{camel_name}{index}Write(writable, ptr, data.length()), + ) + progress = p + dropped = d + }} + if dropped {{ + panic() + }} progress"# ); } else { @@ -664,10 +753,19 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ uwriteln!( lower, r#" - let (progress, _) = {async_qualifier}suspend_for_stream_write( - writable, - wasmLower{camel_name}{index}Write(writable, 0, data.length()), - ) + let mut progress = 0 + let mut dropped = false + while progress == 0 && !dropped {{ + let (p, d) = {async_qualifier}suspend_for_stream_write( + writable, + wasmLower{camel_name}{index}Write(writable, 0, data.length()), + ) + progress = p + dropped = d + }} + if dropped {{ + panic() + }} progress"# ); } @@ -677,6 +775,10 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ r#" }}, close: async fn () {{ + if closed {{ + return + }} + closed = true wasmLower{camel_name}{index}DropWritable(writable) }} }} diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 38ae32f65..120e3bb2f 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::Result; use core::panic; -use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use heck::{ToShoutySnakeCase, ToUpperCamelCase}; use std::{ collections::{HashMap, HashSet}, fmt::Write, @@ -11,6 +11,7 @@ use wit_bindgen_core::{ AsyncFilterSet, Direction, Files, InterfaceGenerator as CoreInterfaceGenerator, Ns, Source, WorldGenerator, abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}, + dealias, uwrite, uwriteln, wit_parser::{ Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Handle, Int, @@ -113,11 +114,6 @@ impl InterfaceFragment { } } -enum PayloadFor { - Future, - Stream, -} - #[derive(Default)] pub struct MoonBit { opts: Opts, @@ -598,6 +594,22 @@ struct InterfaceGenerator<'a> { } impl InterfaceGenerator<'_> { + fn emit_async_bindings(&mut self) { + if self.bindings.0.is_empty() { + return; + } + + let mut emitted = HashSet::::new(); + for (_, (lifted_name, lift, lowered_name, lower)) in self.bindings.0.iter() { + if emitted.insert(lifted_name.clone()) { + uwriteln!(&mut self.ffi, "{}", lift); + } + if emitted.insert(lowered_name.clone()) { + uwriteln!(&mut self.ffi, "{}", lower); + } + } + } + fn finish(self) -> InterfaceFragment { InterfaceFragment { src: self.src, @@ -680,6 +692,7 @@ impl InterfaceGenerator<'_> { }} "# ); + self.emit_async_bindings(); return; } @@ -947,6 +960,10 @@ impl InterfaceGenerator<'_> { .insert(export_name, (func_name.clone(), export)); } + if async_ { + self.emit_async_bindings(); + } + // If post return is needed, generate it if abi::guest_export_needs_post_return(self.resolve, func) { let params = sig @@ -1027,13 +1044,7 @@ impl InterfaceGenerator<'_> { // Compute result type first (needed for taskgroup parameter type) let result_type = match &sig.result_type { None => "Unit".into(), - Some(ty) => match direction { - Direction::Export => self - .world_gen - .pkg_resolver - .type_name_for_lowering(self.name, ty), - Direction::Import => self.world_gen.pkg_resolver.type_name(self.name, ty), - }, + Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), }; let mut params = sig @@ -1058,6 +1069,43 @@ impl InterfaceGenerator<'_> { result_type ) } + + fn contains_future_or_stream(&self, ty: &Type) -> bool { + match ty { + Type::Id(id) => match &self.resolve.types[*id].kind { + TypeDefKind::Type(inner) => self.contains_future_or_stream(inner), + TypeDefKind::Future(_) | TypeDefKind::Stream(_) => true, + TypeDefKind::List(inner) | TypeDefKind::Option(inner) => { + self.contains_future_or_stream(inner) + } + TypeDefKind::Result(result) => { + result + .ok + .as_ref() + .is_some_and(|t| self.contains_future_or_stream(t)) + || result + .err + .as_ref() + .is_some_and(|t| self.contains_future_or_stream(t)) + } + TypeDefKind::Tuple(tuple) => tuple + .types + .iter() + .any(|t| self.contains_future_or_stream(t)), + TypeDefKind::Record(record) => record + .fields + .iter() + .any(|f| self.contains_future_or_stream(&f.ty)), + TypeDefKind::Variant(variant) => variant + .cases + .iter() + .filter_map(|c| c.ty.as_ref()) + .any(|t| self.contains_future_or_stream(t)), + _ => false, + }, + _ => false, + } + } } impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { @@ -1083,11 +1131,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .collect::>() .join("; "); + let has_future_or_stream = record + .fields + .iter() + .any(|f| self.contains_future_or_stream(&f.ty)); let mut deriviation: Vec<_> = Vec::new(); - if self.derive_opts.derive_show { + if self.derive_opts.derive_show && !has_future_or_stream { deriviation.push("Show") } - if self.derive_opts.derive_eq { + if self.derive_opts.derive_eq && !has_future_or_stream { deriviation.push("Eq") } @@ -1368,11 +1420,16 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .collect::>() .join("\n "); + let has_future_or_stream = variant + .cases + .iter() + .filter_map(|c| c.ty.as_ref()) + .any(|ty| self.contains_future_or_stream(ty)); let mut deriviation: Vec<_> = Vec::new(); - if self.derive_opts.derive_show { + if self.derive_opts.derive_show && !has_future_or_stream { deriviation.push("Show") } - if self.derive_opts.derive_eq { + if self.derive_opts.derive_eq && !has_future_or_stream { deriviation.push("Eq") } let declaration = if self.derive_opts.derive_error && name.contains("Error") { @@ -1743,13 +1800,6 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .type_name(self.interface_gen.name, ty) } - fn resolve_type_name_for_lowering(&mut self, ty: &Type) -> String { - self.interface_gen - .world_gen - .pkg_resolver - .type_name_for_lowering(self.interface_gen.name, ty) - } - fn use_ffi(&mut self, str: &'static str) { self.interface_gen.ffi_imports.insert(str); } @@ -2411,14 +2461,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { - // For exports, use lowering type names (OutFuture/OutStream) - // For imports, use lifting type names (FutureR/StreamR) - let ty = match self.direction { - Direction::Export => { - format!("({})", self.resolve_type_name_for_lowering(&ty)) - } - Direction::Import => format!("({})", self.resolve_type_name(&ty)), - }; + let ty = format!("({})", self.resolve_type_name(&ty)); let result = self.locals.tmp("result"); if func.result.is_some() { results.push(result.clone()); @@ -2722,35 +2765,31 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLift { ty, .. } => { - self.use_ffi(ffi::MALLOC); - self.use_ffi(ffi::FREE); - let (lifted_func_name, lift, _, _) = self.interface_gen.bindings.0.get(ty).unwrap(); + let ty = dealias(self.interface_gen.resolve, *ty); + let (lifted_func_name, _lift, _, _) = + self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; results.push(format!("{lifted_func_name}({op})")); - uwriteln!(self.interface_gen.ffi, "{}", lift); } Instruction::FutureLower { ty, .. } => { - let (_, _, lowered_func_name, lower) = - self.interface_gen.bindings.0.get(ty).unwrap(); let op = &operands[0]; - results.push(format!("{lowered_func_name}({op})")); - uwriteln!(self.interface_gen.ffi, "{}", lower); + let _ = ty; + results.push(format!("({op}).take_handle()")); } Instruction::StreamLower { ty, .. } => { - let (_, _, lowered_func_name, lower) = - self.interface_gen.bindings.0.get(ty).unwrap(); let op = &operands[0]; - results.push(format!("{lowered_func_name}({op})")); - uwriteln!(self.interface_gen.ffi, "{}", lower); + let _ = ty; + results.push(format!("({op}).take_handle()")); } Instruction::StreamLift { ty, .. } => { - let (lifted_func_name, lift, _, _) = self.interface_gen.bindings.0.get(ty).unwrap(); + let ty = dealias(self.interface_gen.resolve, *ty); + let (lifted_func_name, _lift, _, _) = + self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; results.push(format!("{lifted_func_name}({op})")); - uwriteln!(self.interface_gen.ffi, "{}", lift); } Instruction::ErrorContextLower { .. } | Instruction::ErrorContextLift { .. } => todo!(), diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index 592ed06cc..a972f9b68 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -288,15 +288,6 @@ impl PkgResolver { } } - /// Generate type name for export result types (lowering context). - /// Uses OutFuture/OutStream instead of FutureR/StreamR. - pub(crate) fn type_name_for_lowering(&mut self, this: &str, ty: &Type) -> String { - let name = self.type_name(this, ty); - // Convert FutureR to OutFuture and StreamR to OutStream for export result types - name.replace("FutureR[", "OutFuture[") - .replace("StreamR[", "OutStream[") - } - pub(crate) fn non_empty_type<'a>(&self, ty: Option<&'a Type>) -> Option<&'a Type> { if let Some(ty) = ty { let id = match ty { diff --git a/tests/runtime-async/async/future-close-after-coming-back/test.mbt b/tests/runtime-async/async/future-close-after-coming-back/test.mbt index b70aea1af..183d6f9ae 100644 --- a/tests/runtime-async/async/future-close-after-coming-back/test.mbt +++ b/tests/runtime-async/async/future-close-after-coming-back/test.mbt @@ -1,11 +1,10 @@ //@ [lang] //@ path = 'gen/interface/a/b/theTest/stub.mbt' -pub async fn f(param : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[Unit] { - let out : @async.OutFuture[Unit] = { ..Default::default() } +pub async fn f(param : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> @async.FutureR[Unit] { + let out : @async.OutFuture[Unit] = @async.OutFuture::new() task_group.spawn_bg(async fn() { out.put(param.get()) }) - out + wasmLiftF0(wasmLowerF0(out)) } - diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt index 2975f6def..63ea7662c 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.mbt +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -2,15 +2,15 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[UInt] { - let out : @async.OutFuture[UInt] = { value: None, coroutine: None } +pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.FutureR[UInt] { + let out : @async.OutFuture[UInt] = @async.OutFuture::new() out.put(value) - out + wasmLiftCreateFutureWithValue0(wasmLowerCreateFutureWithValue0(out)) } ///| -pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.OutFuture[Unit] { - let out : @async.OutFuture[Unit] = { value: None, coroutine: None } +pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.FutureR[Unit] { + let out : @async.OutFuture[Unit] = @async.OutFuture::new() out.put(()) - out + wasmLiftCreateUnitFuture0(wasmLowerCreateUnitFuture0(out)) } diff --git a/tests/runtime-async/async/moonbit-stream-write/runner.rs b/tests/runtime-async/async/moonbit-stream-write/runner.rs index 9e4c1d48b..29542a7bb 100644 --- a/tests/runtime-async/async/moonbit-stream-write/runner.rs +++ b/tests/runtime-async/async/moonbit-stream-write/runner.rs @@ -14,7 +14,7 @@ impl Guest for Component { let mut total = 0u32; let mut count = 0u32; loop { - let buf = vec![0u32; 10]; + let buf = Vec::::with_capacity(10); let (result, values) = rx.read(buf).await; match result { StreamResult::Complete(n) if n > 0 => { @@ -35,7 +35,7 @@ impl Guest for Component { let mut rx = create_unit_stream(5).await; let mut count = 0u32; loop { - let buf = vec![(); 10]; + let buf = Vec::<()>::with_capacity(10); let (result, _values) = rx.read(buf).await; match result { StreamResult::Complete(n) if n > 0 => { diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt index 489c345ef..f8179b3ee 100644 --- a/tests/runtime-async/async/moonbit-stream-write/test.mbt +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -2,31 +2,29 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[UInt] { - let out : @async.OutStream[UInt] = { stream: None, coroutine: None } - // The stream writing will happen when get_stream is called - // Spawn a background task to write values using the task group +pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.StreamR[UInt] { + let out : @async.OutStream[UInt] = @async.OutStream::new() task_group.spawn_bg(async fn() { - let stream = out.get_stream() + let stream_w = out.get_stream() for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[UInt] = [i.reinterpret_as_uint()] - let _ = (stream.write)(arr[:]) + let _ = (stream_w.write)(arr[:]) } - (stream.close)() + (stream_w.close)() }) - out + wasmLiftCreateStreamWithValues0(wasmLowerCreateStreamWithValues0(out)) } ///| -pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.OutStream[Unit] { - let out : @async.OutStream[Unit] = { stream: None, coroutine: None } +pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.StreamR[Unit] { + let out : @async.OutStream[Unit] = @async.OutStream::new() task_group.spawn_bg(async fn() { - let stream = out.get_stream() + let stream_w = out.get_stream() for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[Unit] = [()] - let _ = (stream.write)(arr[:]) + let _ = (stream_w.write)(arr[:]) } - (stream.close)() + (stream_w.close)() }) - out + wasmLiftCreateUnitStream0(wasmLowerCreateUnitStream0(out)) } diff --git a/tests/runtime-async/async/simple-stream-payload/test.mbt b/tests/runtime-async/async/simple-stream-payload/test.mbt index 4b52029f6..6b6f67b7f 100644 --- a/tests/runtime-async/async/simple-stream-payload/test.mbt +++ b/tests/runtime-async/async/simple-stream-payload/test.mbt @@ -2,7 +2,8 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' pub async fn read_stream(x : @async.StreamR[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { - while (x.read)(1) is Some(_) { - - } + guard (x.read)(1) is Some(_) + guard (x.read)(2) is Some(_) + guard (x.read)(2) is Some(_) + (x.close)() } diff --git a/tests/runtime-async/async/simple-stream/test.mbt b/tests/runtime-async/async/simple-stream/test.mbt index 7d541d80d..b52ea264f 100644 --- a/tests/runtime-async/async/simple-stream/test.mbt +++ b/tests/runtime-async/async/simple-stream/test.mbt @@ -2,8 +2,7 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' pub async fn read_stream(x : @async.StreamR[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { - while (x.read)(1) is Some(_) { - - } + guard (x.read)(1) is Some(_) + guard (x.read)(2) is Some(_) + (x.close)() } - From c28f008fa1d9515e54492483836d354be6e159be Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 14:49:43 +0800 Subject: [PATCH 37/61] moonbit: adapt async params and fixed-length list codegen --- crates/moonbit/src/async_support.rs | 22 +++++++++++++++------- crates/moonbit/src/pkg.rs | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index f5fe026d5..0ce250930 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -5,10 +5,10 @@ use std::{ use heck::ToUpperCamelCase; use wit_bindgen_core::{ - Direction, Files, Source, - abi::{self, WasmSignature, deallocate_lists_in_types, lift_from_memory}, + abi::{self, deallocate_lists_in_types, lift_from_memory, WasmSignature}, dealias, uwriteln, - wit_parser::{Function, Type, TypeDefKind, TypeId}, + Direction, Files, Source, + wit_parser::{Function, Param, Type, TypeDefKind, TypeId}, }; use crate::pkg::ToMoonBitIdent; @@ -117,11 +117,19 @@ impl<'a> InterfaceGenerator<'a> { let param_names = func .params .iter() - .map(|(name, _)| name.to_moonbit_ident()) + .map(|Param { name, .. }| name.to_moonbit_ident()) + .collect::>(); + let param_types = func + .params + .iter() + .map(|Param { ty, .. }| *ty) .collect::>(); - let param_types = func.params.iter().map(|(_, ty)| *ty).collect::>(); - let mut bindgen = - FunctionBindgen::new(self, param_names.into_boxed_slice(), Direction::Import, true); + let mut bindgen = FunctionBindgen::new( + self, + param_names.into_boxed_slice(), + Direction::Import, + true, + ); let mut lowered_params = Vec::new(); let params_ptr = if wasm_sig.indirect_params { diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index a972f9b68..538c35cae 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -205,7 +205,7 @@ impl PkgResolver { } _ => format!("Array[{}]", self.type_name(this, &ty)), }, - TypeDefKind::FixedLengthList(ty, _size) => { + TypeDefKind::FixedLengthList(ty, _) => { format!("FixedArray[{}]", self.type_name(this, &ty)) } TypeDefKind::Tuple(tuple) => { From 4ce061d4fd839f6bf05a38105635475482f475fb Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 3 Feb 2026 15:55:32 +0800 Subject: [PATCH 38/61] moonbit: rework async future/stream bindings --- crates/moonbit/src/async/async_abi.mbt | 4 +- crates/moonbit/src/async/task_group.mbt | 20 + crates/moonbit/src/async/trait.mbt | 213 ++++++++--- crates/moonbit/src/async_support.rs | 347 ++++++++---------- crates/moonbit/src/lib.rs | 170 +++++---- crates/moonbit/src/pkg.rs | 9 +- .../async/future-cancel-read/test.mbt | 8 +- .../async/future-cancel-write/test.mbt | 4 +- .../future-close-after-coming-back/test.mbt | 8 +- .../async/moonbit-future-write/test.mbt | 12 +- .../async/moonbit-stream-write/test.mbt | 22 +- .../async/simple-future/test.mbt | 4 +- .../async/simple-stream-payload/test.mbt | 24 +- .../async/simple-stream/test.mbt | 10 +- 14 files changed, 476 insertions(+), 379 deletions(-) diff --git a/crates/moonbit/src/async/async_abi.mbt b/crates/moonbit/src/async/async_abi.mbt index 0dd25fccf..1d6c7ddc3 100644 --- a/crates/moonbit/src/async/async_abi.mbt +++ b/crates/moonbit/src/async/async_abi.mbt @@ -228,10 +228,10 @@ fn CallbackCode::decode(int : Int) -> CallbackCode { fn _yield() -> Bool = "$root" "[cancellable][yield]" ///| -fn _backpressure_inc() = "$root" "[backpressure-inc]" +pub fn backpressure_inc() = "$root" "[backpressure-inc]" ///| -fn _backpressure_dec() = "$root" "[backpressure-dec]" +pub fn backpressure_dec() = "$root" "[backpressure-dec]" ///| fn subtask_cancel(id : Int) -> Int = "$root" "[subtask-cancel]" diff --git a/crates/moonbit/src/async/task_group.mbt b/crates/moonbit/src/async/task_group.mbt index 03384b941..7dd51edcc 100644 --- a/crates/moonbit/src/async/task_group.mbt +++ b/crates/moonbit/src/async/task_group.mbt @@ -40,6 +40,21 @@ struct TaskGroup[X] { group_defer : Array[async () -> Unit] } +///| +/// Spawner for the current task group (if any). +/// This is set by `with_task_group` so that generated bindings can spawn +/// background tasks without explicitly threading `TaskGroup` everywhere. +let current_spawner : Ref[((async () -> Unit) -> Unit)?] = { val: None } + +///| +/// Spawn a background task into the current task group. +/// +/// This will fail if called outside `with_task_group`. +pub fn spawn_bg_current(f : async () -> Unit) -> Unit { + guard current_spawner.val is Some(spawn) + spawn(f) +} + ///| #deprecated("this error is no longer emitted") pub suberror AlreadyTerminated derive(Show) @@ -190,6 +205,11 @@ pub async fn[X] with_task_group(f : async (TaskGroup[X]) -> X) -> X { result: None, group_defer: [], } + let prev = current_spawner.val + current_spawner.val = Some(fn(child) { tg.spawn_bg(child) }) + defer { + current_spawner.val = prev + } tg.spawn_bg(fn() { let value = f(tg) if tg.result is None { diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index 2a4d02df8..1a70a2950 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -1,5 +1,6 @@ ///| pub(all) struct FutureR[X] { + handle : Int get : async () -> X drop : async () -> Unit take_handle : () -> Int @@ -22,100 +23,218 @@ pub fn[X] FutureR::take_handle(self : FutureR[X]) -> Int { ///| pub(all) struct StreamR[X] { + handle : Int read : async (Int) -> ArrayView[X]? close : async () -> Unit take_handle : () -> Int } +///| +pub async fn[X] StreamR::read(self : StreamR[X], count : Int) -> ArrayView[X]? { + (self.read)(count) +} + +///| +pub async fn[X] StreamR::close(self : StreamR[X]) -> Unit { + (self.close)() +} + ///| pub fn[X] StreamR::take_handle(self : StreamR[X]) -> Int { (self.take_handle)() } ///| -pub(all) struct StreamW[X] { +pub(all) struct Sink[X] { write : async (ArrayView[X]) -> Int close : async () -> Unit } ///| -pub(all) struct OutStreamInner[X] { - mut stream : StreamW[X]? - mut coroutine : Coroutine? +pub async fn[X] Sink::write(self : Sink[X], data : ArrayView[X]) -> Int { + (self.write)(data) +} + +///| +pub async fn[X] Sink::close(self : Sink[X]) -> Unit { + (self.close)() +} + +///| +let next_id : Ref[Int] = { val: 0 } + +///| +fn fresh_id() -> Int { + let id = next_id.val + next_id.val = id + 1 + id +} + +///| +pub(all) struct FutureOutInner[X] { + mut producer : (async () -> X)? +} + +///| +pub(all) struct FutureOut[X] { + id : Int + inner : Ref[FutureOutInner[X]] +} + +///| +pub fn[X] FutureOut::new(producer : async () -> X) -> FutureOut[X] { + { id: fresh_id(), inner: { val: { producer: Some(producer) } } } } ///| -pub(all) struct OutStream[X] { - inner : Ref[OutStreamInner[X]] +pub fn[X] FutureOut::take_producer(self : FutureOut[X]) -> (async () -> X) { + guard self.inner.val.producer is Some(p) + self.inner.val.producer = None + p } ///| -pub fn[X] OutStream::new() -> OutStream[X] { - { inner: { val: { stream: None, coroutine: None } } } +pub(all) enum Future[X] { + Incoming(FutureR[X]) + Outgoing(FutureOut[X]) } ///| -pub async fn[X] OutStream::get_stream(self : OutStream[X]) -> StreamW[X] { - if self.inner.val.stream is Some(s) { - return s - } else { - guard self.inner.val.coroutine is None - self.inner.val.coroutine = Some(current_coroutine()) - suspend() catch { - e => { - if self.inner.val.stream is Some(s) { - (s.close)() - } - raise e +pub impl[X] Eq for Future[X] with equal(self, other) -> Bool { + match self { + Incoming(f) => + match other { + Incoming(g) => f.handle == g.handle + Outgoing(_) => false } + Outgoing(f) => + match other { + Incoming(_) => false + Outgoing(g) => f.id == g.id + } + } +} + +///| +pub impl[X] Show for Future[X] with output(self, logger) { + match self { + Incoming(f) => { + logger.write_string("Future::Incoming(") + logger.write_string(f.handle.to_string()) + logger.write_string(")") + } + Outgoing(f) => { + logger.write_string("Future::Outgoing(") + logger.write_string(f.id.to_string()) + logger.write_string(")") } - self.inner.val.stream.unwrap() } } ///| -pub fn[X] OutStream::put_stream( - self : OutStream[X], - stream : StreamW[X], -) -> Unit { - self.inner.val.stream = Some(stream) - if self.inner.val.coroutine is Some(coro) { - coro.wake() +pub fn[X] Future::ready(value : X) -> Future[X] { + Future::Outgoing(FutureOut::new(async fn() { value })) +} + +///| +pub fn[X] Future::from(producer : async () -> X) -> Future[X] { + Future::Outgoing(FutureOut::new(producer)) +} + +///| +pub async fn[X] Future::get(self : Future[X]) -> X { + match self { + Incoming(f) => f.get() + Outgoing(f) => (f.take_producer())() + } +} + +///| +pub async fn[X] Future::drop(self : Future[X]) -> Unit { + match self { + Incoming(f) => f.drop() + Outgoing(_) => () } } ///| -pub(all) struct OutFutureInner[X] { - mut value : X? - mut coroutine : Coroutine? +pub(all) struct StreamOutInner[X] { + mut producer : (async (Sink[X]) -> Unit)? +} + +///| +pub(all) struct StreamOut[X] { + id : Int + inner : Ref[StreamOutInner[X]] +} + +///| +pub fn[X] StreamOut::new(producer : async (Sink[X]) -> Unit) -> StreamOut[X] { + { id: fresh_id(), inner: { val: { producer: Some(producer) } } } } ///| -pub(all) struct OutFuture[X] { - inner : Ref[OutFutureInner[X]] +pub fn[X] StreamOut::take_producer(self : StreamOut[X]) -> (async (Sink[X]) -> Unit) { + guard self.inner.val.producer is Some(p) + self.inner.val.producer = None + p +} + +///| +pub(all) enum Stream[X] { + Incoming(StreamR[X]) + Outgoing(StreamOut[X]) +} + +///| +pub impl[X] Eq for Stream[X] with equal(self, other) -> Bool { + match self { + Incoming(s) => + match other { + Incoming(t) => s.handle == t.handle + Outgoing(_) => false + } + Outgoing(s) => + match other { + Incoming(_) => false + Outgoing(t) => s.id == t.id + } + } +} + +///| +pub impl[X] Show for Stream[X] with output(self, logger) { + match self { + Incoming(s) => { + logger.write_string("Stream::Incoming(") + logger.write_string(s.handle.to_string()) + logger.write_string(")") + } + Outgoing(s) => { + logger.write_string("Stream::Outgoing(") + logger.write_string(s.id.to_string()) + logger.write_string(")") + } + } } ///| -pub fn[X] OutFuture::new() -> OutFuture[X] { - { inner: { val: { value: None, coroutine: None } } } +pub fn[X] Stream::from(producer : async (Sink[X]) -> Unit) -> Stream[X] { + Stream::Outgoing(StreamOut::new(producer)) } ///| -pub async fn[X] OutFuture::get(self : OutFuture[X]) -> X { - if self.inner.val.value is Some(v) { - return v - } else { - guard self.inner.val.coroutine is None - self.inner.val.coroutine = Some(current_coroutine()) - suspend() - self.inner.val.value.unwrap() +pub async fn[X] Stream::read(self : Stream[X], count : Int) -> ArrayView[X]? { + match self { + Incoming(s) => s.read(count) + Outgoing(_) => panic() } } ///| -pub fn[X] OutFuture::put(self : OutFuture[X], value : X) -> Unit { - self.inner.val.value = Some(value) - if self.inner.val.coroutine is Some(coro) { - coro.wake() +pub async fn[X] Stream::close(self : Stream[X]) -> Unit { + match self { + Incoming(s) => s.close() + Outgoing(_) => () } } diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 0ce250930..31ec092b4 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -8,7 +8,9 @@ use wit_bindgen_core::{ abi::{self, deallocate_lists_in_types, lift_from_memory, WasmSignature}, dealias, uwriteln, Direction, Files, Source, - wit_parser::{Function, Param, Type, TypeDefKind, TypeId}, + wit_parser::{ + Function, LiftLowerAbi, ManglingAndAbi, Param, Type, TypeDefKind, TypeId, WasmImport, + }, }; use crate::pkg::ToMoonBitIdent; @@ -250,57 +252,35 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte bindgen.src } - /// Generate the async bindings for this function - pub(crate) fn generate_async_binding(&mut self, func: &Function) -> AsyncBinding { - let mut map = HashMap::new(); + /// Generate the async bindings for this function. + /// + /// Note that these bindings may be referenced while generating other async + /// bindings (e.g. `future }>`), so this method + /// populates `self.bindings` incrementally. + pub(crate) fn generate_async_binding(&mut self, func: &Function) { + self.bindings.0.clear(); let futures_and_streams = func.find_futures_and_streams(self.resolve); - - let module_prefix = match self.direction { - Direction::Import => "", - Direction::Export => "[export]", - }; - let base_module = self - .interface - .map(|name| self.resolve.name_world_key(name)) - .unwrap_or_else(|| "$root".to_string()); - let module = format!("{module_prefix}{base_module}"); - - // Select a single index per (dealiased) type to avoid emitting - // multiple bindings for the same future/stream helper. - let mut selected = Vec::<(TypeId, usize)>::new(); - let camel_name = func.name.to_upper_camel_case(); - for (idx, ty) in futures_and_streams.iter().enumerate() { - let ty = dealias(self.resolve, *ty); - if map.contains_key(&ty) { - continue; - } - map.insert( - ty, - ( - format!("wasmLift{camel_name}{idx}"), - String::new(), - format!("wasmLower{camel_name}{idx}"), - String::new(), - ), - ); - selected.push((ty, idx)); - } - - // Make helper names available while generating bodies so nested - // payloads can reference them. - self.bindings = AsyncBinding(map.clone()); - - for (ty, idx) in selected { - let binding = match self.resolve.types[ty].kind { - TypeDefKind::Future(_) => self.generate_future_binding(ty, idx, &module, &func.name), - TypeDefKind::Stream(_) => self.generate_stream_binding(ty, idx, &module, &func.name), + let (module, func_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::Func { + interface: self.interface, + func, + }, + ); + for (idx, type_) in futures_and_streams.iter().enumerate() { + let ty = dealias(self.resolve, *type_); + match self.resolve.types[ty].kind { + TypeDefKind::Future(_) => { + let binding = self.generate_future_binding(ty, idx, &module, &func_name); + self.bindings.0.insert(ty, binding); + } + TypeDefKind::Stream(_) => { + let binding = self.generate_stream_binding(ty, idx, &module, &func_name); + self.bindings.0.insert(ty, binding); + } _ => unreachable!("Expected future and stream"), - }; - map.insert(ty, binding); + } } - - self.bindings = AsyncBinding(map.clone()); - AsyncBinding(map) } pub(crate) fn generate_future_binding( @@ -310,9 +290,6 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte module: &str, func_name: &str, ) -> (String, String, String, String) { - self.ffi_imports.insert(ffi::MALLOC); - self.ffi_imports.insert(ffi::FREE); - let mut lift = Source::default(); let mut lower = Source::default(); @@ -327,29 +304,32 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte .world_gen .pkg_resolver .type_name(self.name, &Type::Id(ty)); - let lowered = lifted.replace("FutureR", "OutFuture"); // write intrinsics uwriteln!( lift, r#" -fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "{module}" "[async-lower][future-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[future-cancel-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[future-drop-readable-{index}]{func_name}" +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "[export]{module}" "[async-lower][future-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "[export]{module}" "[future-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[future-drop-readable-{index}]{func_name}" "#, ); uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[future-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[async-lower][future-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[future-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "[export]{module}" "[future-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[future-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[future-drop-writable-{index}]{func_name}" "# ); // generate function - let size = self.world_gen.sizes.size(&Type::Id(ty)).size_wasm32(); + let size = if let TypeDefKind::Future(Some(inner_ty)) = self.resolve.types[ty].kind { + self.world_gen.sizes.size(&inner_ty).size_wasm32() + } else { + 0 + }; uwriteln!( lift, r#" @@ -372,7 +352,8 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ wasmLift{camel_name}{index}DropReadable(future_handle) }} }} - {async_qualifier}FutureR::{{ + {async_qualifier}Future::Incoming({async_qualifier}FutureR::{{ + handle: future_handle, get: fn () {{ if result is Some(r) {{ return r @@ -415,13 +396,13 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ }}, drop, take_handle: fn () {{ - if dropped {{ + if dropped || reading > 0 {{ panic() }} dropped = true future_handle }} - }} + }}) }} "# ); @@ -436,12 +417,18 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ - let handles = wasmLower{camel_name}{index}New() - let readable = (handles & 0xFFFFFFFF).to_int() - let writable = (handles >> 32).to_int() - let _ = {async_qualifier}spawn(async fn() {{ - defer wasmLower{camel_name}{index}DropWritable(writable)"# +fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ + match future {{ + {async_qualifier}Future::Incoming(f) => f.take_handle() + {async_qualifier}Future::Outgoing(f) => {{ + let handles = wasmLower{camel_name}{index}New() + let readable = (handles & 0xFFFFFFFF).to_int() + let writable = (handles >> 32).to_int() + let producer = f.take_producer() + {async_qualifier}backpressure_inc() + {async_qualifier}spawn_bg_current(async fn() {{ + defer {async_qualifier}backpressure_dec() + defer wasmLower{camel_name}{index}DropWritable(writable)"# ); if let Some(inner_ty) = inner_type { @@ -452,7 +439,7 @@ fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ uwriteln!( lower, r#" - let value = out_future.get() + let value = producer() let ptr = mbt_ffi_malloc({size}) defer mbt_ffi_free(ptr)"# ); @@ -467,23 +454,25 @@ fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ uwriteln!( lower, r#" - let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, ptr))"# + let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, ptr)) catch {{ _ => false }}"# ); } else { // Unit type - no value to write, just complete the future uwriteln!( lower, r#" - let _ = out_future.get() - let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, 0))"# + let _ = producer() + let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, 0)) catch {{ _ => false }}"# ); } uwriteln!( lower, r#" - }}) - readable + }}) + readable + }} + }} }}"# ); ( @@ -515,24 +504,23 @@ fn wasmLower{camel_name}{index}(out_future : {lowered}) -> Int {{ .world_gen .pkg_resolver .type_name(self.name, &Type::Id(ty)); - let lowered = lifted.replace("StreamR", "OutStream"); // write intrinsics uwriteln!( lift, r#" -fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[async-lower][stream-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[stream-cancel-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[stream-drop-readable-{index}]{func_name}" +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int, len : Int) -> Int = "[export]{module}" "[async-lower][stream-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "[export]{module}" "[stream-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[stream-drop-readable-{index}]{func_name}" "#, ); uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[stream-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[async-lower][stream-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[stream-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[stream-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "[export]{module}" "[stream-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[stream-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[stream-drop-writable-{index}]{func_name}" "# ); @@ -551,26 +539,26 @@ fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop- lift, r#" fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ - let mut user_closed = false - let mut handle_dropped = false - let mut ended = false + let mut closed = false let mut reading = 0 async fn close() {{ - if user_closed {{ - return - }} - user_closed = true - if !handle_dropped && reading > 0 {{ + if !closed && reading > 0 {{ let _ = {async_qualifier}suspend_for_stream_read( stream_handle, wasmLift{camel_name}{index}CancelRead(stream_handle) ) catch {{ _ => (0, false) }} }} - if !handle_dropped {{ - handle_dropped = true + if !closed {{ + closed = true wasmLift{camel_name}{index}DropReadable(stream_handle) }} - }}"# + }} + {async_qualifier}Stream::Incoming({async_qualifier}StreamR::{{ + handle: stream_handle, + read: fn (count : Int) {{ + if closed {{ + return None + }}"# ); if let Some(inner_ty) = inner_type { @@ -582,14 +570,6 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ uwriteln!( lift, r#" - {async_qualifier}StreamR::{{ - read: fn (count : Int) {{ - if user_closed || ended {{ - return None - }} - if count <= 0 {{ - return Some([]) - }} let ptr = mbt_ffi_malloc(count * {elem_size}) reading += 1 let (progress, end) = {{ @@ -601,12 +581,8 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ }} if progress == 0 {{ mbt_ffi_free(ptr) - ended = true - if !handle_dropped {{ - handle_dropped = true - wasmLift{camel_name}{index}DropReadable(stream_handle) - }} - return None + if end {{ close(); return None }} + return Some([]) }} let items = []"# ); @@ -628,13 +604,7 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ lift, r#" mbt_ffi_free(ptr) - if end {{ - ended = true - if !handle_dropped {{ - handle_dropped = true - wasmLift{camel_name}{index}DropReadable(stream_handle) - }} - }} + if end {{ close() }} Some(items[:])"# ); } else { @@ -642,14 +612,6 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ uwriteln!( lift, r#" - {async_qualifier}StreamR::{{ - read: fn (count : Int) {{ - if user_closed || ended {{ - return None - }} - if count <= 0 {{ - return Some(FixedArray::make(0, ())[:]) - }} reading += 1 let (progress, end) = {{ defer {{ reading -= 1 }} @@ -658,22 +620,9 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ wasmLift{camel_name}{index}Read(stream_handle, 0, count), ) }} - if progress == 0 {{ - ended = true - if !handle_dropped {{ - handle_dropped = true - wasmLift{camel_name}{index}DropReadable(stream_handle) - }} - return None - }} + if progress == 0 && end {{ close(); return None }} let result = FixedArray::make(progress, ()) - if end {{ - ended = true - if !handle_dropped {{ - handle_dropped = true - wasmLift{camel_name}{index}DropReadable(stream_handle) - }} - }} + if end {{ close() }} Some(result[:])"# ); } @@ -684,32 +633,42 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ }}, close, take_handle: fn () {{ - if user_closed || handle_dropped || reading > 0 {{ + if closed || reading > 0 {{ panic() }} - user_closed = true - handle_dropped = true + closed = true stream_handle }} - }} + }}) }}"# ); - // Generate lower function (OutStream to handle) + // Generate lower function (Stream to handle) uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ - let handles = wasmLower{camel_name}{index}New() - let readable = (handles & 0xFFFFFFFF).to_int() - let writable = (handles >> 32).to_int() - let mut closed = false - let stream_w = {async_qualifier}StreamW::{{ - write: async fn (data : ArrayView[_]) {{ - if data.length() == 0 {{ - return 0 - }} - "# +fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ + match stream {{ + {async_qualifier}Stream::Incoming(s) => s.take_handle() + {async_qualifier}Stream::Outgoing(s) => {{ + let handles = wasmLower{camel_name}{index}New() + let readable = (handles & 0xFFFFFFFF).to_int() + let writable = (handles >> 32).to_int() + let producer = s.take_producer() + {async_qualifier}backpressure_inc() + let _ = {async_qualifier}spawn_bg_current(async fn() {{ + defer {async_qualifier}backpressure_dec() + let mut closed = false + defer {{ + if !closed {{ + wasmLower{camel_name}{index}DropWritable(writable) + }} + }} + let sink = {async_qualifier}Sink::{{ + write: async fn (data : ArrayView[_]) {{ + if closed || data.length() == 0 {{ + return 0 + }}"# ); if let Some(inner_ty) = inner_type { @@ -722,10 +681,11 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ uwriteln!( lower, r#" - let ptr = mbt_ffi_malloc(data.length() * {elem_size}) - for i = 0; i < data.length(); i = i + 1 {{ - let elem_ptr = ptr + i * {elem_size} - let elem : {elem_type} = data[i]"# + let ptr = mbt_ffi_malloc(data.length() * {elem_size}) + defer mbt_ffi_free(ptr) + for i = 0; i < data.length(); i = i + 1 {{ + let elem_ptr = ptr + i * {elem_size} + let elem : {elem_type} = data[i]"# ); abi::lower_to_memory( @@ -736,62 +696,55 @@ fn wasmLower{camel_name}{index}(out_stream : {lowered}) -> Int {{ &inner_ty, ); uwriteln!(lower, "{}", lower_bindgen.src); - uwriteln!(lower, " }}"); + uwriteln!(lower, " }}"); uwriteln!( lower, r#" - let mut progress = 0 - let mut dropped = false - while progress == 0 && !dropped {{ - let (p, d) = {async_qualifier}suspend_for_stream_write( - writable, - wasmLower{camel_name}{index}Write(writable, ptr, data.length()), - ) - progress = p - dropped = d - }} - if dropped {{ - panic() - }} - progress"# + let (progress, dropped) = {async_qualifier}suspend_for_stream_write( + writable, + wasmLower{camel_name}{index}Write(writable, ptr, data.length()), + ) catch {{ _ => (0, true) }} + if dropped {{ + closed = true + wasmLower{camel_name}{index}DropWritable(writable) + }} + progress"# ); } else { // Unit type stream uwriteln!( lower, r#" - let mut progress = 0 - let mut dropped = false - while progress == 0 && !dropped {{ - let (p, d) = {async_qualifier}suspend_for_stream_write( - writable, - wasmLower{camel_name}{index}Write(writable, 0, data.length()), - ) - progress = p - dropped = d - }} - if dropped {{ - panic() - }} - progress"# + let (progress, dropped) = {async_qualifier}suspend_for_stream_write( + writable, + wasmLower{camel_name}{index}Write(writable, 0, data.length()), + ) catch {{ _ => (0, true) }} + if dropped {{ + closed = true + wasmLower{camel_name}{index}DropWritable(writable) + }} + progress"# ); } uwriteln!( lower, r#" - }}, - close: async fn () {{ - if closed {{ - return - }} - closed = true - wasmLower{camel_name}{index}DropWritable(writable) + }}, + close: async fn () {{ + if !closed {{ + closed = true + wasmLower{camel_name}{index}DropWritable(writable) + }} + }} + }} + producer(sink) + sink.close() + }}) + readable }} }} - out_stream.put_stream(stream_w) - readable }}"# ); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 120e3bb2f..a0565f31a 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -114,6 +114,11 @@ impl InterfaceFragment { } } +enum PayloadFor { + Future, + Stream, +} + #[derive(Default)] pub struct MoonBit { opts: Opts, @@ -155,6 +160,7 @@ impl MoonBit { derive_opts, interface, bindings: AsyncBinding(HashMap::new()), + async_bindings_emitted: HashSet::new(), } } @@ -591,25 +597,13 @@ struct InterfaceGenerator<'a> { // Generated lift and lower bindings: AsyncBinding, + + // Avoid re-emitting the same async helper functions multiple times in the + // same `ffi.mbt`. + async_bindings_emitted: HashSet, } impl InterfaceGenerator<'_> { - fn emit_async_bindings(&mut self) { - if self.bindings.0.is_empty() { - return; - } - - let mut emitted = HashSet::::new(); - for (_, (lifted_name, lift, lowered_name, lower)) in self.bindings.0.iter() { - if emitted.insert(lifted_name.clone()) { - uwriteln!(&mut self.ffi, "{}", lift); - } - if emitted.insert(lowered_name.clone()) { - uwriteln!(&mut self.ffi, "{}", lower); - } - } - } - fn finish(self) -> InterfaceFragment { InterfaceFragment { src: self.src, @@ -629,7 +623,7 @@ impl InterfaceGenerator<'_> { || !func.find_futures_and_streams(self.resolve).is_empty(); if async_ { self.world_gen.async_support.mark_async(); - self.bindings = self.generate_async_binding(func); + self.generate_async_binding(func); } let ffi_import_name = format!("wasmImport{}", func.name.to_upper_camel_case()); @@ -692,7 +686,6 @@ impl InterfaceGenerator<'_> { }} "# ); - self.emit_async_bindings(); return; } @@ -750,7 +743,7 @@ impl InterfaceGenerator<'_> { || !func.find_futures_and_streams(self.resolve).is_empty(); if async_ { self.world_gen.async_support.mark_async(); - self.bindings = self.generate_async_binding(func); + self.generate_async_binding(func); } // Generate stub for user @@ -960,10 +953,6 @@ impl InterfaceGenerator<'_> { .insert(export_name, (func_name.clone(), export)); } - if async_ { - self.emit_async_bindings(); - } - // If post return is needed, generate it if abi::guest_export_needs_post_return(self.resolve, func) { let params = sig @@ -1044,7 +1033,13 @@ impl InterfaceGenerator<'_> { // Compute result type first (needed for taskgroup parameter type) let result_type = match &sig.result_type { None => "Unit".into(), - Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), + Some(ty) => match direction { + Direction::Export => self + .world_gen + .pkg_resolver + .type_name_for_lowering(self.name, ty), + Direction::Import => self.world_gen.pkg_resolver.type_name(self.name, ty), + }, }; let mut params = sig @@ -1069,43 +1064,6 @@ impl InterfaceGenerator<'_> { result_type ) } - - fn contains_future_or_stream(&self, ty: &Type) -> bool { - match ty { - Type::Id(id) => match &self.resolve.types[*id].kind { - TypeDefKind::Type(inner) => self.contains_future_or_stream(inner), - TypeDefKind::Future(_) | TypeDefKind::Stream(_) => true, - TypeDefKind::List(inner) | TypeDefKind::Option(inner) => { - self.contains_future_or_stream(inner) - } - TypeDefKind::Result(result) => { - result - .ok - .as_ref() - .is_some_and(|t| self.contains_future_or_stream(t)) - || result - .err - .as_ref() - .is_some_and(|t| self.contains_future_or_stream(t)) - } - TypeDefKind::Tuple(tuple) => tuple - .types - .iter() - .any(|t| self.contains_future_or_stream(t)), - TypeDefKind::Record(record) => record - .fields - .iter() - .any(|f| self.contains_future_or_stream(&f.ty)), - TypeDefKind::Variant(variant) => variant - .cases - .iter() - .filter_map(|c| c.ty.as_ref()) - .any(|t| self.contains_future_or_stream(t)), - _ => false, - }, - _ => false, - } - } } impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { @@ -1131,15 +1089,11 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .collect::>() .join("; "); - let has_future_or_stream = record - .fields - .iter() - .any(|f| self.contains_future_or_stream(&f.ty)); let mut deriviation: Vec<_> = Vec::new(); - if self.derive_opts.derive_show && !has_future_or_stream { + if self.derive_opts.derive_show { deriviation.push("Show") } - if self.derive_opts.derive_eq && !has_future_or_stream { + if self.derive_opts.derive_eq { deriviation.push("Eq") } @@ -1420,16 +1374,11 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .collect::>() .join("\n "); - let has_future_or_stream = variant - .cases - .iter() - .filter_map(|c| c.ty.as_ref()) - .any(|ty| self.contains_future_or_stream(ty)); let mut deriviation: Vec<_> = Vec::new(); - if self.derive_opts.derive_show && !has_future_or_stream { + if self.derive_opts.derive_show { deriviation.push("Show") } - if self.derive_opts.derive_eq && !has_future_or_stream { + if self.derive_opts.derive_eq { deriviation.push("Eq") } let declaration = if self.derive_opts.derive_error && name.contains("Error") { @@ -1800,6 +1749,13 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .type_name(self.interface_gen.name, ty) } + fn resolve_type_name_for_lowering(&mut self, ty: &Type) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .type_name_for_lowering(self.interface_gen.name, ty) + } + fn use_ffi(&mut self, str: &'static str) { self.interface_gen.ffi_imports.insert(str); } @@ -2461,7 +2417,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { - let ty = format!("({})", self.resolve_type_name(&ty)); + // For exports, use lowering type names (OutFuture/OutStream) + // For imports, use lifting type names (FutureR/StreamR) + let ty = match self.direction { + Direction::Export => { + format!("({})", self.resolve_type_name_for_lowering(&ty)) + } + Direction::Import => format!("({})", self.resolve_type_name(&ty)), + }; let result = self.locals.tmp("result"); if func.result.is_some() { results.push(result.clone()); @@ -2765,32 +2728,67 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLift { ty, .. } => { + self.use_ffi(ffi::MALLOC); + self.use_ffi(ffi::FREE); let ty = dealias(self.interface_gen.resolve, *ty); - let (lifted_func_name, _lift, _, _) = + let (lifted_func_name, lift, _, _) = self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; results.push(format!("{lifted_func_name}({op})")); + if self + .interface_gen + .async_bindings_emitted + .insert(lifted_func_name.clone()) + { + uwriteln!(self.interface_gen.ffi, "{}", lift); + } } Instruction::FutureLower { ty, .. } => { + let ty = dealias(self.interface_gen.resolve, *ty); + let (_, _, lowered_func_name, lower) = + self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; - let _ = ty; - results.push(format!("({op}).take_handle()")); + results.push(format!("{lowered_func_name}({op})")); + if self + .interface_gen + .async_bindings_emitted + .insert(lowered_func_name.clone()) + { + uwriteln!(self.interface_gen.ffi, "{}", lower); + } } Instruction::StreamLower { ty, .. } => { + let ty = dealias(self.interface_gen.resolve, *ty); + let (_, _, lowered_func_name, lower) = + self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; - let _ = ty; - results.push(format!("({op}).take_handle()")); + results.push(format!("{lowered_func_name}({op})")); + if self + .interface_gen + .async_bindings_emitted + .insert(lowered_func_name.clone()) + { + uwriteln!(self.interface_gen.ffi, "{}", lower); + } } Instruction::StreamLift { ty, .. } => { let ty = dealias(self.interface_gen.resolve, *ty); - let (lifted_func_name, _lift, _, _) = + let (lifted_func_name, lift, _, _) = self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; results.push(format!("{lifted_func_name}({op})")); + if self + .interface_gen + .async_bindings_emitted + .insert(lifted_func_name.clone()) + { + uwriteln!(self.interface_gen.ffi, "{}", lift); + } } + Instruction::ErrorContextLower { .. } | Instruction::ErrorContextLift { .. } => todo!(), Instruction::DropHandle { ty } => { @@ -2798,11 +2796,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { match ty { Type::Id(id) => match &self.interface_gen.resolve.types[*id].kind { TypeDefKind::Handle(Handle::Own(_)) => { - let constructor = self - .interface_gen - .world_gen - .pkg_resolver - .type_constructor(self.interface_gen.name, ty); + let constructor = + self.interface_gen.world_gen.pkg_resolver.type_constructor( + self.interface_gen.name, + ty, + ); uwriteln!(self.src, "let _ = {constructor}::drop({op});"); } TypeDefKind::Future(_) | TypeDefKind::Stream(_) => { diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index 538c35cae..e508734f3 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -253,7 +253,7 @@ impl PkgResolver { TypeDefKind::Future(ty) => { let qualifier = self.qualify_package(this, ASYNC_DIR); format!( - "{}FutureR[{}]", + "{}Future[{}]", qualifier, ty.as_ref() .map(|t| self.type_name(this, t)) @@ -264,7 +264,7 @@ impl PkgResolver { TypeDefKind::Stream(ty) => { let qualifier = self.qualify_package(this, ASYNC_DIR); format!( - "{}StreamR[{}]", + "{}Stream[{}]", qualifier, ty.as_ref() .map(|t| self.type_name(this, t)) @@ -288,6 +288,11 @@ impl PkgResolver { } } + /// Generate type name for export result types (lowering context). + pub(crate) fn type_name_for_lowering(&mut self, this: &str, ty: &Type) -> String { + self.type_name(this, ty) + } + pub(crate) fn non_empty_type<'a>(&self, ty: Option<&'a Type>) -> Option<&'a Type> { if let Some(ty) = ty { let id = match ty { diff --git a/tests/runtime-async/async/future-cancel-read/test.mbt b/tests/runtime-async/async/future-cancel-read/test.mbt index d0e9f1224..77f1bcbf4 100644 --- a/tests/runtime-async/async/future-cancel-read/test.mbt +++ b/tests/runtime-async/async/future-cancel-read/test.mbt @@ -2,12 +2,12 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn cancel_before_read(x : @async.FutureR[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn cancel_before_read(x : @async.Future[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } ///| -pub async fn cancel_after_read(x : @async.FutureR[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn cancel_after_read(x : @async.Future[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { task_group.spawn_bg(async fn() { x.drop() }) let _ = x.get() catch { @async.FutureReadError::Cancelled => return @@ -18,8 +18,8 @@ pub async fn cancel_after_read(x : @async.FutureR[UInt], task_group : @async.Tas ///| pub async fn start_read_then_cancel( - data : @async.FutureR[UInt], - signal : @async.FutureR[Unit], + data : @async.Future[UInt], + signal : @async.Future[Unit], task_group : @async.TaskGroup[Unit], ) -> Unit { task_group.spawn_bg(async fn() { diff --git a/tests/runtime-async/async/future-cancel-write/test.mbt b/tests/runtime-async/async/future-cancel-write/test.mbt index 3df11646a..aa9bba90d 100644 --- a/tests/runtime-async/async/future-cancel-write/test.mbt +++ b/tests/runtime-async/async/future-cancel-write/test.mbt @@ -2,11 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn take_then_drop(x : @async.FutureR[String], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn take_then_drop(x : @async.Future[String], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } ///| -pub async fn read_and_drop(x : @async.FutureR[String], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn read_and_drop(x : @async.Future[String], task_group : @async.TaskGroup[Unit]) -> Unit { let _ = x.get() } diff --git a/tests/runtime-async/async/future-close-after-coming-back/test.mbt b/tests/runtime-async/async/future-close-after-coming-back/test.mbt index 183d6f9ae..7ecbc03ee 100644 --- a/tests/runtime-async/async/future-close-after-coming-back/test.mbt +++ b/tests/runtime-async/async/future-close-after-coming-back/test.mbt @@ -1,10 +1,6 @@ //@ [lang] //@ path = 'gen/interface/a/b/theTest/stub.mbt' -pub async fn f(param : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> @async.FutureR[Unit] { - let out : @async.OutFuture[Unit] = @async.OutFuture::new() - task_group.spawn_bg(async fn() { - out.put(param.get()) - }) - wasmLiftF0(wasmLowerF0(out)) +pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { + @async.Future::from(async fn() { param.get() }) } diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt index 63ea7662c..1989f05c9 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.mbt +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -2,15 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.FutureR[UInt] { - let out : @async.OutFuture[UInt] = @async.OutFuture::new() - out.put(value) - wasmLiftCreateFutureWithValue0(wasmLowerCreateFutureWithValue0(out)) +pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.Future[UInt] { + @async.Future::ready(value) } ///| -pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.FutureR[Unit] { - let out : @async.OutFuture[Unit] = @async.OutFuture::new() - out.put(()) - wasmLiftCreateUnitFuture0(wasmLowerCreateUnitFuture0(out)) +pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { + @async.Future::ready(()) } diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt index f8179b3ee..7e6a5ad6e 100644 --- a/tests/runtime-async/async/moonbit-stream-write/test.mbt +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -2,29 +2,23 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.StreamR[UInt] { - let out : @async.OutStream[UInt] = @async.OutStream::new() - task_group.spawn_bg(async fn() { - let stream_w = out.get_stream() +pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.Stream[UInt] { + @async.Stream::from(async fn(sink : @async.Sink[UInt]) { for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[UInt] = [i.reinterpret_as_uint()] - let _ = (stream_w.write)(arr[:]) + let _ = sink.write(arr[:]) } - (stream_w.close)() + sink.close() }) - wasmLiftCreateStreamWithValues0(wasmLowerCreateStreamWithValues0(out)) } ///| -pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.StreamR[Unit] { - let out : @async.OutStream[Unit] = @async.OutStream::new() - task_group.spawn_bg(async fn() { - let stream_w = out.get_stream() +pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.Stream[Unit] { + @async.Stream::from(async fn(sink : @async.Sink[Unit]) { for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[Unit] = [()] - let _ = (stream_w.write)(arr[:]) + let _ = sink.write(arr[:]) } - (stream_w.close)() + sink.close() }) - wasmLiftCreateUnitStream0(wasmLowerCreateUnitStream0(out)) } diff --git a/tests/runtime-async/async/simple-future/test.mbt b/tests/runtime-async/async/simple-future/test.mbt index 91b0fc8df..4b2dccb91 100644 --- a/tests/runtime-async/async/simple-future/test.mbt +++ b/tests/runtime-async/async/simple-future/test.mbt @@ -2,11 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn read_future(x : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn read_future(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.get() } ///| -pub async fn drop_future(x : @async.FutureR[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn drop_future(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } diff --git a/tests/runtime-async/async/simple-stream-payload/test.mbt b/tests/runtime-async/async/simple-stream-payload/test.mbt index 6b6f67b7f..ec40896ad 100644 --- a/tests/runtime-async/async/simple-stream-payload/test.mbt +++ b/tests/runtime-async/async/simple-stream-payload/test.mbt @@ -1,9 +1,23 @@ //@ [lang] //@ path = 'gen/interface/my/test_/i/stub.mbt' -pub async fn read_stream(x : @async.StreamR[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { - guard (x.read)(1) is Some(_) - guard (x.read)(2) is Some(_) - guard (x.read)(2) is Some(_) - (x.close)() +pub async fn read_stream(x : @async.Stream[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { + guard x.read(1) is Some(a) + guard a.length() == 1 + guard a[0] == (0).to_byte() + + guard x.read(2) is Some(b) + guard b.length() == 2 + guard b[0] == (1).to_byte() + guard b[1] == (2).to_byte() + + guard x.read(1) is Some(c) + guard c.length() == 1 + guard c[0] == (3).to_byte() + + guard x.read(1) is Some(d) + guard d.length() == 1 + guard d[0] == (4).to_byte() + + x.close() } diff --git a/tests/runtime-async/async/simple-stream/test.mbt b/tests/runtime-async/async/simple-stream/test.mbt index b52ea264f..325a4ab23 100644 --- a/tests/runtime-async/async/simple-stream/test.mbt +++ b/tests/runtime-async/async/simple-stream/test.mbt @@ -1,8 +1,10 @@ //@ [lang] //@ path = 'gen/interface/my/test_/i/stub.mbt' -pub async fn read_stream(x : @async.StreamR[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { - guard (x.read)(1) is Some(_) - guard (x.read)(2) is Some(_) - (x.close)() +pub async fn read_stream(x : @async.Stream[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { + guard x.read(1) is Some(a) + guard a.length() == 1 + guard x.read(2) is Some(b) + guard b.length() == 2 + x.close() } From 7b8ba7af1bf6b522cb82ec4ae0e6737083d16859 Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 3 Feb 2026 15:56:20 +0800 Subject: [PATCH 39/61] moonbit: add crate README with test commands --- crates/moonbit/README.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/moonbit/README.md b/crates/moonbit/README.md index d0107a4c2..ca478f9f4 100644 --- a/crates/moonbit/README.md +++ b/crates/moonbit/README.md @@ -1,15 +1,22 @@ -# `wit-bindgen` MoonBit Bindings Generator +# `wit-bindgen-moonbit` -This crate implements the MoonBit guest bindings generator for `wit-bindgen`. +MoonBit language bindings generator for WIT and the Component Model. -## Testing +## Usage + +Generate bindings via the `moonbit` subcommand: -The repository’s `wit-bindgen test` subcommand is the preferred way to run MoonBit -codegen/runtime tests. See `tests/README.md` for full details. +```bash +wit-bindgen moonbit [OPTIONS] +``` -### Codegen +See `wit-bindgen help moonbit` for available options. -```sh +## Testing + +From the repo root, run the MoonBit codegen tests: + +```bash cargo run test \ --languages rust,moonbit \ --artifacts target/artifacts \ @@ -17,12 +24,11 @@ cargo run test \ tests/codegen ``` -### Runtime (async) +And the async runtime tests (requires an async component-model runner): -```sh +```bash cargo run test --languages rust,moonbit tests/runtime-async \ --artifacts target/artifacts \ --rust-wit-bindgen-path ./crates/guest-rust \ --runner "wasmtime -W component-model-async" ``` - From 881ef8725c71fe2acbdf98b8851501397f9cba79 Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 5 Feb 2026 09:51:44 +0800 Subject: [PATCH 40/61] moonbit: add local future/stream batches --- crates/moonbit/README.md | 34 ++++ crates/moonbit/src/async/trait.mbt | 267 +++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) diff --git a/crates/moonbit/README.md b/crates/moonbit/README.md index ca478f9f4..a5603264e 100644 --- a/crates/moonbit/README.md +++ b/crates/moonbit/README.md @@ -12,6 +12,40 @@ wit-bindgen moonbit [OPTIONS] See `wit-bindgen help moonbit` for available options. +## Local async usage + +For pure MoonBit code (no FFI), you can create local future/stream pairs. + +Future + Promise: + +```mbt +let (f, p) = @async.Future::new[Int]() +@async.spawn(async fn() { p.write(42) }) +let value = f.get() +``` + +Stream + Sink (batched reads/writes): + +```mbt +let (s, sink) = @async.Stream::new[Byte]() +@async.spawn(async fn() { + let chunk : Array[Byte] = [1, 2, 3, 4] + let _ = sink.write(chunk[:]) + sink.close() +}) +let chunk = s.read(4096) +match chunk { + None => () + Some(bytes) => { + let _ = bytes.length() + } +} +``` + +`Stream::read(count)` returns up to `count` elements; `Sink::write` accepts +`ArrayView[T]` so byte streams can batch data efficiently. `Stream::new` +accepts an optional `capacity` (<= 0 means unbounded). + ## Testing From the repo root, run the MoonBit codegen tests: diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index 1a70a2950..3e1a19a47 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -60,6 +60,22 @@ pub async fn[X] Sink::close(self : Sink[X]) -> Unit { (self.close)() } +///| +pub struct Promise[X] { + write : async (X) -> Unit + close : async () -> Unit +} + +///| +pub async fn[X] Promise::write(self : Promise[X], value : X) -> Unit { + (self.write)(value) +} + +///| +pub async fn[X] Promise::close(self : Promise[X]) -> Unit { + (self.close)() +} + ///| let next_id : Ref[Int] = { val: 0 } @@ -70,6 +86,205 @@ fn fresh_id() -> Int { id } +///| +pub suberror FutureCancelled derive(Show) + +///| +struct Waiter { + mut coro : Coroutine? +} + +///| +fn wake_one(waiters : @deque.Deque[Waiter]) -> Unit { + while waiters.pop_front() is Some(waiter) { + match waiter.coro { + None => () + Some(coro) => { + coro.wake() + return + } + } + } +} + +///| +fn wake_all(waiters : @deque.Deque[Waiter]) -> Unit { + while waiters.pop_front() is Some(waiter) { + match waiter.coro { + None => () + Some(coro) => coro.wake() + } + } +} + +///| +async fn wait_on(waiters : @deque.Deque[Waiter]) -> Unit { + let waiter = { coro: Some(current_coroutine()) } + waiters.push_back(waiter) + try suspend() catch { + err => { + waiter.coro = None + raise err + } + } +} + +///| +struct LocalFutureState[X] { + mut value : X? + mut closed : Bool + waiters : @deque.Deque[Waiter] +} + +///| +fn[X] local_future_close(state : Ref[LocalFutureState[X]]) -> Unit { + if state.val.closed { + return + } + state.val.closed = true + wake_all(state.val.waiters) +} + +///| +fn[X] local_future_write(state : Ref[LocalFutureState[X]], value : X) -> Unit { + if state.val.closed { + panic() + } + if state.val.value is Some(_) { + panic() + } + state.val.value = Some(value) + wake_all(state.val.waiters) +} + +///| +async fn[X] local_future_get(state : Ref[LocalFutureState[X]]) -> X { + for { + match state.val.value { + Some(value) => return value + None => + if state.val.closed { + raise FutureCancelled::FutureCancelled + } else { + wait_on(state.val.waiters) + } + } + } +} + +///| +struct LocalStreamState[X] { + capacity : Int + chunks : @deque.Deque[Array[X]] + mut buffered : Int + mut closed : Bool + mut head : Array[X]? + mut head_pos : Int + readers : @deque.Deque[Waiter] + writers : @deque.Deque[Waiter] +} + +///| +fn[X] local_stream_close(state : Ref[LocalStreamState[X]]) -> Unit { + if state.val.closed { + return + } + state.val.closed = true + wake_all(state.val.readers) + wake_all(state.val.writers) +} + +///| +async fn[X] local_stream_write( + state : Ref[LocalStreamState[X]], + data : ArrayView[X], +) -> Int { + if data.length() == 0 || state.val.closed { + return 0 + } + let total = data.length() + let mut offset = 0 + for { + if offset >= total { + return total + } + if state.val.closed { + return offset + } + let capacity = state.val.capacity + let available = if capacity <= 0 { + total - offset + } else { + capacity - state.val.buffered + } + if available <= 0 { + wait_on(state.val.writers) + continue + } + let take = if available < (total - offset) { + available + } else { + total - offset + } + let chunk : Array[X] = [] + for i = 0; i < take; i = i + 1 { + chunk.push(data[offset + i]) + } + state.val.chunks.push_back(chunk) + state.val.buffered = state.val.buffered + take + offset = offset + take + wake_one(state.val.readers) + } + total +} + +///| +async fn[X] local_stream_read( + state : Ref[LocalStreamState[X]], + count : Int, +) -> ArrayView[X]? { + if count <= 0 { + return Some([]) + } + let result : Array[X] = [] + for { + if result.length() >= count { + return Some(result[:]) + } + match state.val.head { + Some(head) => { + let available = head.length() - state.val.head_pos + let needed = count - result.length() + let take = if needed < available { needed } else { available } + for i = 0; i < take; i = i + 1 { + result.push(head[state.val.head_pos + i]) + } + state.val.head_pos = state.val.head_pos + take + state.val.buffered = state.val.buffered - take + if state.val.head_pos >= head.length() { + state.val.head = None + state.val.head_pos = 0 + } + wake_one(state.val.writers) + continue + } + None => () + } + if state.val.chunks.pop_front() is Some(chunk) { + state.val.head = Some(chunk) + state.val.head_pos = 0 + continue + } + if result.length() > 0 { + return Some(result[:]) + } + if state.val.closed { + return None + } + wait_on(state.val.readers) + } +} + ///| pub(all) struct FutureOutInner[X] { mut producer : (async () -> X)? @@ -136,6 +351,29 @@ pub fn[X] Future::ready(value : X) -> Future[X] { Future::Outgoing(FutureOut::new(async fn() { value })) } +///| +pub fn[X] Future::new() -> (Future[X], Promise[X]) { + let state : Ref[LocalFutureState[X]] = { + val: { + value: None, + closed: false, + waiters: @deque.new(), + }, + } + let handle = fresh_id() + let future = Future::Incoming(FutureR::{ + handle, + get: async fn() { local_future_get(state) }, + drop: async fn() { local_future_close(state) }, + take_handle: fn() { panic() }, + }) + let promise = Promise::{ + write: async fn(value : X) { local_future_write(state, value) }, + close: async fn() { local_future_close(state) }, + } + (future, promise) +} + ///| pub fn[X] Future::from(producer : async () -> X) -> Future[X] { Future::Outgoing(FutureOut::new(producer)) @@ -223,6 +461,35 @@ pub fn[X] Stream::from(producer : async (Sink[X]) -> Unit) -> Stream[X] { Stream::Outgoing(StreamOut::new(producer)) } +///| +pub fn[X] Stream::new(capacity? : Int = 0) -> (Stream[X], Sink[X]) { + let cap = if capacity < 0 { 0 } else { capacity } + let state : Ref[LocalStreamState[X]] = { + val: { + capacity: cap, + chunks: @deque.new(), + buffered: 0, + closed: false, + head: None, + head_pos: 0, + readers: @deque.new(), + writers: @deque.new(), + }, + } + let handle = fresh_id() + let stream = Stream::Incoming(StreamR::{ + handle, + read: async fn(count : Int) { local_stream_read(state, count) }, + close: async fn() { local_stream_close(state) }, + take_handle: fn() { panic() }, + }) + let sink = Sink::{ + write: async fn(data : ArrayView[X]) { local_stream_write(state, data) }, + close: async fn() { local_stream_close(state) }, + } + (stream, sink) +} + ///| pub async fn[X] Stream::read(self : Stream[X], count : Int) -> ArrayView[X]? { match self { From f603390bb5239ac8922382d39ef429c01195a7c5 Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 5 Feb 2026 18:00:02 +0800 Subject: [PATCH 41/61] moonbit: use Cancelled for local future --- crates/moonbit/src/async/trait.mbt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index 3e1a19a47..ce8988793 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -87,8 +87,6 @@ fn fresh_id() -> Int { } ///| -pub suberror FutureCancelled derive(Show) - ///| struct Waiter { mut coro : Coroutine? @@ -164,7 +162,7 @@ async fn[X] local_future_get(state : Ref[LocalFutureState[X]]) -> X { Some(value) => return value None => if state.val.closed { - raise FutureCancelled::FutureCancelled + raise Cancelled::Cancelled } else { wait_on(state.val.waiters) } From e14b1b326519953ca6455dbaa6eaa7d1090f5b69 Mon Sep 17 00:00:00 2001 From: yezihang Date: Wed, 11 Feb 2026 11:19:28 +0800 Subject: [PATCH 42/61] moonbit: align async intrinsic modules and async export wrappers --- crates/moonbit/src/async_support.rs | 28 +++---- crates/moonbit/src/lib.rs | 115 +++++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 17 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 31ec092b4..e2e5002b9 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -309,18 +309,18 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte uwriteln!( lift, r#" -fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "[export]{module}" "[async-lower][future-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "[export]{module}" "[future-cancel-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[future-drop-readable-{index}]{func_name}" +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int) -> Int = "{module}" "[async-lower][future-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[future-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[future-drop-readable-{index}]{func_name}" "#, ); uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[future-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "[export]{module}" "[future-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[future-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[future-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[future-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[future-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" "# ); @@ -509,18 +509,18 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ uwriteln!( lift, r#" -fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int, len : Int) -> Int = "[export]{module}" "[async-lower][stream-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "[export]{module}" "[stream-cancel-read-{index}]{func_name}" -fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "[export]{module}" "[stream-drop-readable-{index}]{func_name}" +fn wasmLift{camel_name}{index}Read(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[async-lower][stream-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}CancelRead(_ : Int) -> Int = "{module}" "[stream-cancel-read-{index}]{func_name}" +fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[stream-drop-readable-{index}]{func_name}" "#, ); uwriteln!( lower, r#" -fn wasmLower{camel_name}{index}New() -> UInt64 = "[export]{module}" "[stream-new-{index}]{func_name}" -fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "[export]{module}" "[stream-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "[export]{module}" "[stream-cancel-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "[export]{module}" "[stream-drop-writable-{index}]{func_name}" +fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[stream-new-{index}]{func_name}" +fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[stream-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[stream-cancel-write-{index}]{func_name}" +fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop-writable-{index}]{func_name}" "# ); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index a0565f31a..022d4c5ff 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -830,8 +830,8 @@ impl InterfaceGenerator<'_> { r#" #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ - {async_pkg}with_waitableset(fn() {{ - {async_pkg}with_task_group(fn(task_group) {{ + {async_pkg}with_waitableset(async fn() {{ + {async_pkg}with_task_group(async fn(task_group) {{ {cleanup_list} {src} }}) @@ -954,7 +954,7 @@ impl InterfaceGenerator<'_> { } // If post return is needed, generate it - if abi::guest_export_needs_post_return(self.resolve, func) { + if !async_ && abi::guest_export_needs_post_return(self.resolve, func) { let params = sig .results .iter() @@ -2375,6 +2375,115 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(array); } + Instruction::FixedLengthListLift { + element, + size, + id: _, + } => { + let mut lifted = Vec::with_capacity(*size as usize); + for operand in operands.drain(0..(*size as usize)) { + lifted.push(operand); + } + if lifted.is_empty() { + let ty = self.resolve_type_name(element); + results.push(format!("([] : FixedArray[{ty}])")); + } else { + results.push(format!("[{}]", lifted.join(", "))); + } + } + + Instruction::FixedLengthListLower { + element: _, + size, + id: _, + } => { + let op = &operands[0]; + for i in 0..(*size as usize) { + results.push(format!("({op})[{i}]")); + } + } + + Instruction::FixedLengthListLowerToMemory { + element, + size, + id: _, + } => { + let Block { + body, + results: block_results, + } = self.blocks.pop().unwrap(); + assert!(block_results.is_empty()); + + let op = &operands[0]; + let target = &operands[1]; + let ty = self.resolve_type_name(element); + let elem_size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); + + for i in 0..(*size as usize) { + uwrite!( + self.src, + " + {{ + let iter_elem : {ty} = ({op})[{i}] + let iter_base = ({target}) + ({i} * {elem_size}) + {body} + }} + ", + ); + } + } + + Instruction::FixedLengthListLiftFromMemory { + element, + size, + id: _, + } => { + let Block { + body, + results: block_results, + } = self.blocks.pop().unwrap(); + let address = &operands[0]; + let ty = self.resolve_type_name(element); + let elem_size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); + + let element_result = match &block_results[..] { + [result] => result, + _ => todo!("result count == {}", block_results.len()), + }; + + let mut lifted = Vec::with_capacity(*size as usize); + for i in 0..(*size as usize) { + let value = self.locals.tmp("fixed_elem"); + uwrite!( + self.src, + " + let {value} : {ty} = {{ + let iter_base = ({address}) + ({i} * {elem_size}) + {body} + {element_result} + }} + ", + ); + lifted.push(value); + } + + if lifted.is_empty() { + results.push(format!("([] : FixedArray[{ty}])")); + } else { + results.push(format!("[{}]", lifted.join(", "))); + } + } + Instruction::IterElem { .. } => results.push("iter_elem".into()), Instruction::IterBasePointer => results.push("iter_base".into()), From 9c38c5b8c41f1d7efccc80555452c50e45e63104 Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 12 Feb 2026 20:13:46 +0800 Subject: [PATCH 43/61] moonbit: fix runtime harness for new moon outputs and async export metadata --- crates/moonbit/src/async_support.rs | 18 +++++-- crates/test/src/moonbit.rs | 53 +++++++++++++------ .../async/simple-call-import/runner.mbt | 1 + .../simple-import-params-results/runner.mbt | 1 + 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index e2e5002b9..89b928544 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -14,7 +14,7 @@ use wit_bindgen_core::{ }; use crate::pkg::ToMoonBitIdent; -use crate::{FunctionBindgen, ffi, indent}; +use crate::{ffi, indent, FunctionBindgen}; use super::InterfaceGenerator; @@ -300,6 +300,11 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte .world_gen .pkg_resolver .qualify_package(self.name, ASYNC_DIR); + let module = if self.direction == Direction::Export && !module.starts_with("[export]") { + format!("[export]{module}") + } else { + module.to_string() + }; let lifted = self .world_gen .pkg_resolver @@ -500,6 +505,11 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ .world_gen .pkg_resolver .qualify_package(self.name, ASYNC_DIR); + let module = if self.direction == Direction::Export && !module.starts_with("[export]") { + format!("[export]{module}") + } else { + module.to_string() + }; let lifted = self .world_gen .pkg_resolver @@ -563,7 +573,8 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut lift_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import, true); + let mut lift_bindgen = + FunctionBindgen::new(self, Box::new([]), Direction::Import, true); lift_bindgen.use_ffi(ffi::MALLOC); lift_bindgen.use_ffi(ffi::FREE); @@ -674,7 +685,8 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); let elem_type = self.world_gen.pkg_resolver.type_name(self.name, &inner_ty); - let mut lower_bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export, true); + let mut lower_bindgen = + FunctionBindgen::new(self, Box::new([]), Direction::Export, true); lower_bindgen.use_ffi(ffi::MALLOC); lower_bindgen.use_ffi(ffi::FREE); diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index 8080bc734..8e66dbcf9 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -1,5 +1,5 @@ use crate::{LanguageMethods, Runner}; -use anyhow::bail; +use anyhow::{bail, Context}; use serde::Deserialize; use std::process::Command; @@ -60,24 +60,48 @@ impl LanguageMethods for MoonBit { } // Compile the MoonBit bindings to a wasm file - let manifest = compile.bindings_dir.join("moon.mod.json"); let mut cmd = Command::new("moon"); cmd.arg("build") + .arg("--target") + .arg("wasm") + .arg("--release") .arg("--no-strip") // for debugging - .arg("--manifest-path") - .arg(&manifest); + .current_dir(&compile.bindings_dir); runner.run_command(&mut cmd)?; - // Build the component - let artifact = compile - .bindings_dir - .join("target/wasm/release/build/gen/gen.wasm"); + // Build the component. MoonBit toolchains may use either `_build` or + // `target` output roots depending on version/configuration. + let artifact_candidates = [ + compile + .bindings_dir + .join("_build/wasm/release/build/gen/gen.wasm"), + compile + .bindings_dir + .join("target/wasm/release/build/gen/gen.wasm"), + compile + .bindings_dir + .join("_build/wasm/debug/build/gen/gen.wasm"), + compile + .bindings_dir + .join("target/wasm/debug/build/gen/gen.wasm"), + ]; + let artifact = artifact_candidates + .iter() + .find(|path| path.exists()) + .cloned() + .with_context(|| { + format!( + "failed to locate MoonBit output wasm, looked in: {:?}", + artifact_candidates + ) + })?; // Embed WIT files let manifest_dir = compile.component.path.parent().unwrap(); + let embedded = artifact.with_extension("embedded.wasm"); let mut cmd = Command::new("wasm-tools"); cmd.arg("component") .arg("embed") .args(["--encoding", "utf16"]) - .args(["-o", artifact.to_str().unwrap()]) + .args(["-o", embedded.to_str().unwrap()]) .args(["-w", &compile.component.bindgen.world]) .arg(manifest_dir) .arg(&artifact); @@ -87,7 +111,7 @@ impl LanguageMethods for MoonBit { cmd.arg("component") .arg("new") .args(["-o", compile.output.to_str().unwrap()]) - .arg(&artifact); + .arg(&embedded); runner.run_command(&mut cmd)?; Ok(()) } @@ -102,18 +126,15 @@ impl LanguageMethods for MoonBit { } fn verify(&self, runner: &Runner, verify: &crate::Verify) -> anyhow::Result<()> { - let manifest = verify.bindings_dir.join("moon.mod.json"); let mut cmd = Command::new("moon"); cmd.arg("check") .arg("--warn-list") - .arg("-28") - // .arg("--deny-warn") - .arg("--manifest-path") - .arg(&manifest); + .arg("-28") // avoid warning noise in generated bindings + .current_dir(&verify.bindings_dir); runner.run_command(&mut cmd)?; let mut cmd = Command::new("moon"); - cmd.arg("build").arg("--manifest-path").arg(&manifest); + cmd.arg("build").current_dir(&verify.bindings_dir); runner.run_command(&mut cmd)?; Ok(()) diff --git a/tests/runtime-async/async/simple-call-import/runner.mbt b/tests/runtime-async/async/simple-call-import/runner.mbt index 90cb85e6d..24f7c9960 100644 --- a/tests/runtime-async/async/simple-call-import/runner.mbt +++ b/tests/runtime-async/async/simple-call-import/runner.mbt @@ -1,3 +1,4 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' //@ [lang] //@ path = 'gen/world/runner/stub.mbt' //@ pkg_config = """{ "warn-list": "-44", "import": ["a/b/interface/a/b/i", "a/b/async"] }""" diff --git a/tests/runtime-async/async/simple-import-params-results/runner.mbt b/tests/runtime-async/async/simple-import-params-results/runner.mbt index cccd6bd67..9e5c46683 100644 --- a/tests/runtime-async/async/simple-import-params-results/runner.mbt +++ b/tests/runtime-async/async/simple-import-params-results/runner.mbt @@ -1,3 +1,4 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' //@ [lang] //@ path = 'gen/world/runner/stub.mbt' //@ pkg_config = """{ "warn-list": "-44", "import": ["a/b/interface/a/b/i", "a/b/async"] }""" From 0688bf3a13015c53bab00565e7d0ec41035cf382 Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 12 Feb 2026 20:28:20 +0800 Subject: [PATCH 44/61] moonbit: add runner.mbt coverage for async runtime cases --- .../async/future-cancel-read/runner.mbt | 10 +++++++ .../async/future-cancel-write/runner.mbt | 10 +++++++ .../future-close-after-coming-back/runner.mbt | 10 +++++++ .../async/moonbit-future-write/runner.mbt | 13 ++++++++++ .../async/moonbit-stream-write/runner.mbt | 26 +++++++++++++++++++ .../async/simple-future/runner.mbt | 10 +++++++ .../async/simple-stream-payload/runner.mbt | 20 ++++++++++++++ .../async/simple-stream/runner.mbt | 16 ++++++++++++ 8 files changed, 115 insertions(+) create mode 100644 tests/runtime-async/async/future-cancel-read/runner.mbt create mode 100644 tests/runtime-async/async/future-cancel-write/runner.mbt create mode 100644 tests/runtime-async/async/future-close-after-coming-back/runner.mbt create mode 100644 tests/runtime-async/async/moonbit-future-write/runner.mbt create mode 100644 tests/runtime-async/async/moonbit-stream-write/runner.mbt create mode 100644 tests/runtime-async/async/simple-future/runner.mbt create mode 100644 tests/runtime-async/async/simple-stream-payload/runner.mbt create mode 100644 tests/runtime-async/async/simple-stream/runner.mbt diff --git a/tests/runtime-async/async/future-cancel-read/runner.mbt b/tests/runtime-async/async/future-cancel-read/runner.mbt new file mode 100644 index 000000000..568fc1b67 --- /dev/null +++ b/tests/runtime-async/async/future-cancel-read/runner.mbt @@ -0,0 +1,10 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["my/test/interface/my/test_/i", "my/test/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + @i.cancel_before_read(@async.Future::ready(1U)) + @i.start_read_then_cancel(@async.Future::ready(4U), @async.Future::ready(())) +} diff --git a/tests/runtime-async/async/future-cancel-write/runner.mbt b/tests/runtime-async/async/future-cancel-write/runner.mbt new file mode 100644 index 000000000..7362b4b15 --- /dev/null +++ b/tests/runtime-async/async/future-cancel-write/runner.mbt @@ -0,0 +1,10 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["my/test/interface/my/test_/i", "my/test/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + @i.take_then_drop(@async.Future::ready("hello")) + @i.read_and_drop(@async.Future::ready("hello2")) +} diff --git a/tests/runtime-async/async/future-close-after-coming-back/runner.mbt b/tests/runtime-async/async/future-close-after-coming-back/runner.mbt new file mode 100644 index 000000000..c2ba3c5d9 --- /dev/null +++ b/tests/runtime-async/async/future-close-after-coming-back/runner.mbt @@ -0,0 +1,10 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["a/b/interface/a/b/theTest", "a/b/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + let returned = @theTest.f(@async.Future::ready(())) + returned.drop() +} diff --git a/tests/runtime-async/async/moonbit-future-write/runner.mbt b/tests/runtime-async/async/moonbit-future-write/runner.mbt new file mode 100644 index 000000000..d70b68bce --- /dev/null +++ b/tests/runtime-async/async/moonbit-future-write/runner.mbt @@ -0,0 +1,13 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["my/test/interface/my/test_/i", "my/test/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + let rx = @i.create_future_with_value(42U) + assert_eq(rx.get(), 42U) + + let done = @i.create_unit_future() + done.get() +} diff --git a/tests/runtime-async/async/moonbit-stream-write/runner.mbt b/tests/runtime-async/async/moonbit-stream-write/runner.mbt new file mode 100644 index 000000000..136d26737 --- /dev/null +++ b/tests/runtime-async/async/moonbit-stream-write/runner.mbt @@ -0,0 +1,26 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["my/test/interface/my/test_/i", "my/test/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + let stream = @i.create_stream_with_values(3U) + let mut total = 0U + let mut count = 0U + while stream.read(16) is Some(chunk) { + for value in chunk { + total += value + count += 1U + } + } + assert_eq(count, 3U) + assert_eq(total, 3U) + + let unit_stream = @i.create_unit_stream(5U) + let mut unit_count = 0U + while unit_stream.read(16) is Some(chunk) { + unit_count += chunk.length().reinterpret_as_uint() + } + assert_eq(unit_count, 5U) +} diff --git a/tests/runtime-async/async/simple-future/runner.mbt b/tests/runtime-async/async/simple-future/runner.mbt new file mode 100644 index 000000000..d40c345f3 --- /dev/null +++ b/tests/runtime-async/async/simple-future/runner.mbt @@ -0,0 +1,10 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["my/test/interface/my/test_/i", "my/test/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + @i.read_future(@async.Future::ready(())) + @i.drop_future(@async.Future::ready(())) +} diff --git a/tests/runtime-async/async/simple-stream-payload/runner.mbt b/tests/runtime-async/async/simple-stream-payload/runner.mbt new file mode 100644 index 000000000..ed56ffd40 --- /dev/null +++ b/tests/runtime-async/async/simple-stream-payload/runner.mbt @@ -0,0 +1,20 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["my/test/interface/my/test_/i", "my/test/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + let stream = @async.Stream::from(async fn(sink : @async.Sink[Byte]) { + let a : Array[Byte] = [(0).to_byte()] + let b : Array[Byte] = [(1).to_byte(), (2).to_byte()] + let c : Array[Byte] = [(3).to_byte()] + let d : Array[Byte] = [(4).to_byte()] + assert_eq(sink.write(a[:]), 1) + assert_eq(sink.write(b[:]), 2) + assert_eq(sink.write(c[:]), 1) + assert_eq(sink.write(d[:]), 1) + sink.close() + }) + @i.read_stream(stream) +} diff --git a/tests/runtime-async/async/simple-stream/runner.mbt b/tests/runtime-async/async/simple-stream/runner.mbt new file mode 100644 index 000000000..b656c2a24 --- /dev/null +++ b/tests/runtime-async/async/simple-stream/runner.mbt @@ -0,0 +1,16 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' +//@ [lang] +//@ path = 'gen/world/runner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["my/test/interface/my/test_/i", "my/test/async"] }""" + +///| +pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { + let stream = @async.Stream::from(async fn(sink : @async.Sink[Unit]) { + let first : Array[Unit] = [()] + let second : Array[Unit] = [(), ()] + assert_eq(sink.write(first[:]), 1) + assert_eq(sink.write(second[:]), 2) + sink.close() + }) + @i.read_stream(stream) +} From 2f0c75e4daa9439d31d63a33a3a430f8e5df4b82 Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 12 Feb 2026 20:28:56 +0800 Subject: [PATCH 45/61] moonbit: remove unused codegen constants and enum --- crates/moonbit/src/lib.rs | 8 -------- crates/moonbit/src/pkg.rs | 2 -- 2 files changed, 10 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 022d4c5ff..d322db0fa 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -40,9 +40,6 @@ mod pkg; // We use AsyncCallback ABI for async functions // TODO: Export will share the type signatures with the import by using a newtype alias -pub(crate) const FFI_DIR: &str = "ffi"; - -pub(crate) const FFI: &str = include_str!("./ffi/ffi.mbt"); const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -114,11 +111,6 @@ impl InterfaceFragment { } } -enum PayloadFor { - Future, - Stream, -} - #[derive(Default)] pub struct MoonBit { opts: Opts, diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index e508734f3..2b15b5432 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -11,8 +11,6 @@ use wit_bindgen_core::{ use crate::async_support::ASYNC_DIR; -pub(crate) const FFI_DIR: &str = "ffi"; - #[derive(Default)] pub(crate) struct Imports { pub packages: HashMap, From 5f4c78bcfe1c86b2b2dbb26e1a945ea220fa922c Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 13 Feb 2026 18:44:37 +0800 Subject: [PATCH 46/61] moonbit: add async runtime test implementations --- .../async/cancel-import/test.mbt | 26 ++++ .../future-cancel-write-then-read/test.mbt | 7 ++ .../future-close-then-receive-read/test.mbt | 17 +++ .../async/future-closes-with-error/test.mbt | 10 ++ .../test.mbt | 7 ++ .../future-write-then-read-remote/test.mbt | 7 ++ .../async/incomplete-writes/leaf.mbt | 26 ++++ .../async/incomplete-writes/test.mbt | 116 ++++++++++++++++++ .../async/moonbit-future-write/runner.rs | 2 + .../async/moonbit-stream-write/runner.rs | 2 + .../async/pending-import/test.mbt | 7 ++ tests/runtime-async/async/ping-pong/test.mbt | 17 +++ .../async/rust-cross-task-wakeup/test.mbt | 21 ++++ .../async/rust-lowered-send/test.mbt | 8 ++ .../async/simple-pending-import/test.mbt | 9 ++ .../runtime-async/async/simple-yield/test.mbt | 7 ++ .../async/threading-builtins/test.mbt | 7 ++ .../async/yield-loop-receives-events/leaf.mbt | 8 ++ .../yield-loop-receives-events/middle.mbt | 22 ++++ 19 files changed, 326 insertions(+) create mode 100644 tests/runtime-async/async/cancel-import/test.mbt create mode 100644 tests/runtime-async/async/future-cancel-write-then-read/test.mbt create mode 100644 tests/runtime-async/async/future-close-then-receive-read/test.mbt create mode 100644 tests/runtime-async/async/future-closes-with-error/test.mbt create mode 100644 tests/runtime-async/async/future-write-then-read-comes-back/test.mbt create mode 100644 tests/runtime-async/async/future-write-then-read-remote/test.mbt create mode 100644 tests/runtime-async/async/incomplete-writes/leaf.mbt create mode 100644 tests/runtime-async/async/incomplete-writes/test.mbt create mode 100644 tests/runtime-async/async/pending-import/test.mbt create mode 100644 tests/runtime-async/async/ping-pong/test.mbt create mode 100644 tests/runtime-async/async/rust-cross-task-wakeup/test.mbt create mode 100644 tests/runtime-async/async/rust-lowered-send/test.mbt create mode 100644 tests/runtime-async/async/simple-pending-import/test.mbt create mode 100644 tests/runtime-async/async/simple-yield/test.mbt create mode 100644 tests/runtime-async/async/threading-builtins/test.mbt create mode 100644 tests/runtime-async/async/yield-loop-receives-events/leaf.mbt create mode 100644 tests/runtime-async/async/yield-loop-receives-events/middle.mbt diff --git a/tests/runtime-async/async/cancel-import/test.mbt b/tests/runtime-async/async/cancel-import/test.mbt new file mode 100644 index 000000000..876dc9782 --- /dev/null +++ b/tests/runtime-async/async/cancel-import/test.mbt @@ -0,0 +1,26 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/i/stub.mbt' + +///| +pub async fn pending_import(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { + let _ = @async.protect_from_cancel(async fn() { + x.get() + }) catch { + @async.FutureReadError::Cancelled => { + return + } + @async.Cancelled::Cancelled => { + return + } + _ => panic() + } +} + +///| +pub fn backpressure_set(x : Bool) -> Unit { + if x { + @async.backpressure_inc() + } else { + @async.backpressure_dec() + } +} diff --git a/tests/runtime-async/async/future-cancel-write-then-read/test.mbt b/tests/runtime-async/async/future-cancel-write-then-read/test.mbt new file mode 100644 index 000000000..3ec9701e0 --- /dev/null +++ b/tests/runtime-async/async/future-cancel-write-then-read/test.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/theTest/stub.mbt' + +///| +pub async fn f(param : @async.Future[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { + assert_eq(param.get(), (0).to_byte()) +} diff --git a/tests/runtime-async/async/future-close-then-receive-read/test.mbt b/tests/runtime-async/async/future-close-then-receive-read/test.mbt new file mode 100644 index 000000000..74afda86b --- /dev/null +++ b/tests/runtime-async/async/future-close-then-receive-read/test.mbt @@ -0,0 +1,17 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/theTest/stub.mbt' + +///| +let slot : Ref[@async.Future[Unit]?] = { val: None } + +///| +pub async fn set(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { + slot.val = Some(param) +} + +///| +pub async fn get(task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { + guard slot.val is Some(value) + slot.val = None + value +} diff --git a/tests/runtime-async/async/future-closes-with-error/test.mbt b/tests/runtime-async/async/future-closes-with-error/test.mbt new file mode 100644 index 000000000..14bed46df --- /dev/null +++ b/tests/runtime-async/async/future-closes-with-error/test.mbt @@ -0,0 +1,10 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/theTest/stub.mbt' + +///| +pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { + let _ = param.get() catch { + @async.FutureReadError::Cancelled => () + _ => panic() + } +} diff --git a/tests/runtime-async/async/future-write-then-read-comes-back/test.mbt b/tests/runtime-async/async/future-write-then-read-comes-back/test.mbt new file mode 100644 index 000000000..4e4094481 --- /dev/null +++ b/tests/runtime-async/async/future-write-then-read-comes-back/test.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/theTest/stub.mbt' + +///| +pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { + param +} diff --git a/tests/runtime-async/async/future-write-then-read-remote/test.mbt b/tests/runtime-async/async/future-write-then-read-remote/test.mbt new file mode 100644 index 000000000..6fb0d0ce8 --- /dev/null +++ b/tests/runtime-async/async/future-write-then-read-remote/test.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/theTest/stub.mbt' + +///| +pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { + param.get() +} diff --git a/tests/runtime-async/async/incomplete-writes/leaf.mbt b/tests/runtime-async/async/incomplete-writes/leaf.mbt new file mode 100644 index 000000000..47c8d3cd2 --- /dev/null +++ b/tests/runtime-async/async/incomplete-writes/leaf.mbt @@ -0,0 +1,26 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/leafInterface/stub.mbt' + +///| +let leaf_values : Map[Int, String] = {} + +///| +let next_leaf_rep : Ref[Int] = { val: 1 } + +///| Destructor of the resource. +pub fn LeafThing::dtor(self : LeafThing) -> Unit { + leaf_values.remove(self.rep()) +} + +///| +pub fn LeafThing::leaf_thing(s : String) -> LeafThing { + let rep = next_leaf_rep.val + next_leaf_rep.val += 1 + leaf_values[rep] = s + LeafThing::new(rep) +} + +///| +pub fn LeafThing::get(self : LeafThing) -> String { + leaf_values[self.rep()] +} diff --git a/tests/runtime-async/async/incomplete-writes/test.mbt b/tests/runtime-async/async/incomplete-writes/test.mbt new file mode 100644 index 000000000..52942ec94 --- /dev/null +++ b/tests/runtime-async/async/incomplete-writes/test.mbt @@ -0,0 +1,116 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/testInterface/stub.mbt' + +///| +let test_values : Map[Int, String] = {} + +///| +let next_test_rep : Ref[Int] = { val: 1 } + +///| Destructor of the resource. +pub fn TestThing::dtor(self : TestThing) -> Unit { + test_values.remove(self.rep()) +} + +///| +pub fn TestThing::test_thing(s : String) -> TestThing { + let rep = next_test_rep.val + next_test_rep.val += 1 + test_values[rep] = s + TestThing::new(rep) +} + +///| +pub fn TestThing::get(self : TestThing) -> String { + test_values[self.rep()] +} + +///| +pub async fn short_reads_test( + s : @async.Stream[TestThing], + task_group : @async.TaskGroup[Unit], +) -> @async.Stream[TestThing] { + @async.Stream::from(async fn(sink : @async.Sink[TestThing]) { + let things : Array[TestThing] = [] + for { + match s.read(1) { + Some(chunk) => { + for i = 0; i < chunk.length(); i = i + 1 { + things.push(chunk[i]) + } + } + None => break + } + } + s.close() + if things.length() > 0 { + let _ = sink.write(things[:]) + } + sink.close() + }) +} + +///| +pub async fn short_reads_leaf( + s : @async.Stream[@leafInterface.LeafThing], + task_group : @async.TaskGroup[Unit], +) -> @async.Stream[@leafInterface.LeafThing] { + @async.Stream::from(async fn(sink : @async.Sink[@leafInterface.LeafThing]) { + let things : Array[@leafInterface.LeafThing] = [] + for { + match s.read(1) { + Some(chunk) => { + for i = 0; i < chunk.length(); i = i + 1 { + things.push(chunk[i]) + } + } + None => break + } + } + s.close() + if things.length() > 0 { + let _ = sink.write(things[:]) + } + sink.close() + }) +} + +///| +pub async fn dropped_reader_test( + f1 : @async.Future[TestThing], + f2 : @async.Future[TestThing], + task_group : @async.TaskGroup[Unit], +) -> (@async.Future[TestThing], @async.Future[TestThing]) { + let (out1, writer1) : (@async.Future[TestThing], @async.Promise[TestThing]) = @async.Future::new() + let (out2, writer2) : (@async.Future[TestThing], @async.Promise[TestThing]) = @async.Future::new() + task_group.spawn_bg(async fn() { + f1.drop() + let thing = f2.get() + writer1.close() + writer2.write(thing) + }) + (out1, out2) +} + +///| +pub async fn dropped_reader_leaf( + f1 : @async.Future[@leafInterface.LeafThing], + f2 : @async.Future[@leafInterface.LeafThing], + task_group : @async.TaskGroup[Unit], +) -> (@async.Future[@leafInterface.LeafThing], @async.Future[@leafInterface.LeafThing]) { + let (out1, writer1) : ( + @async.Future[@leafInterface.LeafThing], + @async.Promise[@leafInterface.LeafThing], + ) = @async.Future::new() + let (out2, writer2) : ( + @async.Future[@leafInterface.LeafThing], + @async.Promise[@leafInterface.LeafThing], + ) = @async.Future::new() + task_group.spawn_bg(async fn() { + f1.drop() + let thing = f2.get() + writer1.close() + writer2.write(thing) + }) + (out1, out2) +} diff --git a/tests/runtime-async/async/moonbit-future-write/runner.rs b/tests/runtime-async/async/moonbit-future-write/runner.rs index 78e80b844..a8f9455e6 100644 --- a/tests/runtime-async/async/moonbit-future-write/runner.rs +++ b/tests/runtime-async/async/moonbit-future-write/runner.rs @@ -1,3 +1,5 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' + include!(env!("BINDINGS")); use crate::my::test::i::*; diff --git a/tests/runtime-async/async/moonbit-stream-write/runner.rs b/tests/runtime-async/async/moonbit-stream-write/runner.rs index 29542a7bb..c6232bea1 100644 --- a/tests/runtime-async/async/moonbit-stream-write/runner.rs +++ b/tests/runtime-async/async/moonbit-stream-write/runner.rs @@ -1,3 +1,5 @@ +//@ wasmtime-flags = '-Wcomponent-model-async' + include!(env!("BINDINGS")); use crate::my::test::i::*; diff --git a/tests/runtime-async/async/pending-import/test.mbt b/tests/runtime-async/async/pending-import/test.mbt new file mode 100644 index 000000000..00e1fc02a --- /dev/null +++ b/tests/runtime-async/async/pending-import/test.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/i/stub.mbt' + +///| +pub async fn pending_import(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { + x.get() +} diff --git a/tests/runtime-async/async/ping-pong/test.mbt b/tests/runtime-async/async/ping-pong/test.mbt new file mode 100644 index 000000000..35903b3de --- /dev/null +++ b/tests/runtime-async/async/ping-pong/test.mbt @@ -0,0 +1,17 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/i/stub.mbt' + +///| +pub async fn ping( + x : @async.Future[String], + y : String, + task_group : @async.TaskGroup[Unit], +) -> @async.Future[String] { + let message = x.get() + y + @async.Future::from(async fn() { message }) +} + +///| +pub async fn pong(x : @async.Future[String], task_group : @async.TaskGroup[Unit]) -> String { + x.get() +} diff --git a/tests/runtime-async/async/rust-cross-task-wakeup/test.mbt b/tests/runtime-async/async/rust-cross-task-wakeup/test.mbt new file mode 100644 index 000000000..b44148684 --- /dev/null +++ b/tests/runtime-async/async/rust-cross-task-wakeup/test.mbt @@ -0,0 +1,21 @@ +//@ [lang] +//@ path = 'gen/interface/my/test_/i/stub.mbt' + +///| +let resolved : Ref[Bool] = { val: false } + +///| +pub async fn pending_import(task_group : @async.TaskGroup[Unit]) -> Unit { + resolved.val = false + for { + if resolved.val { + return + } + @async.pause() + } +} + +///| +pub fn resolve_pending_import() -> Unit { + resolved.val = true +} diff --git a/tests/runtime-async/async/rust-lowered-send/test.mbt b/tests/runtime-async/async/rust-lowered-send/test.mbt new file mode 100644 index 000000000..1a5a6f467 --- /dev/null +++ b/tests/runtime-async/async/rust-lowered-send/test.mbt @@ -0,0 +1,8 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/i/stub.mbt' + +///| +pub async fn one_argument(x : String, task_group : @async.TaskGroup[Unit]) -> Unit { + let copied = x + "" + assert_eq(copied, "hello") +} diff --git a/tests/runtime-async/async/simple-pending-import/test.mbt b/tests/runtime-async/async/simple-pending-import/test.mbt new file mode 100644 index 000000000..e4380f28a --- /dev/null +++ b/tests/runtime-async/async/simple-pending-import/test.mbt @@ -0,0 +1,9 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/i/stub.mbt' + +///| +pub async fn f(task_group : @async.TaskGroup[Unit]) -> Unit { + for i = 0; i < 10; i = i + 1 { + @async.pause() + } +} diff --git a/tests/runtime-async/async/simple-yield/test.mbt b/tests/runtime-async/async/simple-yield/test.mbt new file mode 100644 index 000000000..385e6d5d8 --- /dev/null +++ b/tests/runtime-async/async/simple-yield/test.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/i/stub.mbt' + +///| +pub async fn f(task_group : @async.TaskGroup[Unit]) -> Unit { + @async.pause() +} diff --git a/tests/runtime-async/async/threading-builtins/test.mbt b/tests/runtime-async/async/threading-builtins/test.mbt new file mode 100644 index 000000000..385e6d5d8 --- /dev/null +++ b/tests/runtime-async/async/threading-builtins/test.mbt @@ -0,0 +1,7 @@ +//@ [lang] +//@ path = 'gen/interface/a/b/i/stub.mbt' + +///| +pub async fn f(task_group : @async.TaskGroup[Unit]) -> Unit { + @async.pause() +} diff --git a/tests/runtime-async/async/yield-loop-receives-events/leaf.mbt b/tests/runtime-async/async/yield-loop-receives-events/leaf.mbt new file mode 100644 index 000000000..5bdfa6e28 --- /dev/null +++ b/tests/runtime-async/async/yield-loop-receives-events/leaf.mbt @@ -0,0 +1,8 @@ +//@ [lang] +//@ path = 'gen/interface/test_/common/iMiddle/stub.mbt' + +///| +pub async fn f(task_group : @async.TaskGroup[Unit]) -> Unit { + @async.pause() + @async.pause() +} diff --git a/tests/runtime-async/async/yield-loop-receives-events/middle.mbt b/tests/runtime-async/async/yield-loop-receives-events/middle.mbt new file mode 100644 index 000000000..4a0a6cb67 --- /dev/null +++ b/tests/runtime-async/async/yield-loop-receives-events/middle.mbt @@ -0,0 +1,22 @@ +//@ [lang] +//@ path = 'gen/interface/test_/common/iRunner/stub.mbt' +//@ pkg_config = """{ "warn-list": "-44", "import": ["test/common/interface/test_/common/iMiddle", "test/common/async"] }""" + +///| +let hit : Ref[Bool] = { val: false } + +///| +pub async fn f(task_group : @async.TaskGroup[Unit]) -> Unit { + hit.val = false + task_group.spawn_bg(async fn() { + @iMiddle.f() + hit.val = true + }) + + for { + if hit.val { + return + } + @async.pause() + } +} From 7fe6528ff1e439d18dd7175e21e259b858bbcc52 Mon Sep 17 00:00:00 2001 From: yezihang Date: Sat, 14 Feb 2026 17:13:50 +0800 Subject: [PATCH 47/61] moonbit: fix async future-write temp ptr and make embed encoding configurable --- crates/moonbit/src/async_support.rs | 8 ++++---- crates/test/src/moonbit.rs | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 89b928544..8c5df1188 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -445,13 +445,13 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ lower, r#" let value = producer() - let ptr = mbt_ffi_malloc({size}) - defer mbt_ffi_free(ptr)"# + let ret_area = mbt_ffi_malloc({size}) + defer mbt_ffi_free(ret_area)"# ); abi::lower_to_memory( &resolve, &mut bindgen, - "ptr".to_string(), + "ret_area".to_string(), "value".to_string(), &inner_ty, ); @@ -459,7 +459,7 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ uwriteln!( lower, r#" - let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, ptr)) catch {{ _ => false }}"# + let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, ret_area)) catch {{ _ => false }}"# ); } else { // Unit type - no value to write, just complete the future diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index 8e66dbcf9..37c67fb68 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -11,6 +11,12 @@ struct LangConfig { path: String, #[serde(default)] pkg_config: Option, + #[serde(default = "default_encoding")] + encoding: String, +} + +fn default_encoding() -> String { + "utf16".to_string() } pub struct MoonBit; @@ -41,6 +47,14 @@ impl LanguageMethods for MoonBit { fn compile(&self, runner: &Runner, compile: &crate::Compile) -> anyhow::Result<()> { let config = compile.component.deserialize_lang_config::()?; + let encoding = match config.encoding.as_str() { + "utf8" | "utf16" | "compact-utf16" => config.encoding.as_str(), + other => { + bail!( + "unsupported MoonBit component encoding `{other}`; expected one of: utf8, utf16, compact-utf16" + ) + } + }; // Copy the file to the bindings directory if !config.path.is_empty() { let src_path = &compile.component.path; @@ -100,7 +114,7 @@ impl LanguageMethods for MoonBit { let mut cmd = Command::new("wasm-tools"); cmd.arg("component") .arg("embed") - .args(["--encoding", "utf16"]) + .args(["--encoding", encoding]) .args(["-o", embedded.to_str().unwrap()]) .args(["-w", &compile.component.bindgen.world]) .arg(manifest_dir) From 8d822b4a7eab22b3f9d692a827ddef7ef142199e Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 13:45:54 +0800 Subject: [PATCH 48/61] moonbit: fix async cancel-import deadlock in stackless runtime --- crates/moonbit/src/async/coroutine.mbt | 12 ++++--- crates/moonbit/src/async/ev.mbt | 33 +++++++++++------ crates/moonbit/src/async_support.rs | 36 ++++++++++++++----- .../async/cancel-import/test.mbt | 12 +------ 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/crates/moonbit/src/async/coroutine.mbt b/crates/moonbit/src/async/coroutine.mbt index 110a59945..3d11edbe0 100644 --- a/crates/moonbit/src/async/coroutine.mbt +++ b/crates/moonbit/src/async/coroutine.mbt @@ -107,10 +107,14 @@ pub fn spawn(f : async () -> Unit) -> Coroutine { fn run(_) { run_async(fn() { coro.shielded = false - try f() catch { - err => coro.state = Fail(err) - } noraise { - _ => coro.state = Done + if coro.cancelled { + coro.state = Fail(Cancelled::Cancelled) + } else { + try f() catch { + err => coro.state = Fail(err) + } noraise { + _ => coro.state = Done + } } for coro in coro.downstream { coro.wake() diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt index 4e7e4b6ac..97500b0b3 100644 --- a/crates/moonbit/src/async/ev.mbt +++ b/crates/moonbit/src/async/ev.mbt @@ -30,6 +30,11 @@ fn current_waitableset() -> WaitableSet { WaitableSet(tls_get()) } +///| +fn should_complete(waitable_set : WaitableSet) -> Bool { + ev.finished.get(waitable_set) is Some(true) && no_more_work() +} + ///| pub fn with_waitableset(f : async () -> Unit) -> Int { let waitable_set = WaitableSet::new() @@ -41,7 +46,7 @@ pub fn with_waitableset(f : async () -> Unit) -> Int { ev.tasks.set(waitable_set, coro) ev.finished.set(waitable_set, false) reschedule() - if ev.finished.get(waitable_set) is Some(true) && no_more_work() { + if should_complete(waitable_set) { ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) waitable_set.drop() @@ -58,7 +63,7 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { match events { None => { reschedule() - if ev.finished.get(waitable_set) is Some(true) && no_more_work() { + if should_complete(waitable_set) { ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) waitable_set.drop() @@ -71,16 +76,13 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { guard ev.tasks.get(waitable_set) is Some(coro) coro.cancel() reschedule() - if ev.finished.get(waitable_set) is Some(true) && no_more_work() { - ev.tasks.remove(waitable_set) - ev.finished.remove(waitable_set) + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + if ev.subscribes.is_empty() { waitable_set.drop() - task_cancel() - return CallbackCode::Completed.encode() - } else { - // Unlikely to reach here - return CallbackCode::Wait(waitable_set.0).encode() } + task_cancel() + return CallbackCode::Completed.encode() } _ => { let sub = ev.subscribes.get(waitable_id) @@ -91,7 +93,7 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { ev.subscribes.remove(waitable_id) waitable_join(waitable_id, 0) reschedule() - if ev.finished.get(waitable_set) is Some(true) && no_more_work() { + if should_complete(waitable_set) { ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) waitable_set.drop() @@ -106,6 +108,15 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { ///| let ev : EventLoop = { subscribes: {}, tasks: {}, finished: {} } +///| +pub fn detach_waitable(waitable_id : Int) -> Unit { + if ev.subscribes.get(waitable_id) is Some(subscriber) { + subscriber.coro.clear() + ev.subscribes.remove(waitable_id) + } + waitable_join(waitable_id, 0) +} + ///| pub async fn suspend_for_subtask( val : Int, diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 8c5df1188..87da0f3e2 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -344,12 +344,17 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ let mut reading = 0 async fn drop() {{ if !dropped && reading > 0 {{ - {async_qualifier}suspend_for_future_read( - future_handle, - wasmLift{camel_name}{index}CancelRead(future_handle) - ) catch {{ - {async_qualifier}FutureReadError::Cancelled => () - _ => panic() + let cancel = wasmLift{camel_name}{index}CancelRead(future_handle) + if cancel == -1 {{ + {async_qualifier}detach_waitable(future_handle) + }} else {{ + {async_qualifier}suspend_for_future_read( + future_handle, + cancel + ) catch {{ + {async_qualifier}FutureReadError::Cancelled => () + _ => panic() + }} }} }} if !dropped {{ @@ -369,12 +374,27 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ let ptr = mbt_ffi_malloc({size}) defer mbt_ffi_free(ptr) {{ + let mut read_cancelled = false reading += 1 - defer {{ reading -= 1 }} + defer {{ + if !read_cancelled {{ + reading -= 1 + }} + }} {async_qualifier}suspend_for_future_read( future_handle, wasmLift{camel_name}{index}Read(future_handle, ptr), - ) + ) catch {{ + err => {{ + if err is {async_qualifier}Cancelled::Cancelled {{ + read_cancelled = true + }} + drop() catch {{ + _ => () + }} + raise err + }} + }} }} result = {{ "# diff --git a/tests/runtime-async/async/cancel-import/test.mbt b/tests/runtime-async/async/cancel-import/test.mbt index 876dc9782..3ddcc4ec2 100644 --- a/tests/runtime-async/async/cancel-import/test.mbt +++ b/tests/runtime-async/async/cancel-import/test.mbt @@ -3,17 +3,7 @@ ///| pub async fn pending_import(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { - let _ = @async.protect_from_cancel(async fn() { - x.get() - }) catch { - @async.FutureReadError::Cancelled => { - return - } - @async.Cancelled::Cancelled => { - return - } - _ => panic() - } + x.get() } ///| From a53d13534f61ada42380daa4918116c5f797b9ba Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 14:25:54 +0800 Subject: [PATCH 49/61] moonbit: avoid stackless yield-loop starvation while preserving cancel semantics --- crates/moonbit/src/async/ev.mbt | 48 ++++++++++++-------------- crates/moonbit/src/async/scheduler.mbt | 5 ++- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt index 97500b0b3..ac4ee67b0 100644 --- a/crates/moonbit/src/async/ev.mbt +++ b/crates/moonbit/src/async/ev.mbt @@ -35,6 +35,20 @@ fn should_complete(waitable_set : WaitableSet) -> Bool { ev.finished.get(waitable_set) is Some(true) && no_more_work() } +///| +fn next_callback(waitable_set : WaitableSet) -> Int { + if should_complete(waitable_set) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + return CallbackCode::Completed.encode() + } + if has_immediately_ready_task() && ev.subscribes.is_empty() { + return CallbackCode::Yield.encode() + } + CallbackCode::Wait(waitable_set.0).encode() +} + ///| pub fn with_waitableset(f : async () -> Unit) -> Int { let waitable_set = WaitableSet::new() @@ -46,14 +60,7 @@ pub fn with_waitableset(f : async () -> Unit) -> Int { ev.tasks.set(waitable_set, coro) ev.finished.set(waitable_set, false) reschedule() - if should_complete(waitable_set) { - ev.tasks.remove(waitable_set) - ev.finished.remove(waitable_set) - waitable_set.drop() - return CallbackCode::Completed.encode() - } else { - return CallbackCode::Wait(waitable_set.0).encode() - } + next_callback(waitable_set) } ///| @@ -63,19 +70,17 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { match events { None => { reschedule() - if should_complete(waitable_set) { - ev.tasks.remove(waitable_set) - ev.finished.remove(waitable_set) - waitable_set.drop() - return CallbackCode::Completed.encode() - } else { - return CallbackCode::Wait(waitable_set.0).encode() - } + next_callback(waitable_set) } TaskCancelled => { guard ev.tasks.get(waitable_set) is Some(coro) coro.cancel() - reschedule() + for { + reschedule() + if !has_immediately_ready_task() { + break + } + } ev.tasks.remove(waitable_set) ev.finished.remove(waitable_set) if ev.subscribes.is_empty() { @@ -93,14 +98,7 @@ pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { ev.subscribes.remove(waitable_id) waitable_join(waitable_id, 0) reschedule() - if should_complete(waitable_set) { - ev.tasks.remove(waitable_set) - ev.finished.remove(waitable_set) - waitable_set.drop() - return CallbackCode::Completed.encode() - } else { - return CallbackCode::Wait(waitable_set.0).encode() - } + next_callback(waitable_set) } } } diff --git a/crates/moonbit/src/async/scheduler.mbt b/crates/moonbit/src/async/scheduler.mbt index c99947b54..01457685b 100644 --- a/crates/moonbit/src/async/scheduler.mbt +++ b/crates/moonbit/src/async/scheduler.mbt @@ -45,7 +45,10 @@ pub fn no_more_work() -> Bool { ///| pub fn reschedule() -> Unit { - while scheduler.run_later.pop_front() is Some(coro) { + let mut budget = 1024 + while budget > 0 { + budget -= 1 + guard scheduler.run_later.pop_front() is Some(coro) else { break } coro.ready = false guard coro.state is Suspend(ok_cont~, err_cont~) else { } coro.state = Running From 3b62994022562a5a1330851228eddd3227802228 Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 14:53:55 +0800 Subject: [PATCH 50/61] moonbit: dedupe fixed-length list instruction handlers --- crates/moonbit/src/lib.rs | 108 -------------------------------------- 1 file changed, 108 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index d322db0fa..7cc057093 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -2912,114 +2912,6 @@ impl Bindgen for FunctionBindgen<'_, '_> { _ => unreachable!(), } } - Instruction::FixedLengthListLift { - element, - size, - id: _, - } => { - let mut lifted = Vec::with_capacity(*size as usize); - for operand in operands.drain(0..(*size as usize)) { - lifted.push(operand); - } - if lifted.is_empty() { - let ty = self.resolve_type_name(element); - results.push(format!("([] : FixedArray[{ty}])")); - } else { - results.push(format!("[{}]", lifted.join(", "))); - } - } - - Instruction::FixedLengthListLower { - element: _, - size, - id: _, - } => { - let op = &operands[0]; - for i in 0..(*size as usize) { - results.push(format!("({op})[{i}]")); - } - } - - Instruction::FixedLengthListLowerToMemory { - element, - size, - id: _, - } => { - let Block { - body, - results: block_results, - } = self.blocks.pop().unwrap(); - assert!(block_results.is_empty()); - - let op = &operands[0]; - let target = &operands[1]; - let ty = self.resolve_type_name(element); - let elem_size = self - .interface_gen - .world_gen - .sizes - .size(element) - .size_wasm32(); - - for i in 0..(*size as usize) { - uwrite!( - self.src, - " - {{ - let iter_elem : {ty} = ({op})[{i}] - let iter_base = ({target}) + ({i} * {elem_size}) - {body} - }} - ", - ); - } - } - - Instruction::FixedLengthListLiftFromMemory { - element, - size, - id: _, - } => { - let Block { - body, - results: block_results, - } = self.blocks.pop().unwrap(); - let address = &operands[0]; - let ty = self.resolve_type_name(element); - let elem_size = self - .interface_gen - .world_gen - .sizes - .size(element) - .size_wasm32(); - - let element_result = match &block_results[..] { - [result] => result, - _ => todo!("result count == {}", block_results.len()), - }; - - let mut lifted = Vec::with_capacity(*size as usize); - for i in 0..(*size as usize) { - let value = self.locals.tmp("fixed_elem"); - uwrite!( - self.src, - " - let {value} : {ty} = {{ - let iter_base = ({address}) + ({i} * {elem_size}) - {body} - {element_result} - }} - ", - ); - lifted.push(value); - } - - if lifted.is_empty() { - results.push(format!("([] : FixedArray[{ty}])")); - } else { - results.push(format!("[{}]", lifted.join(", "))); - } - } } } From 5fade417f79444104d45797eb2f7cac411f9044d Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 15:48:47 +0800 Subject: [PATCH 51/61] moonbit: replace global async spawner with coroutine-local state --- crates/moonbit/src/async/coroutine.mbt | 7 +++++++ crates/moonbit/src/async/task_group.mbt | 16 ++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/moonbit/src/async/coroutine.mbt b/crates/moonbit/src/async/coroutine.mbt index 3d11edbe0..e8c3f49b8 100644 --- a/crates/moonbit/src/async/coroutine.mbt +++ b/crates/moonbit/src/async/coroutine.mbt @@ -27,6 +27,7 @@ struct Coroutine { mut shielded : Bool mut cancelled : Bool mut ready : Bool + mut spawner : ((async () -> Unit) -> Unit)? downstream : Set[Coroutine] } @@ -96,6 +97,11 @@ pub async fn suspend() -> Unit raise Cancelled { ///| pub fn spawn(f : async () -> Unit) -> Coroutine { scheduler.coro_id += 1 + let inherited_spawner = if scheduler.curr_coro is Some(parent) { + parent.spawner + } else { + None + } let coro = { state: Running, ready: true, @@ -103,6 +109,7 @@ pub fn spawn(f : async () -> Unit) -> Coroutine { downstream: Set::new(), coro_id: scheduler.coro_id, cancelled: false, + spawner: inherited_spawner, } fn run(_) { run_async(fn() { diff --git a/crates/moonbit/src/async/task_group.mbt b/crates/moonbit/src/async/task_group.mbt index 7dd51edcc..d0c487a92 100644 --- a/crates/moonbit/src/async/task_group.mbt +++ b/crates/moonbit/src/async/task_group.mbt @@ -40,18 +40,13 @@ struct TaskGroup[X] { group_defer : Array[async () -> Unit] } -///| -/// Spawner for the current task group (if any). -/// This is set by `with_task_group` so that generated bindings can spawn -/// background tasks without explicitly threading `TaskGroup` everywhere. -let current_spawner : Ref[((async () -> Unit) -> Unit)?] = { val: None } - ///| /// Spawn a background task into the current task group. /// /// This will fail if called outside `with_task_group`. pub fn spawn_bg_current(f : async () -> Unit) -> Unit { - guard current_spawner.val is Some(spawn) + let coro = current_coroutine() + guard coro.spawner is Some(spawn) spawn(f) } @@ -205,10 +200,11 @@ pub async fn[X] with_task_group(f : async (TaskGroup[X]) -> X) -> X { result: None, group_defer: [], } - let prev = current_spawner.val - current_spawner.val = Some(fn(child) { tg.spawn_bg(child) }) + let curr = current_coroutine() + let prev = curr.spawner + curr.spawner = Some(fn(child) { tg.spawn_bg(child) }) defer { - current_spawner.val = prev + curr.spawner = prev } tg.spawn_bg(fn() { let value = f(tg) From e6ae477b29318e19b5751f6e19dd453c577d95cd Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 17:01:35 +0800 Subject: [PATCH 52/61] moonbit: split Future and CMFuture, add nested future coverage --- crates/moonbit/src/async/trait.mbt | 124 ++++++++++++------ crates/moonbit/src/async_support.rs | 6 +- crates/moonbit/src/lib.rs | 6 +- crates/moonbit/src/pkg.rs | 2 +- tests/codegen/futures.wit | 5 + .../async/cancel-import/test.mbt | 2 +- .../async/future-cancel-read/runner.mbt | 4 +- .../async/future-cancel-read/test.mbt | 8 +- .../future-cancel-write-then-read/test.mbt | 2 +- .../async/future-cancel-write/runner.mbt | 4 +- .../async/future-cancel-write/test.mbt | 4 +- .../future-close-after-coming-back/runner.mbt | 2 +- .../future-close-after-coming-back/test.mbt | 4 +- .../future-close-then-receive-read/test.mbt | 6 +- .../async/future-closes-with-error/test.mbt | 2 +- .../test.mbt | 2 +- .../future-write-then-read-remote/test.mbt | 2 +- .../async/incomplete-writes/test.mbt | 24 ++-- .../async/moonbit-future-write/runner.mbt | 8 ++ .../async/moonbit-future-write/runner.rs | 12 ++ .../async/moonbit-future-write/test.mbt | 18 ++- .../async/moonbit-future-write/test.wit | 8 ++ .../async/pending-import/test.mbt | 2 +- tests/runtime-async/async/ping-pong/test.mbt | 8 +- .../async/simple-future/runner.mbt | 4 +- .../async/simple-future/test.mbt | 4 +- 26 files changed, 183 insertions(+), 90 deletions(-) diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index ce8988793..a1c8128d3 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -284,36 +284,98 @@ async fn[X] local_stream_read( } ///| -pub(all) struct FutureOutInner[X] { +pub struct Future[X] { + id : Int + state : Ref[LocalFutureState[X]] +} + +///| +pub impl[X] Eq for Future[X] with equal(self, other) -> Bool { + self.id == other.id +} + +///| +pub impl[X] Show for Future[X] with output(self, logger) { + logger.write_string("Future(") + logger.write_string(self.id.to_string()) + logger.write_string(")") +} + +///| +pub fn[X] Future::new() -> (Future[X], Promise[X]) { + let state : Ref[LocalFutureState[X]] = { + val: { + value: None, + closed: false, + waiters: @deque.new(), + }, + } + let future = Future::{ id: fresh_id(), state } + let promise = Promise::{ + write: async fn(value : X) { local_future_write(state, value) }, + close: async fn() { local_future_close(state) }, + } + (future, promise) +} + +///| +pub fn[X] Future::ready(value : X) -> Future[X] { + let state : Ref[LocalFutureState[X]] = { + val: { + value: Some(value), + closed: false, + waiters: @deque.new(), + }, + } + Future::{ id: fresh_id(), state } +} + +///| +pub async fn[X] Future::get(self : Future[X]) -> X { + local_future_get(self.state) +} + +///| +pub async fn[X] Future::drop(self : Future[X]) -> Unit { + local_future_close(self.state) +} + +///| +pub fn[X] Future::to_cm(self : Future[X]) -> CMFuture[X] { + CMFuture::from_local(self) +} + +///| +pub(all) struct CMFutureOutInner[X] { mut producer : (async () -> X)? } ///| -pub(all) struct FutureOut[X] { +pub(all) struct CMFutureOut[X] { id : Int - inner : Ref[FutureOutInner[X]] + inner : Ref[CMFutureOutInner[X]] } ///| -pub fn[X] FutureOut::new(producer : async () -> X) -> FutureOut[X] { +pub fn[X] CMFutureOut::new(producer : async () -> X) -> CMFutureOut[X] { { id: fresh_id(), inner: { val: { producer: Some(producer) } } } } ///| -pub fn[X] FutureOut::take_producer(self : FutureOut[X]) -> (async () -> X) { +pub fn[X] CMFutureOut::take_producer(self : CMFutureOut[X]) -> (async () -> X) { guard self.inner.val.producer is Some(p) self.inner.val.producer = None p } ///| -pub(all) enum Future[X] { +pub(all) enum CMFuture[X] { Incoming(FutureR[X]) - Outgoing(FutureOut[X]) + Outgoing(CMFutureOut[X]) } ///| -pub impl[X] Eq for Future[X] with equal(self, other) -> Bool { +pub impl[X] Eq for CMFuture[X] with equal(self, other) -> Bool { match self { Incoming(f) => match other { @@ -329,15 +391,15 @@ pub impl[X] Eq for Future[X] with equal(self, other) -> Bool { } ///| -pub impl[X] Show for Future[X] with output(self, logger) { +pub impl[X] Show for CMFuture[X] with output(self, logger) { match self { Incoming(f) => { - logger.write_string("Future::Incoming(") + logger.write_string("CMFuture::Incoming(") logger.write_string(f.handle.to_string()) logger.write_string(")") } Outgoing(f) => { - logger.write_string("Future::Outgoing(") + logger.write_string("CMFuture::Outgoing(") logger.write_string(f.id.to_string()) logger.write_string(")") } @@ -345,40 +407,28 @@ pub impl[X] Show for Future[X] with output(self, logger) { } ///| -pub fn[X] Future::ready(value : X) -> Future[X] { - Future::Outgoing(FutureOut::new(async fn() { value })) +pub fn[X] CMFuture::new() -> (CMFuture[X], Promise[X]) { + let (future, promise) = Future::new() + (CMFuture::from_local(future), promise) } ///| -pub fn[X] Future::new() -> (Future[X], Promise[X]) { - let state : Ref[LocalFutureState[X]] = { - val: { - value: None, - closed: false, - waiters: @deque.new(), - }, - } - let handle = fresh_id() - let future = Future::Incoming(FutureR::{ - handle, - get: async fn() { local_future_get(state) }, - drop: async fn() { local_future_close(state) }, - take_handle: fn() { panic() }, - }) - let promise = Promise::{ - write: async fn(value : X) { local_future_write(state, value) }, - close: async fn() { local_future_close(state) }, - } - (future, promise) +pub fn[X] CMFuture::ready(value : X) -> CMFuture[X] { + CMFuture::Outgoing(CMFutureOut::new(async fn() { value })) } ///| -pub fn[X] Future::from(producer : async () -> X) -> Future[X] { - Future::Outgoing(FutureOut::new(producer)) +pub fn[X] CMFuture::from_local(future : Future[X]) -> CMFuture[X] { + CMFuture::Outgoing(CMFutureOut::new(async fn() { future.get() })) } ///| -pub async fn[X] Future::get(self : Future[X]) -> X { +pub fn[X] CMFuture::from(producer : async () -> X) -> CMFuture[X] { + CMFuture::Outgoing(CMFutureOut::new(producer)) +} + +///| +pub async fn[X] CMFuture::get(self : CMFuture[X]) -> X { match self { Incoming(f) => f.get() Outgoing(f) => (f.take_producer())() @@ -386,7 +436,7 @@ pub async fn[X] Future::get(self : Future[X]) -> X { } ///| -pub async fn[X] Future::drop(self : Future[X]) -> Unit { +pub async fn[X] CMFuture::drop(self : CMFuture[X]) -> Unit { match self { Incoming(f) => f.drop() Outgoing(_) => () diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 87da0f3e2..7c68e03e0 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -362,7 +362,7 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ wasmLift{camel_name}{index}DropReadable(future_handle) }} }} - {async_qualifier}Future::Incoming({async_qualifier}FutureR::{{ + {async_qualifier}CMFuture::Incoming({async_qualifier}FutureR::{{ handle: future_handle, get: fn () {{ if result is Some(r) {{ @@ -444,8 +444,8 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ r#" fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ match future {{ - {async_qualifier}Future::Incoming(f) => f.take_handle() - {async_qualifier}Future::Outgoing(f) => {{ + {async_qualifier}CMFuture::Incoming(f) => f.take_handle() + {async_qualifier}CMFuture::Outgoing(f) => {{ let handles = wasmLower{camel_name}{index}New() let readable = (handles & 0xFFFFFFFF).to_int() let writable = (handles >> 32).to_int() diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 7cc057093..b2ab45cd4 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1494,7 +1494,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { } fn type_future(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { - // Not needed. They will become `FutureR[T]` in MoonBit. + // Not needed. They will become `CMFuture[T]` in MoonBit. } fn type_stream(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { @@ -2518,8 +2518,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { - // For exports, use lowering type names (OutFuture/OutStream) - // For imports, use lifting type names (FutureR/StreamR) + // For exports, use lowering type names. + // For imports, use lifting type names. let ty = match self.direction { Direction::Export => { format!("({})", self.resolve_type_name_for_lowering(&ty)) diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index 2b15b5432..b11a63b58 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -251,7 +251,7 @@ impl PkgResolver { TypeDefKind::Future(ty) => { let qualifier = self.qualify_package(this, ASYNC_DIR); format!( - "{}Future[{}]", + "{}CMFuture[{}]", qualifier, ty.as_ref() .map(|t| self.type_name(this, t)) diff --git a/tests/codegen/futures.wit b/tests/codegen/futures.wit index e312bc2e4..252d0d702 100644 --- a/tests/codegen/futures.wit +++ b/tests/codegen/futures.wit @@ -54,8 +54,13 @@ interface futures { b: string, c: future, } + record nested-future-record { + inner: future>, + } record-future: func(x: future) -> future; record-future-reverse: func(x: future) -> future; + nested-future: func(x: future>) -> future>; + nested-future-record-roundtrip: func(x: nested-future-record) -> nested-future-record; variant some-variant { a(string), diff --git a/tests/runtime-async/async/cancel-import/test.mbt b/tests/runtime-async/async/cancel-import/test.mbt index 3ddcc4ec2..1ff19a590 100644 --- a/tests/runtime-async/async/cancel-import/test.mbt +++ b/tests/runtime-async/async/cancel-import/test.mbt @@ -2,7 +2,7 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn pending_import(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn pending_import(x : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.get() } diff --git a/tests/runtime-async/async/future-cancel-read/runner.mbt b/tests/runtime-async/async/future-cancel-read/runner.mbt index 568fc1b67..45c4cb356 100644 --- a/tests/runtime-async/async/future-cancel-read/runner.mbt +++ b/tests/runtime-async/async/future-cancel-read/runner.mbt @@ -5,6 +5,6 @@ ///| pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { - @i.cancel_before_read(@async.Future::ready(1U)) - @i.start_read_then_cancel(@async.Future::ready(4U), @async.Future::ready(())) + @i.cancel_before_read(@async.CMFuture::ready(1U)) + @i.start_read_then_cancel(@async.CMFuture::ready(4U), @async.CMFuture::ready(())) } diff --git a/tests/runtime-async/async/future-cancel-read/test.mbt b/tests/runtime-async/async/future-cancel-read/test.mbt index 77f1bcbf4..dc14e796a 100644 --- a/tests/runtime-async/async/future-cancel-read/test.mbt +++ b/tests/runtime-async/async/future-cancel-read/test.mbt @@ -2,12 +2,12 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn cancel_before_read(x : @async.Future[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn cancel_before_read(x : @async.CMFuture[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } ///| -pub async fn cancel_after_read(x : @async.Future[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn cancel_after_read(x : @async.CMFuture[UInt], task_group : @async.TaskGroup[Unit]) -> Unit { task_group.spawn_bg(async fn() { x.drop() }) let _ = x.get() catch { @async.FutureReadError::Cancelled => return @@ -18,8 +18,8 @@ pub async fn cancel_after_read(x : @async.Future[UInt], task_group : @async.Task ///| pub async fn start_read_then_cancel( - data : @async.Future[UInt], - signal : @async.Future[Unit], + data : @async.CMFuture[UInt], + signal : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit], ) -> Unit { task_group.spawn_bg(async fn() { diff --git a/tests/runtime-async/async/future-cancel-write-then-read/test.mbt b/tests/runtime-async/async/future-cancel-write-then-read/test.mbt index 3ec9701e0..535f3c39d 100644 --- a/tests/runtime-async/async/future-cancel-write-then-read/test.mbt +++ b/tests/runtime-async/async/future-cancel-write-then-read/test.mbt @@ -2,6 +2,6 @@ //@ path = 'gen/interface/a/b/theTest/stub.mbt' ///| -pub async fn f(param : @async.Future[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn f(param : @async.CMFuture[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { assert_eq(param.get(), (0).to_byte()) } diff --git a/tests/runtime-async/async/future-cancel-write/runner.mbt b/tests/runtime-async/async/future-cancel-write/runner.mbt index 7362b4b15..dc43140c8 100644 --- a/tests/runtime-async/async/future-cancel-write/runner.mbt +++ b/tests/runtime-async/async/future-cancel-write/runner.mbt @@ -5,6 +5,6 @@ ///| pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { - @i.take_then_drop(@async.Future::ready("hello")) - @i.read_and_drop(@async.Future::ready("hello2")) + @i.take_then_drop(@async.CMFuture::ready("hello")) + @i.read_and_drop(@async.CMFuture::ready("hello2")) } diff --git a/tests/runtime-async/async/future-cancel-write/test.mbt b/tests/runtime-async/async/future-cancel-write/test.mbt index aa9bba90d..c6626202c 100644 --- a/tests/runtime-async/async/future-cancel-write/test.mbt +++ b/tests/runtime-async/async/future-cancel-write/test.mbt @@ -2,11 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn take_then_drop(x : @async.Future[String], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn take_then_drop(x : @async.CMFuture[String], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } ///| -pub async fn read_and_drop(x : @async.Future[String], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn read_and_drop(x : @async.CMFuture[String], task_group : @async.TaskGroup[Unit]) -> Unit { let _ = x.get() } diff --git a/tests/runtime-async/async/future-close-after-coming-back/runner.mbt b/tests/runtime-async/async/future-close-after-coming-back/runner.mbt index c2ba3c5d9..1a9982a25 100644 --- a/tests/runtime-async/async/future-close-after-coming-back/runner.mbt +++ b/tests/runtime-async/async/future-close-after-coming-back/runner.mbt @@ -5,6 +5,6 @@ ///| pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { - let returned = @theTest.f(@async.Future::ready(())) + let returned = @theTest.f(@async.CMFuture::ready(())) returned.drop() } diff --git a/tests/runtime-async/async/future-close-after-coming-back/test.mbt b/tests/runtime-async/async/future-close-after-coming-back/test.mbt index 7ecbc03ee..115e7e4cd 100644 --- a/tests/runtime-async/async/future-close-after-coming-back/test.mbt +++ b/tests/runtime-async/async/future-close-after-coming-back/test.mbt @@ -1,6 +1,6 @@ //@ [lang] //@ path = 'gen/interface/a/b/theTest/stub.mbt' -pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { - @async.Future::from(async fn() { param.get() }) +pub async fn f(param : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> @async.CMFuture[Unit] { + @async.CMFuture::from(async fn() { param.get() }) } diff --git a/tests/runtime-async/async/future-close-then-receive-read/test.mbt b/tests/runtime-async/async/future-close-then-receive-read/test.mbt index 74afda86b..9cc71b837 100644 --- a/tests/runtime-async/async/future-close-then-receive-read/test.mbt +++ b/tests/runtime-async/async/future-close-then-receive-read/test.mbt @@ -2,15 +2,15 @@ //@ path = 'gen/interface/a/b/theTest/stub.mbt' ///| -let slot : Ref[@async.Future[Unit]?] = { val: None } +let slot : Ref[@async.CMFuture[Unit]?] = { val: None } ///| -pub async fn set(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn set(param : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { slot.val = Some(param) } ///| -pub async fn get(task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { +pub async fn get(task_group : @async.TaskGroup[Unit]) -> @async.CMFuture[Unit] { guard slot.val is Some(value) slot.val = None value diff --git a/tests/runtime-async/async/future-closes-with-error/test.mbt b/tests/runtime-async/async/future-closes-with-error/test.mbt index 14bed46df..4baa6547e 100644 --- a/tests/runtime-async/async/future-closes-with-error/test.mbt +++ b/tests/runtime-async/async/future-closes-with-error/test.mbt @@ -2,7 +2,7 @@ //@ path = 'gen/interface/a/b/theTest/stub.mbt' ///| -pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn f(param : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { let _ = param.get() catch { @async.FutureReadError::Cancelled => () _ => panic() diff --git a/tests/runtime-async/async/future-write-then-read-comes-back/test.mbt b/tests/runtime-async/async/future-write-then-read-comes-back/test.mbt index 4e4094481..2ce9ee4ca 100644 --- a/tests/runtime-async/async/future-write-then-read-comes-back/test.mbt +++ b/tests/runtime-async/async/future-write-then-read-comes-back/test.mbt @@ -2,6 +2,6 @@ //@ path = 'gen/interface/a/b/theTest/stub.mbt' ///| -pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { +pub async fn f(param : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> @async.CMFuture[Unit] { param } diff --git a/tests/runtime-async/async/future-write-then-read-remote/test.mbt b/tests/runtime-async/async/future-write-then-read-remote/test.mbt index 6fb0d0ce8..53e57f21b 100644 --- a/tests/runtime-async/async/future-write-then-read-remote/test.mbt +++ b/tests/runtime-async/async/future-write-then-read-remote/test.mbt @@ -2,6 +2,6 @@ //@ path = 'gen/interface/a/b/theTest/stub.mbt' ///| -pub async fn f(param : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn f(param : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { param.get() } diff --git a/tests/runtime-async/async/incomplete-writes/test.mbt b/tests/runtime-async/async/incomplete-writes/test.mbt index 52942ec94..914192f2f 100644 --- a/tests/runtime-async/async/incomplete-writes/test.mbt +++ b/tests/runtime-async/async/incomplete-writes/test.mbt @@ -77,12 +77,12 @@ pub async fn short_reads_leaf( ///| pub async fn dropped_reader_test( - f1 : @async.Future[TestThing], - f2 : @async.Future[TestThing], + f1 : @async.CMFuture[TestThing], + f2 : @async.CMFuture[TestThing], task_group : @async.TaskGroup[Unit], -) -> (@async.Future[TestThing], @async.Future[TestThing]) { - let (out1, writer1) : (@async.Future[TestThing], @async.Promise[TestThing]) = @async.Future::new() - let (out2, writer2) : (@async.Future[TestThing], @async.Promise[TestThing]) = @async.Future::new() +) -> (@async.CMFuture[TestThing], @async.CMFuture[TestThing]) { + let (out1, writer1) : (@async.CMFuture[TestThing], @async.Promise[TestThing]) = @async.CMFuture::new() + let (out2, writer2) : (@async.CMFuture[TestThing], @async.Promise[TestThing]) = @async.CMFuture::new() task_group.spawn_bg(async fn() { f1.drop() let thing = f2.get() @@ -94,18 +94,18 @@ pub async fn dropped_reader_test( ///| pub async fn dropped_reader_leaf( - f1 : @async.Future[@leafInterface.LeafThing], - f2 : @async.Future[@leafInterface.LeafThing], + f1 : @async.CMFuture[@leafInterface.LeafThing], + f2 : @async.CMFuture[@leafInterface.LeafThing], task_group : @async.TaskGroup[Unit], -) -> (@async.Future[@leafInterface.LeafThing], @async.Future[@leafInterface.LeafThing]) { +) -> (@async.CMFuture[@leafInterface.LeafThing], @async.CMFuture[@leafInterface.LeafThing]) { let (out1, writer1) : ( - @async.Future[@leafInterface.LeafThing], + @async.CMFuture[@leafInterface.LeafThing], @async.Promise[@leafInterface.LeafThing], - ) = @async.Future::new() + ) = @async.CMFuture::new() let (out2, writer2) : ( - @async.Future[@leafInterface.LeafThing], + @async.CMFuture[@leafInterface.LeafThing], @async.Promise[@leafInterface.LeafThing], - ) = @async.Future::new() + ) = @async.CMFuture::new() task_group.spawn_bg(async fn() { f1.drop() let thing = f2.get() diff --git a/tests/runtime-async/async/moonbit-future-write/runner.mbt b/tests/runtime-async/async/moonbit-future-write/runner.mbt index d70b68bce..42dfb497e 100644 --- a/tests/runtime-async/async/moonbit-future-write/runner.mbt +++ b/tests/runtime-async/async/moonbit-future-write/runner.mbt @@ -10,4 +10,12 @@ pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { let done = @i.create_unit_future() done.get() + + let nested = @i.create_nested_future(7U) + let inner = nested.get() + assert_eq(inner.get(), 7U) + + let nested_record = @i.create_nested_future_record(9U) + let nested_inner = nested_record.nested.get() + assert_eq(nested_inner.get(), 9U) } diff --git a/tests/runtime-async/async/moonbit-future-write/runner.rs b/tests/runtime-async/async/moonbit-future-write/runner.rs index a8f9455e6..8048a7644 100644 --- a/tests/runtime-async/async/moonbit-future-write/runner.rs +++ b/tests/runtime-async/async/moonbit-future-write/runner.rs @@ -18,5 +18,17 @@ impl Guest for Component { // Test creating a unit future let rx = create_unit_future().await; rx.await; + + // Test future> + let outer = create_nested_future(7).await; + let inner = outer.await; + let nested_value = inner.await; + assert_eq!(nested_value, 7); + + // Test record containing future> + let record = create_nested_future_record(9).await; + let record_inner = record.nested.await; + let record_value = record_inner.await; + assert_eq!(record_value, 9); } } diff --git a/tests/runtime-async/async/moonbit-future-write/test.mbt b/tests/runtime-async/async/moonbit-future-write/test.mbt index 1989f05c9..ac2326267 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.mbt +++ b/tests/runtime-async/async/moonbit-future-write/test.mbt @@ -2,11 +2,21 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.Future[UInt] { - @async.Future::ready(value) +pub async fn create_future_with_value(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.CMFuture[UInt] { + @async.CMFuture::ready(value) } ///| -pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.Future[Unit] { - @async.Future::ready(()) +pub async fn create_unit_future(task_group : @async.TaskGroup[Unit]) -> @async.CMFuture[Unit] { + @async.CMFuture::ready(()) +} + +///| +pub async fn create_nested_future(value : UInt, task_group : @async.TaskGroup[Unit]) -> @async.CMFuture[@async.CMFuture[UInt]] { + @async.CMFuture::ready(@async.CMFuture::ready(value)) +} + +///| +pub async fn create_nested_future_record(value : UInt, task_group : @async.TaskGroup[Unit]) -> NestedFutureRecord { + { nested: @async.CMFuture::ready(@async.CMFuture::ready(value)) } } diff --git a/tests/runtime-async/async/moonbit-future-write/test.wit b/tests/runtime-async/async/moonbit-future-write/test.wit index 660de8193..4e6510936 100644 --- a/tests/runtime-async/async/moonbit-future-write/test.wit +++ b/tests/runtime-async/async/moonbit-future-write/test.wit @@ -5,6 +5,14 @@ interface i { create-future-with-value: async func(value: u32) -> future; // MoonBit creates a future without a payload create-unit-future: async func() -> future; + // MoonBit returns nested future handles + create-nested-future: async func(value: u32) -> future>; + + record nested-future-record { + nested: future>, + } + // MoonBit returns a record containing nested futures + create-nested-future-record: async func(value: u32) -> nested-future-record; } world test { diff --git a/tests/runtime-async/async/pending-import/test.mbt b/tests/runtime-async/async/pending-import/test.mbt index 00e1fc02a..b69058ede 100644 --- a/tests/runtime-async/async/pending-import/test.mbt +++ b/tests/runtime-async/async/pending-import/test.mbt @@ -2,6 +2,6 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn pending_import(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn pending_import(x : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.get() } diff --git a/tests/runtime-async/async/ping-pong/test.mbt b/tests/runtime-async/async/ping-pong/test.mbt index 35903b3de..e2d801bf4 100644 --- a/tests/runtime-async/async/ping-pong/test.mbt +++ b/tests/runtime-async/async/ping-pong/test.mbt @@ -3,15 +3,15 @@ ///| pub async fn ping( - x : @async.Future[String], + x : @async.CMFuture[String], y : String, task_group : @async.TaskGroup[Unit], -) -> @async.Future[String] { +) -> @async.CMFuture[String] { let message = x.get() + y - @async.Future::from(async fn() { message }) + @async.CMFuture::from(async fn() { message }) } ///| -pub async fn pong(x : @async.Future[String], task_group : @async.TaskGroup[Unit]) -> String { +pub async fn pong(x : @async.CMFuture[String], task_group : @async.TaskGroup[Unit]) -> String { x.get() } diff --git a/tests/runtime-async/async/simple-future/runner.mbt b/tests/runtime-async/async/simple-future/runner.mbt index d40c345f3..d1247c61b 100644 --- a/tests/runtime-async/async/simple-future/runner.mbt +++ b/tests/runtime-async/async/simple-future/runner.mbt @@ -5,6 +5,6 @@ ///| pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { - @i.read_future(@async.Future::ready(())) - @i.drop_future(@async.Future::ready(())) + @i.read_future(@async.CMFuture::ready(())) + @i.drop_future(@async.CMFuture::ready(())) } diff --git a/tests/runtime-async/async/simple-future/test.mbt b/tests/runtime-async/async/simple-future/test.mbt index 4b2dccb91..640bd04d4 100644 --- a/tests/runtime-async/async/simple-future/test.mbt +++ b/tests/runtime-async/async/simple-future/test.mbt @@ -2,11 +2,11 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn read_future(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn read_future(x : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.get() } ///| -pub async fn drop_future(x : @async.Future[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn drop_future(x : @async.CMFuture[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { x.drop() } From aba0b319c07087eb528191cd160bfdc0ad6c34bf Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 17:15:55 +0800 Subject: [PATCH 53/61] moonbit: split Stream and CMStream for async ABI safety --- crates/moonbit/src/async/trait.mbt | 148 +++++++++++++----- crates/moonbit/src/async_support.rs | 6 +- crates/moonbit/src/lib.rs | 2 +- crates/moonbit/src/pkg.rs | 2 +- .../async/incomplete-writes/test.mbt | 12 +- .../async/moonbit-stream-write/test.mbt | 8 +- .../async/simple-stream-payload/runner.mbt | 2 +- .../async/simple-stream-payload/test.mbt | 2 +- .../async/simple-stream/runner.mbt | 2 +- .../async/simple-stream/test.mbt | 2 +- 10 files changed, 128 insertions(+), 58 deletions(-) diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index a1c8128d3..af1ded4ae 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -444,36 +444,92 @@ pub async fn[X] CMFuture::drop(self : CMFuture[X]) -> Unit { } ///| -pub(all) struct StreamOutInner[X] { +pub struct Stream[X] { + id : Int + state : Ref[LocalStreamState[X]] +} + +///| +pub impl[X] Eq for Stream[X] with equal(self, other) -> Bool { + self.id == other.id +} + +///| +pub impl[X] Show for Stream[X] with output(self, logger) { + logger.write_string("Stream(") + logger.write_string(self.id.to_string()) + logger.write_string(")") +} + +///| +pub fn[X] Stream::new(capacity? : Int = 0) -> (Stream[X], Sink[X]) { + let cap = if capacity < 0 { 0 } else { capacity } + let state : Ref[LocalStreamState[X]] = { + val: { + capacity: cap, + chunks: @deque.new(), + buffered: 0, + closed: false, + head: None, + head_pos: 0, + readers: @deque.new(), + writers: @deque.new(), + }, + } + let stream = Stream::{ id: fresh_id(), state } + let sink = Sink::{ + write: async fn(data : ArrayView[X]) { local_stream_write(state, data) }, + close: async fn() { local_stream_close(state) }, + } + (stream, sink) +} + +///| +pub async fn[X] Stream::read(self : Stream[X], count : Int) -> ArrayView[X]? { + local_stream_read(self.state, count) +} + +///| +pub async fn[X] Stream::close(self : Stream[X]) -> Unit { + local_stream_close(self.state) +} + +///| +pub fn[X] Stream::to_cm(self : Stream[X]) -> CMStream[X] { + CMStream::from_local(self) +} + +///| +pub(all) struct CMStreamOutInner[X] { mut producer : (async (Sink[X]) -> Unit)? } ///| -pub(all) struct StreamOut[X] { +pub(all) struct CMStreamOut[X] { id : Int - inner : Ref[StreamOutInner[X]] + inner : Ref[CMStreamOutInner[X]] } ///| -pub fn[X] StreamOut::new(producer : async (Sink[X]) -> Unit) -> StreamOut[X] { +pub fn[X] CMStreamOut::new(producer : async (Sink[X]) -> Unit) -> CMStreamOut[X] { { id: fresh_id(), inner: { val: { producer: Some(producer) } } } } ///| -pub fn[X] StreamOut::take_producer(self : StreamOut[X]) -> (async (Sink[X]) -> Unit) { +pub fn[X] CMStreamOut::take_producer(self : CMStreamOut[X]) -> (async (Sink[X]) -> Unit) { guard self.inner.val.producer is Some(p) self.inner.val.producer = None p } ///| -pub(all) enum Stream[X] { +pub(all) enum CMStream[X] { Incoming(StreamR[X]) - Outgoing(StreamOut[X]) + Outgoing(CMStreamOut[X]) } ///| -pub impl[X] Eq for Stream[X] with equal(self, other) -> Bool { +pub impl[X] Eq for CMStream[X] with equal(self, other) -> Bool { match self { Incoming(s) => match other { @@ -489,15 +545,15 @@ pub impl[X] Eq for Stream[X] with equal(self, other) -> Bool { } ///| -pub impl[X] Show for Stream[X] with output(self, logger) { +pub impl[X] Show for CMStream[X] with output(self, logger) { match self { Incoming(s) => { - logger.write_string("Stream::Incoming(") + logger.write_string("CMStream::Incoming(") logger.write_string(s.handle.to_string()) logger.write_string(")") } Outgoing(s) => { - logger.write_string("Stream::Outgoing(") + logger.write_string("CMStream::Outgoing(") logger.write_string(s.id.to_string()) logger.write_string(")") } @@ -505,41 +561,55 @@ pub impl[X] Show for Stream[X] with output(self, logger) { } ///| -pub fn[X] Stream::from(producer : async (Sink[X]) -> Unit) -> Stream[X] { - Stream::Outgoing(StreamOut::new(producer)) +pub fn[X] CMStream::from(producer : async (Sink[X]) -> Unit) -> CMStream[X] { + CMStream::Outgoing(CMStreamOut::new(producer)) } ///| -pub fn[X] Stream::new(capacity? : Int = 0) -> (Stream[X], Sink[X]) { - let cap = if capacity < 0 { 0 } else { capacity } - let state : Ref[LocalStreamState[X]] = { - val: { - capacity: cap, - chunks: @deque.new(), - buffered: 0, - closed: false, - head: None, - head_pos: 0, - readers: @deque.new(), - writers: @deque.new(), - }, +async fn[X] cm_stream_write_all(sink : Sink[X], data : ArrayView[X]) -> Bool { + if data.length() == 0 { + return true } - let handle = fresh_id() - let stream = Stream::Incoming(StreamR::{ - handle, - read: async fn(count : Int) { local_stream_read(state, count) }, - close: async fn() { local_stream_close(state) }, - take_handle: fn() { panic() }, - }) - let sink = Sink::{ - write: async fn(data : ArrayView[X]) { local_stream_write(state, data) }, - close: async fn() { local_stream_close(state) }, + let pending : Array[X] = [] + for i = 0; i < data.length(); i = i + 1 { + pending.push(data[i]) + } + let mut offset = 0 + for { + if offset >= pending.length() { + return true + } + let written = sink.write(pending[offset:]) + if written <= 0 { + return false + } + offset = offset + written } - (stream, sink) } ///| -pub async fn[X] Stream::read(self : Stream[X], count : Int) -> ArrayView[X]? { +pub fn[X] CMStream::from_local(stream : Stream[X], chunk_size? : Int = 64) -> CMStream[X] { + let read_size = if chunk_size <= 0 { 64 } else { chunk_size } + CMStream::Outgoing(CMStreamOut::new(async fn(sink : Sink[X]) { + for { + match stream.read(read_size) { + Some(data) => { + if not(cm_stream_write_all(sink, data)) { + stream.close() + return + } + } + None => { + sink.close() + return + } + } + } + })) +} + +///| +pub async fn[X] CMStream::read(self : CMStream[X], count : Int) -> ArrayView[X]? { match self { Incoming(s) => s.read(count) Outgoing(_) => panic() @@ -547,7 +617,7 @@ pub async fn[X] Stream::read(self : Stream[X], count : Int) -> ArrayView[X]? { } ///| -pub async fn[X] Stream::close(self : Stream[X]) -> Unit { +pub async fn[X] CMStream::close(self : CMStream[X]) -> Unit { match self { Incoming(s) => s.close() Outgoing(_) => () diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 7c68e03e0..c132995db 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -583,7 +583,7 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ wasmLift{camel_name}{index}DropReadable(stream_handle) }} }} - {async_qualifier}Stream::Incoming({async_qualifier}StreamR::{{ + {async_qualifier}CMStream::Incoming({async_qualifier}StreamR::{{ handle: stream_handle, read: fn (count : Int) {{ if closed {{ @@ -680,8 +680,8 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ r#" fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ match stream {{ - {async_qualifier}Stream::Incoming(s) => s.take_handle() - {async_qualifier}Stream::Outgoing(s) => {{ + {async_qualifier}CMStream::Incoming(s) => s.take_handle() + {async_qualifier}CMStream::Outgoing(s) => {{ let handles = wasmLower{camel_name}{index}New() let readable = (handles & 0xFFFFFFFF).to_int() let writable = (handles >> 32).to_int() diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index b2ab45cd4..966950532 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1498,7 +1498,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { } fn type_stream(&mut self, _id: TypeId, _name: &str, _ty: &Option, _docs: &Docs) { - // Not needed. They will become `StreamR[T]` in MoonBit. + // Not needed. They will become `CMStream[T]` in MoonBit. } fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) { diff --git a/crates/moonbit/src/pkg.rs b/crates/moonbit/src/pkg.rs index b11a63b58..96ef9222d 100644 --- a/crates/moonbit/src/pkg.rs +++ b/crates/moonbit/src/pkg.rs @@ -262,7 +262,7 @@ impl PkgResolver { TypeDefKind::Stream(ty) => { let qualifier = self.qualify_package(this, ASYNC_DIR); format!( - "{}Stream[{}]", + "{}CMStream[{}]", qualifier, ty.as_ref() .map(|t| self.type_name(this, t)) diff --git a/tests/runtime-async/async/incomplete-writes/test.mbt b/tests/runtime-async/async/incomplete-writes/test.mbt index 914192f2f..01bed3bf2 100644 --- a/tests/runtime-async/async/incomplete-writes/test.mbt +++ b/tests/runtime-async/async/incomplete-writes/test.mbt @@ -27,10 +27,10 @@ pub fn TestThing::get(self : TestThing) -> String { ///| pub async fn short_reads_test( - s : @async.Stream[TestThing], + s : @async.CMStream[TestThing], task_group : @async.TaskGroup[Unit], -) -> @async.Stream[TestThing] { - @async.Stream::from(async fn(sink : @async.Sink[TestThing]) { +) -> @async.CMStream[TestThing] { + @async.CMStream::from(async fn(sink : @async.Sink[TestThing]) { let things : Array[TestThing] = [] for { match s.read(1) { @@ -52,10 +52,10 @@ pub async fn short_reads_test( ///| pub async fn short_reads_leaf( - s : @async.Stream[@leafInterface.LeafThing], + s : @async.CMStream[@leafInterface.LeafThing], task_group : @async.TaskGroup[Unit], -) -> @async.Stream[@leafInterface.LeafThing] { - @async.Stream::from(async fn(sink : @async.Sink[@leafInterface.LeafThing]) { +) -> @async.CMStream[@leafInterface.LeafThing] { + @async.CMStream::from(async fn(sink : @async.Sink[@leafInterface.LeafThing]) { let things : Array[@leafInterface.LeafThing] = [] for { match s.read(1) { diff --git a/tests/runtime-async/async/moonbit-stream-write/test.mbt b/tests/runtime-async/async/moonbit-stream-write/test.mbt index 7e6a5ad6e..b151beaef 100644 --- a/tests/runtime-async/async/moonbit-stream-write/test.mbt +++ b/tests/runtime-async/async/moonbit-stream-write/test.mbt @@ -2,8 +2,8 @@ //@ path = 'gen/interface/my/test_/i/stub.mbt' ///| -pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.Stream[UInt] { - @async.Stream::from(async fn(sink : @async.Sink[UInt]) { +pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.CMStream[UInt] { + @async.CMStream::from(async fn(sink : @async.Sink[UInt]) { for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[UInt] = [i.reinterpret_as_uint()] let _ = sink.write(arr[:]) @@ -13,8 +13,8 @@ pub async fn create_stream_with_values(count : UInt, task_group : @async.TaskGro } ///| -pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.Stream[Unit] { - @async.Stream::from(async fn(sink : @async.Sink[Unit]) { +pub async fn create_unit_stream(count : UInt, task_group : @async.TaskGroup[Unit]) -> @async.CMStream[Unit] { + @async.CMStream::from(async fn(sink : @async.Sink[Unit]) { for i = 0; i < count.reinterpret_as_int(); i = i + 1 { let arr : Array[Unit] = [()] let _ = sink.write(arr[:]) diff --git a/tests/runtime-async/async/simple-stream-payload/runner.mbt b/tests/runtime-async/async/simple-stream-payload/runner.mbt index ed56ffd40..629640d5c 100644 --- a/tests/runtime-async/async/simple-stream-payload/runner.mbt +++ b/tests/runtime-async/async/simple-stream-payload/runner.mbt @@ -5,7 +5,7 @@ ///| pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { - let stream = @async.Stream::from(async fn(sink : @async.Sink[Byte]) { + let stream = @async.CMStream::from(async fn(sink : @async.Sink[Byte]) { let a : Array[Byte] = [(0).to_byte()] let b : Array[Byte] = [(1).to_byte(), (2).to_byte()] let c : Array[Byte] = [(3).to_byte()] diff --git a/tests/runtime-async/async/simple-stream-payload/test.mbt b/tests/runtime-async/async/simple-stream-payload/test.mbt index ec40896ad..8e7be83ae 100644 --- a/tests/runtime-async/async/simple-stream-payload/test.mbt +++ b/tests/runtime-async/async/simple-stream-payload/test.mbt @@ -1,7 +1,7 @@ //@ [lang] //@ path = 'gen/interface/my/test_/i/stub.mbt' -pub async fn read_stream(x : @async.Stream[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn read_stream(x : @async.CMStream[Byte], task_group : @async.TaskGroup[Unit]) -> Unit { guard x.read(1) is Some(a) guard a.length() == 1 guard a[0] == (0).to_byte() diff --git a/tests/runtime-async/async/simple-stream/runner.mbt b/tests/runtime-async/async/simple-stream/runner.mbt index b656c2a24..b0642bd09 100644 --- a/tests/runtime-async/async/simple-stream/runner.mbt +++ b/tests/runtime-async/async/simple-stream/runner.mbt @@ -5,7 +5,7 @@ ///| pub async fn run(task_group : @async.TaskGroup[Unit]) -> Unit { - let stream = @async.Stream::from(async fn(sink : @async.Sink[Unit]) { + let stream = @async.CMStream::from(async fn(sink : @async.Sink[Unit]) { let first : Array[Unit] = [()] let second : Array[Unit] = [(), ()] assert_eq(sink.write(first[:]), 1) diff --git a/tests/runtime-async/async/simple-stream/test.mbt b/tests/runtime-async/async/simple-stream/test.mbt index 325a4ab23..1830c4aa1 100644 --- a/tests/runtime-async/async/simple-stream/test.mbt +++ b/tests/runtime-async/async/simple-stream/test.mbt @@ -1,7 +1,7 @@ //@ [lang] //@ path = 'gen/interface/my/test_/i/stub.mbt' -pub async fn read_stream(x : @async.Stream[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { +pub async fn read_stream(x : @async.CMStream[Unit], task_group : @async.TaskGroup[Unit]) -> Unit { guard x.read(1) is Some(a) guard a.length() == 1 guard x.read(2) is Some(b) From c6bb75de608e7e8c29f747d9adbe3faed0d53b53 Mon Sep 17 00:00:00 2001 From: yezihang Date: Fri, 27 Feb 2026 18:36:53 +0800 Subject: [PATCH 54/61] moonbit: reduce async template warnings and clean generated stubs --- crates/moonbit/src/async/async_abi.mbt | 2 +- crates/moonbit/src/async/coroutine.mbt | 2 +- crates/moonbit/src/async/ev.mbt | 1 - crates/moonbit/src/async/moon.pkg.json | 10 ++- crates/moonbit/src/async/task_group.mbt | 2 +- crates/moonbit/src/async/trait.mbt | 18 ++-- crates/moonbit/src/async_support.rs | 10 +-- crates/moonbit/src/lib.rs | 106 ++++++++++++++++++------ 8 files changed, 108 insertions(+), 43 deletions(-) diff --git a/crates/moonbit/src/async/async_abi.mbt b/crates/moonbit/src/async/async_abi.mbt index 1d6c7ddc3..0e7423f88 100644 --- a/crates/moonbit/src/async/async_abi.mbt +++ b/crates/moonbit/src/async/async_abi.mbt @@ -207,7 +207,7 @@ fn CallbackCode::encode(self : Self) -> Int { } ///| -fn CallbackCode::decode(int : Int) -> CallbackCode { +fn CallbackCode::_decode(int : Int) -> CallbackCode { let id = int >> 4 match int & 0xf { 0 => Completed diff --git a/crates/moonbit/src/async/coroutine.mbt b/crates/moonbit/src/async/coroutine.mbt index e8c3f49b8..b46a32942 100644 --- a/crates/moonbit/src/async/coroutine.mbt +++ b/crates/moonbit/src/async/coroutine.mbt @@ -112,7 +112,7 @@ pub fn spawn(f : async () -> Unit) -> Coroutine { spawner: inherited_spawner, } fn run(_) { - run_async(fn() { + run_async(() => { coro.shielded = false if coro.cancelled { coro.state = Fail(Cancelled::Cancelled) diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt index ac4ee67b0..0e90b23be 100644 --- a/crates/moonbit/src/async/ev.mbt +++ b/crates/moonbit/src/async/ev.mbt @@ -355,7 +355,6 @@ pub async fn suspend_for_stream_write(idx : Int, val : Int) -> (Int, Bool) { ///| pub suberror OpCancelled { SubTaskCancelled(before_started~ : Bool) - StreamWriteCancelled StreamReadCancelled FutureWriteCancelled } diff --git a/crates/moonbit/src/async/moon.pkg.json b/crates/moonbit/src/async/moon.pkg.json index b7a5c45c3..504a690ce 100644 --- a/crates/moonbit/src/async/moon.pkg.json +++ b/crates/moonbit/src/async/moon.pkg.json @@ -1 +1,9 @@ -{ "warn-list": "-44", "supported-targets": ["wasm"] } \ No newline at end of file +{ + "warn-list": "-44", + "import": [ + { "path": "moonbitlang/core/deque", "alias": "deque" }, + { "path": "moonbitlang/core/ref", "alias": "ref" }, + { "path": "moonbitlang/core/set", "alias": "set" } + ], + "supported-targets": ["wasm"] +} diff --git a/crates/moonbit/src/async/task_group.mbt b/crates/moonbit/src/async/task_group.mbt index d0c487a92..25d49d3c7 100644 --- a/crates/moonbit/src/async/task_group.mbt +++ b/crates/moonbit/src/async/task_group.mbt @@ -206,7 +206,7 @@ pub async fn[X] with_task_group(f : async (TaskGroup[X]) -> X) -> X { defer { curr.spawner = prev } - tg.spawn_bg(fn() { + tg.spawn_bg(() => { let value = f(tg) if tg.result is None { tg.result = Some(value) diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt index af1ded4ae..ba65f10f8 100644 --- a/crates/moonbit/src/async/trait.mbt +++ b/crates/moonbit/src/async/trait.mbt @@ -88,7 +88,7 @@ fn fresh_id() -> Int { ///| ///| -struct Waiter { +priv struct Waiter { mut coro : Coroutine? } @@ -312,8 +312,8 @@ pub fn[X] Future::new() -> (Future[X], Promise[X]) { } let future = Future::{ id: fresh_id(), state } let promise = Promise::{ - write: async fn(value : X) { local_future_write(state, value) }, - close: async fn() { local_future_close(state) }, + write: (value : X) => { local_future_write(state, value) }, + close: () => { local_future_close(state) }, } (future, promise) } @@ -336,7 +336,7 @@ pub async fn[X] Future::get(self : Future[X]) -> X { } ///| -pub async fn[X] Future::drop(self : Future[X]) -> Unit { +pub fn[X] Future::drop(self : Future[X]) -> Unit { local_future_close(self.state) } @@ -414,12 +414,12 @@ pub fn[X] CMFuture::new() -> (CMFuture[X], Promise[X]) { ///| pub fn[X] CMFuture::ready(value : X) -> CMFuture[X] { - CMFuture::Outgoing(CMFutureOut::new(async fn() { value })) + CMFuture::Outgoing(CMFutureOut::new(() => value)) } ///| pub fn[X] CMFuture::from_local(future : Future[X]) -> CMFuture[X] { - CMFuture::Outgoing(CMFutureOut::new(async fn() { future.get() })) + CMFuture::Outgoing(CMFutureOut::new(() => future.get())) } ///| @@ -478,8 +478,8 @@ pub fn[X] Stream::new(capacity? : Int = 0) -> (Stream[X], Sink[X]) { } let stream = Stream::{ id: fresh_id(), state } let sink = Sink::{ - write: async fn(data : ArrayView[X]) { local_stream_write(state, data) }, - close: async fn() { local_stream_close(state) }, + write: (data : ArrayView[X]) => { local_stream_write(state, data) }, + close: () => { local_stream_close(state) }, } (stream, sink) } @@ -490,7 +490,7 @@ pub async fn[X] Stream::read(self : Stream[X], count : Int) -> ArrayView[X]? { } ///| -pub async fn[X] Stream::close(self : Stream[X]) -> Unit { +pub fn[X] Stream::close(self : Stream[X]) -> Unit { local_stream_close(self.state) } diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index c132995db..b3b5583a2 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -324,7 +324,7 @@ fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[future-drop-r r#" fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[future-new-{index}]{func_name}" fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int) -> Int = "{module}" "[future-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" +fn _wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[future-cancel-write-{index}]{func_name}" fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[future-drop-writable-{index}]{func_name}" "# ); @@ -364,7 +364,7 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ }} {async_qualifier}CMFuture::Incoming({async_qualifier}FutureR::{{ handle: future_handle, - get: fn () {{ + get: () => {{ if result is Some(r) {{ return r }} @@ -549,7 +549,7 @@ fn wasmLift{camel_name}{index}DropReadable(_ : Int) = "{module}" "[stream-drop-r r#" fn wasmLower{camel_name}{index}New() -> UInt64 = "{module}" "[stream-new-{index}]{func_name}" fn wasmLower{camel_name}{index}Write(handle : Int, ptr : Int, len : Int) -> Int = "{module}" "[stream-write-{index}]{func_name}" -fn wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[stream-cancel-write-{index}]{func_name}" +fn _wasmLower{camel_name}{index}CancelWrite(_ : Int) -> Int = "{module}" "[stream-cancel-write-{index}]{func_name}" fn wasmLower{camel_name}{index}DropWritable(_ : Int) = "{module}" "[stream-drop-writable-{index}]{func_name}" "# ); @@ -585,7 +585,7 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ }} {async_qualifier}CMStream::Incoming({async_qualifier}StreamR::{{ handle: stream_handle, - read: fn (count : Int) {{ + read: (count : Int) => {{ if closed {{ return None }}"# @@ -764,7 +764,7 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ lower, r#" }}, - close: async fn () {{ + close: () => {{ if !closed {{ closed = true wasmLower{camel_name}{index}DropWritable(writable) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 966950532..76ca435c7 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -132,6 +132,33 @@ pub struct MoonBit { } impl MoonBit { + fn builtin_symbol(builtin: &str) -> Option<&str> { + builtin.lines().find_map(|line| { + let trimmed = line.trim(); + let rest = trimmed + .strip_prefix("extern \"wasm\" fn ") + .or_else(|| trimmed.strip_prefix("pub fn "))?; + rest.split('(').next().map(str::trim) + }) + } + + fn emit_used_builtins<'a>( + ffi: &mut Source, + ffi_body: &str, + builtins: impl IntoIterator, + ) { + let mut used = builtins.into_iter().copied().collect::>(); + used.sort_unstable(); + for builtin in used { + let should_emit = MoonBit::builtin_symbol(builtin) + .map(|symbol| ffi_body.contains(&format!("{symbol}("))) + .unwrap_or(true); + if should_emit { + uwriteln!(ffi, "{builtin}"); + } + } + } + fn interface<'a>( &'a mut self, resolve: &'a Resolve, @@ -281,7 +308,7 @@ impl WorldGenerator for MoonBit { if let Some(content) = &resolve.interfaces[id].docs.contents && !content.is_empty() { - files.push(&format!("{}/README.md", directory), content.as_bytes()); + files.push(&format!("{directory}/README.md"), content.as_bytes()); } assert!(fragment.stub.is_empty()); @@ -295,9 +322,8 @@ impl WorldGenerator for MoonBit { let mut ffi = Source::default(); wit_bindgen_core::generated_preamble(&mut ffi, VERSION); uwriteln!(ffi, "{}", fragment.ffi); - for builtin in fragment.builtins { - uwriteln!(ffi, "{}", builtin); - } + let builtin_refs = format!("{}\n{}", fragment.src, fragment.ffi); + MoonBit::emit_used_builtins(&mut ffi, &builtin_refs, fragment.builtins.iter()); files.push(&format!("{directory}/ffi.mbt"), indent(&ffi).as_bytes()); // moon.pkg.json @@ -359,7 +385,7 @@ impl WorldGenerator for MoonBit { if let Some(content) = &resolve.worlds[world].docs.contents && !content.is_empty() { - files.push(&format!("{}/README.md", directory), content.as_bytes()); + files.push(&format!("{directory}/README.md"), content.as_bytes()); } // Source let mut src = Source::default(); @@ -372,9 +398,11 @@ impl WorldGenerator for MoonBit { wit_bindgen_core::generated_preamble(&mut ffi, VERSION); uwriteln!(ffi, "{}", self.import_world_fragment.ffi); builtins.extend(self.import_world_fragment.builtins.iter()); - for b in builtins.iter() { - uwriteln!(ffi, "{}", b); - } + let builtin_refs = format!( + "{}\n{}", + self.import_world_fragment.src, self.import_world_fragment.ffi + ); + MoonBit::emit_used_builtins(&mut ffi, &builtin_refs, builtins.iter()); files.push( &format!("{directory}/ffi_import.mbt"), indent(&ffi).as_bytes(), @@ -456,9 +484,8 @@ impl WorldGenerator for MoonBit { wit_bindgen_core::generated_preamble(&mut ffi, VERSION); uwriteln!(&mut ffi, "{}", fragment.ffi); - for b in fragment.builtins.iter() { - uwriteln!(ffi, "{}", b); - } + let builtin_refs = format!("{}\n{}", fragment.src, fragment.ffi); + MoonBit::emit_used_builtins(&mut ffi, &builtin_refs, fragment.builtins.iter()); files.push(&format!("{directory}/ffi.mbt",), indent(&ffi).as_bytes()); } @@ -514,9 +541,8 @@ impl WorldGenerator for MoonBit { let mut export = Source::default(); wit_bindgen_core::generated_preamble(&mut export, VERSION); uwriteln!(&mut export, "{}", fragment.ffi); - for b in fragment.builtins.iter() { - uwriteln!(&mut export, "{}", b); - } + let builtin_refs = format!("{}\n{}", fragment.src, fragment.ffi); + MoonBit::emit_used_builtins(&mut export, &builtin_refs, fragment.builtins.iter()); files.push(&format!("{directory}/ffi.mbt",), indent(&export).as_bytes()); } @@ -556,11 +582,25 @@ impl WorldGenerator for MoonBit { ); let mut moon_pkg = Source::default(); - self.write_moon_pkg( - &mut moon_pkg, - self.pkg_resolver.package_import.get(&self.opts.r#gen_dir), - true, - ); + let mut filtered_imports = Imports::default(); + if let Some(imports) = self.pkg_resolver.package_import.get(&self.opts.r#gen_dir) { + for (path, alias) in imports.packages.iter() { + // The root `gen` package doesn't reference async helpers directly; + // interface subpackages import async on their own. + if alias == "async" || path.ends_with("/async") { + continue; + } + filtered_imports + .packages + .insert(path.to_string(), alias.to_string()); + } + } + let imports = if filtered_imports.packages.is_empty() { + None + } else { + Some(&filtered_imports) + }; + self.write_moon_pkg(&mut moon_pkg, imports, true); files.push( &format!("{}/moon.pkg.json", self.opts.r#gen_dir), indent(&moon_pkg).as_bytes(), @@ -704,7 +744,7 @@ impl InterfaceGenerator<'_> { let src = bindgen.src.clone(); let cleanup_list = if bindgen.needs_cleanup_list { - "let cleanup_list : Array[Int] = []" + "let _cleanup_list : Array[Int] = []" } else { "" }; @@ -742,13 +782,31 @@ impl InterfaceGenerator<'_> { { let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); let func_sig = self.sig_string_with_direction(&mbt_sig, async_, Direction::Export); + let mut ignored_params = mbt_sig + .params + .iter() + .map(|(name, _)| format!("ignore({name})")) + .collect::>(); + if async_ { + ignored_params.push("ignore(task_group)".to_string()); + } + let ignored_params = if ignored_params.is_empty() { + String::new() + } else { + format!("{}\n", ignored_params.join("\n")) + }; + let async_marker = if async_ { + "@async.protect_from_cancel(() => ())\n" + } else { + "" + }; print_docs(&mut self.stub, &func.docs); uwrite!( self.stub, r#" {func_sig} {{ - ... + {ignored_params}{async_marker}abort("not implemented") }} "# ); @@ -783,7 +841,7 @@ impl InterfaceGenerator<'_> { // Handle cleanup for both sync and async exports let cleanup_list = if bindgen.needs_cleanup_list { - "let cleanup_list : Array[Int] = []" + "let _cleanup_list : Array[Int] = []" } else { "" }; @@ -1206,7 +1264,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { r#" /// Destructor of the resource. pub fn {name}::dtor(_self : {name}) -> Unit {{ - ... + abort("not implemented") }} "# ); @@ -2586,7 +2644,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwrite!( self.src, " - cleanup_list.each(mbt_ffi_free) + _cleanup_list.each(mbt_ffi_free) ", ); } From 571078bf4dc37d8995aabf431a4ae7e500dc3b4a Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 3 Mar 2026 10:10:00 +0800 Subject: [PATCH 55/61] moonbit: make cleanup list demand-driven --- crates/moonbit/src/async_support.rs | 11 +++++++---- crates/moonbit/src/lib.rs | 30 ++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index b3b5583a2..7dad1e49b 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -131,6 +131,7 @@ impl<'a> InterfaceGenerator<'a> { param_names.into_boxed_slice(), Direction::Import, true, + false, ); let mut lowered_params = Vec::new(); @@ -402,7 +403,8 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ let operand = if let TypeDefKind::Future(Some(ty)) = self.resolve.types[ty].kind { // TODO : solve ownership let resolve = self.resolve.clone(); - let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Import, true); + let mut bindgen = + FunctionBindgen::new(self, Box::new([]), Direction::Import, true, false); let operand = lift_from_memory(&resolve, &mut bindgen, "ptr".to_string(), &ty); uwriteln!(lift, "{}", bindgen.src); operand @@ -458,7 +460,8 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export, true); + let mut bindgen = + FunctionBindgen::new(self, Box::new([]), Direction::Export, true, false); bindgen.use_ffi(ffi::MALLOC); bindgen.use_ffi(ffi::FREE); uwriteln!( @@ -594,7 +597,7 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); let mut lift_bindgen = - FunctionBindgen::new(self, Box::new([]), Direction::Import, true); + FunctionBindgen::new(self, Box::new([]), Direction::Import, true, false); lift_bindgen.use_ffi(ffi::MALLOC); lift_bindgen.use_ffi(ffi::FREE); @@ -706,7 +709,7 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ let resolve = self.resolve.clone(); let elem_type = self.world_gen.pkg_resolver.type_name(self.name, &inner_ty); let mut lower_bindgen = - FunctionBindgen::new(self, Box::new([]), Direction::Export, true); + FunctionBindgen::new(self, Box::new([]), Direction::Export, true, false); lower_bindgen.use_ffi(ffi::MALLOC); lower_bindgen.use_ffi(ffi::FREE); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 76ca435c7..c1b522a16 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -730,6 +730,7 @@ impl InterfaceGenerator<'_> { .collect(), Direction::Import, false, // sync import + true, ); abi::call( @@ -744,7 +745,7 @@ impl InterfaceGenerator<'_> { let src = bindgen.src.clone(); let cleanup_list = if bindgen.needs_cleanup_list { - "let _cleanup_list : Array[Int] = []" + "let cleanup_list : Array[Int] = []" } else { "" }; @@ -826,6 +827,7 @@ impl InterfaceGenerator<'_> { (0..sig.params.len()).map(|i| format!("p{i}")).collect(), Direction::Export, async_, + true, ); abi::call( @@ -841,7 +843,7 @@ impl InterfaceGenerator<'_> { // Handle cleanup for both sync and async exports let cleanup_list = if bindgen.needs_cleanup_list { - "let _cleanup_list : Array[Int] = []" + "let cleanup_list : Array[Int] = []" } else { "" }; @@ -1021,6 +1023,7 @@ impl InterfaceGenerator<'_> { (0..sig.results.len()).map(|i| format!("p{i}")).collect(), Direction::Export, false, // post-return is not async + true, ); abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen); @@ -1588,6 +1591,7 @@ struct FunctionBindgen<'a, 'b> { payloads: Vec, cleanup: Vec, needs_cleanup_list: bool, + defer_cleanup: bool, direction: Direction, async_: bool, } @@ -1598,6 +1602,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { params: Box<[String]>, direction: Direction, async_: bool, + defer_cleanup: bool, ) -> FunctionBindgen<'a, 'b> { let mut locals = Ns::default(); params.iter().for_each(|str| { @@ -1613,6 +1618,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { payloads: Vec::new(), cleanup: Vec::new(), needs_cleanup_list: false, + defer_cleanup, direction, async_, } @@ -2644,7 +2650,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwrite!( self.src, " - _cleanup_list.each(mbt_ffi_free) + cleanup_list.each(mbt_ffi_free) ", ); } @@ -3002,12 +3008,18 @@ impl Bindgen for FunctionBindgen<'_, '_> { let BlockStorage { body, cleanup } = self.block_storage.pop().unwrap(); if !self.cleanup.is_empty() { - self.needs_cleanup_list = true; - self.use_ffi(ffi::FREE); - - for cleanup in &self.cleanup { - let address = &cleanup.address; - uwriteln!(self.src, "cleanup_list.push({address})",); + if self.defer_cleanup { + self.needs_cleanup_list = true; + for cleanup in &self.cleanup { + let address = &cleanup.address; + uwriteln!(self.src, "cleanup_list.push({address})",); + } + } else { + self.use_ffi(ffi::FREE); + for cleanup in &self.cleanup { + let address = &cleanup.address; + uwriteln!(self.src, "mbt_ffi_free({address})",); + } } } From 847871f9fee84f2f2a04f92f64e718c5db3c7e74 Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 3 Mar 2026 17:51:15 +0800 Subject: [PATCH 56/61] fix(moonbit): handle fixed-length lists and cleanup builtins --- crates/moonbit/src/lib.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index c1b522a16..1222a0d4e 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -151,7 +151,18 @@ impl MoonBit { used.sort_unstable(); for builtin in used { let should_emit = MoonBit::builtin_symbol(builtin) - .map(|symbol| ffi_body.contains(&format!("{symbol}("))) + .map(|symbol| { + let needles = [ + format!("{symbol}("), + format!("{symbol})"), + format!("{symbol},"), + format!("{symbol} "), + format!("{symbol}\n"), + format!("{symbol};"), + format!("{symbol}]"), + ]; + needles.iter().any(|needle| ffi_body.contains(needle)) + }) .unwrap_or(true); if should_emit { uwriteln!(ffi, "{builtin}"); @@ -2440,11 +2451,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { for operand in operands.drain(0..(*size as usize)) { lifted.push(operand); } + let ty = self.resolve_type_name(element); if lifted.is_empty() { - let ty = self.resolve_type_name(element); results.push(format!("([] : FixedArray[{ty}])")); } else { - results.push(format!("[{}]", lifted.join(", "))); + results.push(format!("([{}] : FixedArray[{ty}])", lifted.join(", "))); } } @@ -2536,7 +2547,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if lifted.is_empty() { results.push(format!("([] : FixedArray[{ty}])")); } else { - results.push(format!("[{}]", lifted.join(", "))); + results.push(format!("([{}] : FixedArray[{ty}])", lifted.join(", "))); } } From d27e2db1f0946fa69af1a91f8c40f779c7f3b787 Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 3 Mar 2026 18:10:00 +0800 Subject: [PATCH 57/61] refactor(moonbit): emit builtins from explicit ffi usage --- crates/moonbit/src/lib.rs | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 1222a0d4e..e20af8dae 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -132,16 +132,6 @@ pub struct MoonBit { } impl MoonBit { - fn builtin_symbol(builtin: &str) -> Option<&str> { - builtin.lines().find_map(|line| { - let trimmed = line.trim(); - let rest = trimmed - .strip_prefix("extern \"wasm\" fn ") - .or_else(|| trimmed.strip_prefix("pub fn "))?; - rest.split('(').next().map(str::trim) - }) - } - fn emit_used_builtins<'a>( ffi: &mut Source, ffi_body: &str, @@ -150,23 +140,8 @@ impl MoonBit { let mut used = builtins.into_iter().copied().collect::>(); used.sort_unstable(); for builtin in used { - let should_emit = MoonBit::builtin_symbol(builtin) - .map(|symbol| { - let needles = [ - format!("{symbol}("), - format!("{symbol})"), - format!("{symbol},"), - format!("{symbol} "), - format!("{symbol}\n"), - format!("{symbol};"), - format!("{symbol}]"), - ]; - needles.iter().any(|needle| ffi_body.contains(needle)) - }) - .unwrap_or(true); - if should_emit { - uwriteln!(ffi, "{builtin}"); - } + let _ = ffi_body; // builtins are tracked explicitly via use_ffi + uwriteln!(ffi, "{builtin}"); } } From 59bf630d86b5da404abf003a463f659997e376af Mon Sep 17 00:00:00 2001 From: yezihang Date: Tue, 3 Mar 2026 18:43:35 +0800 Subject: [PATCH 58/61] fix(moonbit): make ffi builtin collection explicit and leak-free --- crates/moonbit/src/async_support.rs | 114 ++++++++++++++++++--------- crates/moonbit/src/lib.rs | 116 ++++++++++++++-------------- 2 files changed, 133 insertions(+), 97 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 7dad1e49b..5c673a7b7 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -100,8 +100,16 @@ impl AsyncSupport { } } -/// lift func name, lift, lower func name, lower -pub(crate) struct AsyncBinding(pub HashMap); +pub(crate) struct AsyncBindingEntry { + pub lift_name: String, + pub lift_src: String, + pub lift_builtins: HashSet<&'static str>, + pub lower_name: String, + pub lower_src: String, + pub lower_builtins: HashSet<&'static str>, +} + +pub(crate) struct AsyncBinding(pub HashMap); /// Async-specific helpers used by `InterfaceGenerator` to keep the main /// visitor implementation focused on shared lowering/lifting logic. @@ -250,7 +258,10 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte uwriteln!(bindgen.src, "return {lifted}"); } - bindgen.src + let builtins = bindgen.take_local_ffi_imports(); + let src = bindgen.src; + self.ffi_imports.extend(builtins); + src } /// Generate the async bindings for this function. @@ -290,7 +301,7 @@ defer {cleanup_params}()\n{async_pkg}suspend_for_subtask({subtask}, cleanup_afte index: usize, module: &str, func_name: &str, - ) -> (String, String, String, String) { + ) -> AsyncBindingEntry { let mut lift = Source::default(); let mut lower = Source::default(); @@ -400,17 +411,23 @@ fn wasmLift{camel_name}{index}(future_handle : Int) -> {lifted} {{ result = {{ "# ); - let operand = if let TypeDefKind::Future(Some(ty)) = self.resolve.types[ty].kind { - // TODO : solve ownership - let resolve = self.resolve.clone(); - let mut bindgen = - FunctionBindgen::new(self, Box::new([]), Direction::Import, true, false); - let operand = lift_from_memory(&resolve, &mut bindgen, "ptr".to_string(), &ty); - uwriteln!(lift, "{}", bindgen.src); - operand - } else { - "()".into() - }; + let (operand, lift_builtins) = + if let TypeDefKind::Future(Some(ty)) = self.resolve.types[ty].kind { + // TODO : solve ownership + let resolve = self.resolve.clone(); + let mut bindgen = + FunctionBindgen::new(self, Box::new([]), Direction::Import, true, false); + bindgen.use_ffi(ffi::MALLOC); + bindgen.use_ffi(ffi::FREE); + let operand = lift_from_memory(&resolve, &mut bindgen, "ptr".to_string(), &ty); + uwriteln!(lift, "{}", bindgen.src); + (operand, bindgen.take_local_ffi_imports()) + } else { + let mut builtins = HashSet::new(); + builtins.insert(ffi::MALLOC); + builtins.insert(ffi::FREE); + ("()".into(), builtins) + }; // lift from memory if it were actual data uwriteln!( @@ -458,7 +475,7 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ defer wasmLower{camel_name}{index}DropWritable(writable)"# ); - if let Some(inner_ty) = inner_type { + let lower_builtins = if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); let mut bindgen = FunctionBindgen::new(self, Box::new([]), Direction::Export, true, false); @@ -484,6 +501,7 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ r#" let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, ret_area)) catch {{ _ => false }}"# ); + bindgen.take_local_ffi_imports() } else { // Unit type - no value to write, just complete the future uwriteln!( @@ -492,7 +510,8 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ let _ = producer() let _ = {async_qualifier}suspend_for_future_write(writable, wasmLower{camel_name}{index}Write(writable, 0)) catch {{ _ => false }}"# ); - } + HashSet::new() + }; uwriteln!( lower, @@ -503,12 +522,15 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ }} }}"# ); - ( - lifted_func_name, - lift.to_string(), - lowered_func_name, - lower.to_string(), - ) + let lower_src = lower.to_string(); + AsyncBindingEntry { + lift_name: lifted_func_name, + lift_src: lift.to_string(), + lift_builtins, + lower_name: lowered_func_name, + lower_src, + lower_builtins, + } } pub(crate) fn generate_stream_binding( @@ -517,7 +539,7 @@ fn wasmLower{camel_name}{index}(future : {lifted}) -> Int {{ index: usize, module: &str, func_name: &str, - ) -> (String, String, String, String) { + ) -> AsyncBindingEntry { let mut lift = Source::default(); let mut lower = Source::default(); @@ -594,10 +616,15 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ }}"# ); - if let Some(inner_ty) = inner_type { + let lift_builtins = if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut lift_bindgen = - FunctionBindgen::new(self, Box::new([]), Direction::Import, true, false); + let mut lift_bindgen = FunctionBindgen::new( + self, + Box::new([]), + Direction::Import, + true, + false, + ); lift_bindgen.use_ffi(ffi::MALLOC); lift_bindgen.use_ffi(ffi::FREE); @@ -641,6 +668,7 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ if end {{ close() }} Some(items[:])"# ); + lift_bindgen.take_local_ffi_imports() } else { // Unit type stream uwriteln!( @@ -659,7 +687,8 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ if end {{ close() }} Some(result[:])"# ); - } + HashSet::new() + }; uwriteln!( lift, @@ -705,11 +734,16 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ }}"# ); - if let Some(inner_ty) = inner_type { + let lower_builtins = if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); let elem_type = self.world_gen.pkg_resolver.type_name(self.name, &inner_ty); - let mut lower_bindgen = - FunctionBindgen::new(self, Box::new([]), Direction::Export, true, false); + let mut lower_bindgen = FunctionBindgen::new( + self, + Box::new([]), + Direction::Export, + true, + false, + ); lower_bindgen.use_ffi(ffi::MALLOC); lower_bindgen.use_ffi(ffi::FREE); @@ -746,6 +780,7 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ }} progress"# ); + lower_bindgen.take_local_ffi_imports() } else { // Unit type stream uwriteln!( @@ -761,7 +796,8 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ }} progress"# ); - } + HashSet::new() + }; uwriteln!( lower, @@ -783,11 +819,13 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ }}"# ); - ( - lifted_func_name, - lift.to_string(), - lowered_func_name, - lower.to_string(), - ) + AsyncBindingEntry { + lift_name: lifted_func_name, + lift_src: lift.to_string(), + lift_builtins, + lower_name: lowered_func_name, + lower_src: lower.to_string(), + lower_builtins, + } } } diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index e20af8dae..59475324f 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -132,19 +132,6 @@ pub struct MoonBit { } impl MoonBit { - fn emit_used_builtins<'a>( - ffi: &mut Source, - ffi_body: &str, - builtins: impl IntoIterator, - ) { - let mut used = builtins.into_iter().copied().collect::>(); - used.sort_unstable(); - for builtin in used { - let _ = ffi_body; // builtins are tracked explicitly via use_ffi - uwriteln!(ffi, "{builtin}"); - } - } - fn interface<'a>( &'a mut self, resolve: &'a Resolve, @@ -308,8 +295,9 @@ impl WorldGenerator for MoonBit { let mut ffi = Source::default(); wit_bindgen_core::generated_preamble(&mut ffi, VERSION); uwriteln!(ffi, "{}", fragment.ffi); - let builtin_refs = format!("{}\n{}", fragment.src, fragment.ffi); - MoonBit::emit_used_builtins(&mut ffi, &builtin_refs, fragment.builtins.iter()); + for builtin in fragment.builtins { + uwriteln!(ffi, "{builtin}"); + } files.push(&format!("{directory}/ffi.mbt"), indent(&ffi).as_bytes()); // moon.pkg.json @@ -384,11 +372,9 @@ impl WorldGenerator for MoonBit { wit_bindgen_core::generated_preamble(&mut ffi, VERSION); uwriteln!(ffi, "{}", self.import_world_fragment.ffi); builtins.extend(self.import_world_fragment.builtins.iter()); - let builtin_refs = format!( - "{}\n{}", - self.import_world_fragment.src, self.import_world_fragment.ffi - ); - MoonBit::emit_used_builtins(&mut ffi, &builtin_refs, builtins.iter()); + for builtin in builtins { + uwriteln!(ffi, "{builtin}"); + } files.push( &format!("{directory}/ffi_import.mbt"), indent(&ffi).as_bytes(), @@ -470,8 +456,9 @@ impl WorldGenerator for MoonBit { wit_bindgen_core::generated_preamble(&mut ffi, VERSION); uwriteln!(&mut ffi, "{}", fragment.ffi); - let builtin_refs = format!("{}\n{}", fragment.src, fragment.ffi); - MoonBit::emit_used_builtins(&mut ffi, &builtin_refs, fragment.builtins.iter()); + for builtin in fragment.builtins.iter() { + uwriteln!(ffi, "{builtin}"); + } files.push(&format!("{directory}/ffi.mbt",), indent(&ffi).as_bytes()); } @@ -527,8 +514,9 @@ impl WorldGenerator for MoonBit { let mut export = Source::default(); wit_bindgen_core::generated_preamble(&mut export, VERSION); uwriteln!(&mut export, "{}", fragment.ffi); - let builtin_refs = format!("{}\n{}", fragment.src, fragment.ffi); - MoonBit::emit_used_builtins(&mut export, &builtin_refs, fragment.builtins.iter()); + for builtin in fragment.builtins.iter() { + uwriteln!(export, "{builtin}"); + } files.push(&format!("{directory}/ffi.mbt",), indent(&export).as_bytes()); } @@ -693,7 +681,7 @@ impl InterfaceGenerator<'_> { if async_ { let src = self.generate_async_import(func, &ffi_import_name, &wasm_sig); let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); - let sig = self.sig_string(&mbt_sig, async_); + let sig = self.sig_string_with_direction(&mbt_sig, async_, Direction::Import); print_docs(&mut self.src, &func.docs); uwrite!( @@ -727,17 +715,18 @@ impl InterfaceGenerator<'_> { &mut bindgen, false, ); - let src = bindgen.src.clone(); - let cleanup_list = if bindgen.needs_cleanup_list { "let cleanup_list : Array[Int] = []" } else { "" }; + let builtins = bindgen.take_local_ffi_imports(); + drop(bindgen); + self.ffi_imports.extend(builtins); let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); - let sig = self.sig_string(&mbt_sig, async_); + let sig = self.sig_string_with_direction(&mbt_sig, async_, Direction::Import); print_docs(&mut self.src, &func.docs); @@ -824,15 +813,15 @@ impl InterfaceGenerator<'_> { &mut bindgen, async_, ); - - let src = bindgen.src; - // Handle cleanup for both sync and async exports let cleanup_list = if bindgen.needs_cleanup_list { "let cleanup_list : Array[Int] = []" } else { "" }; + let builtins = bindgen.take_local_ffi_imports(); + let src = bindgen.src; + self.ffi_imports.extend(builtins); let result_type = match &sig.results[..] { [] => "Unit", @@ -1013,8 +1002,9 @@ impl InterfaceGenerator<'_> { ); abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen); - + let builtins = bindgen.take_local_ffi_imports(); let src = bindgen.src; + self.ffi_imports.extend(builtins); let func_name = self .world_gen @@ -1059,10 +1049,6 @@ impl InterfaceGenerator<'_> { } } - fn sig_string(&mut self, sig: &MoonbitSignature, async_: bool) -> String { - self.sig_string_with_direction(sig, async_, Direction::Import) - } - fn sig_string_with_direction( &mut self, sig: &MoonbitSignature, @@ -1580,6 +1566,7 @@ struct FunctionBindgen<'a, 'b> { defer_cleanup: bool, direction: Direction, async_: bool, + local_ffi_imports: HashSet<&'static str>, } impl<'a, 'b> FunctionBindgen<'a, 'b> { @@ -1607,9 +1594,14 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { defer_cleanup, direction, async_, + local_ffi_imports: HashSet::new(), } } + fn take_local_ffi_imports(&mut self) -> HashSet<&'static str> { + std::mem::take(&mut self.local_ffi_imports) + } + fn lower_variant( &mut self, cases: &[(&str, Option)], @@ -1799,7 +1791,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } fn use_ffi(&mut self, str: &'static str) { - self.interface_gen.ffi_imports.insert(str); + self.local_ffi_imports.insert(str); } } @@ -2879,64 +2871,70 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::FutureLift { ty, .. } => { - self.use_ffi(ffi::MALLOC); - self.use_ffi(ffi::FREE); let ty = dealias(self.interface_gen.resolve, *ty); - let (lifted_func_name, lift, _, _) = - self.interface_gen.bindings.0.get(&ty).unwrap(); + let binding = self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; - results.push(format!("{lifted_func_name}({op})")); + results.push(format!("{}({op})", binding.lift_name)); if self .interface_gen .async_bindings_emitted - .insert(lifted_func_name.clone()) + .insert(binding.lift_name.clone()) { - uwriteln!(self.interface_gen.ffi, "{}", lift); + self.interface_gen + .ffi_imports + .extend(binding.lift_builtins.iter().copied()); + uwriteln!(self.interface_gen.ffi, "{}", binding.lift_src); } } Instruction::FutureLower { ty, .. } => { let ty = dealias(self.interface_gen.resolve, *ty); - let (_, _, lowered_func_name, lower) = - self.interface_gen.bindings.0.get(&ty).unwrap(); + let binding = self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; - results.push(format!("{lowered_func_name}({op})")); + results.push(format!("{}({op})", binding.lower_name)); if self .interface_gen .async_bindings_emitted - .insert(lowered_func_name.clone()) + .insert(binding.lower_name.clone()) { - uwriteln!(self.interface_gen.ffi, "{}", lower); + self.interface_gen + .ffi_imports + .extend(binding.lower_builtins.iter().copied()); + uwriteln!(self.interface_gen.ffi, "{}", binding.lower_src); } } Instruction::StreamLower { ty, .. } => { let ty = dealias(self.interface_gen.resolve, *ty); - let (_, _, lowered_func_name, lower) = - self.interface_gen.bindings.0.get(&ty).unwrap(); + let binding = self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; - results.push(format!("{lowered_func_name}({op})")); + results.push(format!("{}({op})", binding.lower_name)); if self .interface_gen .async_bindings_emitted - .insert(lowered_func_name.clone()) + .insert(binding.lower_name.clone()) { - uwriteln!(self.interface_gen.ffi, "{}", lower); + self.interface_gen + .ffi_imports + .extend(binding.lower_builtins.iter().copied()); + uwriteln!(self.interface_gen.ffi, "{}", binding.lower_src); } } Instruction::StreamLift { ty, .. } => { let ty = dealias(self.interface_gen.resolve, *ty); - let (lifted_func_name, lift, _, _) = - self.interface_gen.bindings.0.get(&ty).unwrap(); + let binding = self.interface_gen.bindings.0.get(&ty).unwrap(); let op = &operands[0]; - results.push(format!("{lifted_func_name}({op})")); + results.push(format!("{}({op})", binding.lift_name)); if self .interface_gen .async_bindings_emitted - .insert(lifted_func_name.clone()) + .insert(binding.lift_name.clone()) { - uwriteln!(self.interface_gen.ffi, "{}", lift); + self.interface_gen + .ffi_imports + .extend(binding.lift_builtins.iter().copied()); + uwriteln!(self.interface_gen.ffi, "{}", binding.lift_src); } } From 0b0968c79fa2e57ece26992e5f228ac2e68447f3 Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 5 Mar 2026 14:56:20 +0800 Subject: [PATCH 59/61] chore: run fmt and fix clippy in moonbit test tooling --- crates/moonbit/src/async_support.rs | 24 +++++++----------------- crates/moonbit/src/lib.rs | 23 +++++++++++++++-------- crates/test/src/moonbit.rs | 7 ++----- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 5c673a7b7..bbec007ae 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -5,16 +5,16 @@ use std::{ use heck::ToUpperCamelCase; use wit_bindgen_core::{ - abi::{self, deallocate_lists_in_types, lift_from_memory, WasmSignature}, - dealias, uwriteln, Direction, Files, Source, + abi::{self, WasmSignature, deallocate_lists_in_types, lift_from_memory}, + dealias, uwriteln, wit_parser::{ Function, LiftLowerAbi, ManglingAndAbi, Param, Type, TypeDefKind, TypeId, WasmImport, }, }; use crate::pkg::ToMoonBitIdent; -use crate::{ffi, indent, FunctionBindgen}; +use crate::{FunctionBindgen, ffi, indent}; use super::InterfaceGenerator; @@ -618,13 +618,8 @@ fn wasmLift{camel_name}{index}(stream_handle : Int) -> {lifted} {{ let lift_builtins = if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); - let mut lift_bindgen = FunctionBindgen::new( - self, - Box::new([]), - Direction::Import, - true, - false, - ); + let mut lift_bindgen = + FunctionBindgen::new(self, Box::new([]), Direction::Import, true, false); lift_bindgen.use_ffi(ffi::MALLOC); lift_bindgen.use_ffi(ffi::FREE); @@ -737,13 +732,8 @@ fn wasmLower{camel_name}{index}(stream : {lifted}) -> Int {{ let lower_builtins = if let Some(inner_ty) = inner_type { let resolve = self.resolve.clone(); let elem_type = self.world_gen.pkg_resolver.type_name(self.name, &inner_ty); - let mut lower_bindgen = FunctionBindgen::new( - self, - Box::new([]), - Direction::Export, - true, - false, - ); + let mut lower_bindgen = + FunctionBindgen::new(self, Box::new([]), Direction::Export, true, false); lower_bindgen.use_ffi(ffi::MALLOC); lower_bindgen.use_ffi(ffi::FREE); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 59475324f..9a538bb03 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -11,8 +11,7 @@ use wit_bindgen_core::{ AsyncFilterSet, Direction, Files, InterfaceGenerator as CoreInterfaceGenerator, Ns, Source, WorldGenerator, abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}, - dealias, - uwrite, uwriteln, + dealias, uwrite, uwriteln, wit_parser::{ Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Handle, Int, InterfaceId, LiftLowerAbi, Mangling, ManglingAndAbi, Param, Record, Resolve, @@ -858,6 +857,9 @@ impl InterfaceGenerator<'_> { #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ {async_pkg}with_waitableset(async fn() {{ + // Intentionally run export body in a task-group child task. + // MoonBit's structured concurrency model uses the task group + // as an umbrella for async work started by the export. {async_pkg}with_task_group(async fn(task_group) {{ {cleanup_list} {src} @@ -1076,7 +1078,12 @@ impl InterfaceGenerator<'_> { }) .collect::>(); - // For async exports, add taskgroup parameter (always Unit type) + // For async exports, add a task-group parameter. + // + // This is intentionally `TaskGroup[Unit]` even when the function result + // type is not `Unit`: this task group models the umbrella lifetime for + // export-side structured concurrency and cancellation, rather than the + // direct return payload type of the exported function. if async_ && matches!(direction, Direction::Export) { params.push("task_group : @async.TaskGroup[Unit]".to_string()); } @@ -2945,11 +2952,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { match ty { Type::Id(id) => match &self.interface_gen.resolve.types[*id].kind { TypeDefKind::Handle(Handle::Own(_)) => { - let constructor = - self.interface_gen.world_gen.pkg_resolver.type_constructor( - self.interface_gen.name, - ty, - ); + let constructor = self + .interface_gen + .world_gen + .pkg_resolver + .type_constructor(self.interface_gen.name, ty); uwriteln!(self.src, "let _ = {constructor}::drop({op});"); } TypeDefKind::Future(_) | TypeDefKind::Stream(_) => { diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index 37c67fb68..8635ecf71 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -1,5 +1,5 @@ use crate::{LanguageMethods, Runner}; -use anyhow::{bail, Context}; +use anyhow::{Context, bail}; use serde::Deserialize; use std::process::Command; @@ -103,10 +103,7 @@ impl LanguageMethods for MoonBit { .find(|path| path.exists()) .cloned() .with_context(|| { - format!( - "failed to locate MoonBit output wasm, looked in: {:?}", - artifact_candidates - ) + format!("failed to locate MoonBit output wasm, looked in: {artifact_candidates:?}",) })?; // Embed WIT files let manifest_dir = compile.component.path.parent().unwrap(); From d14f65146170da1200adc02f61a7e17a0bb902a3 Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 5 Mar 2026 15:00:07 +0800 Subject: [PATCH 60/61] chore(moonbit): drop unused ffi files and keep utf16 embedding --- crates/moonbit/src/ffi/async_primitive.mbt | 229 --------- crates/moonbit/src/ffi/ffi.mbt | 213 -------- crates/moonbit/src/ffi/future.mbt | 540 --------------------- crates/moonbit/src/ffi/subtask.mbt | 57 --- crates/moonbit/src/ffi/waitable_task.mbt | 311 ------------ crates/moonbit/src/ffi/wasm_primitive.mbt | 185 ------- crates/test/src/moonbit.rs | 16 +- 7 files changed, 1 insertion(+), 1550 deletions(-) delete mode 100644 crates/moonbit/src/ffi/async_primitive.mbt delete mode 100644 crates/moonbit/src/ffi/ffi.mbt delete mode 100644 crates/moonbit/src/ffi/future.mbt delete mode 100644 crates/moonbit/src/ffi/subtask.mbt delete mode 100644 crates/moonbit/src/ffi/waitable_task.mbt delete mode 100644 crates/moonbit/src/ffi/wasm_primitive.mbt diff --git a/crates/moonbit/src/ffi/async_primitive.mbt b/crates/moonbit/src/ffi/async_primitive.mbt deleted file mode 100644 index 3a15c1330..000000000 --- a/crates/moonbit/src/ffi/async_primitive.mbt +++ /dev/null @@ -1,229 +0,0 @@ -///| -async fn[T, E : Error] async_suspend( - cb : ((T) -> Unit, (E) -> Unit) -> Unit, -) -> T raise E = "%async.suspend" - -///| -fn run_async(f : async () -> Unit noraise) = "%async.run" - -///| -priv enum State { - Done - Fail(Error) - Running - Suspend(ok_cont~ : (Unit) -> Unit, err_cont~ : (Error) -> Unit) -} - -///| -struct Coroutine { - coro_id : Int - mut state : State - mut shielded : Bool - mut cancelled : Bool - mut ready : Bool - downstream : Map[Int, Coroutine] -} - -///| -pub impl Eq for Coroutine with equal(c1, c2) { - c1.coro_id == c2.coro_id -} - -///| -pub impl Hash for Coroutine with hash_combine(self, hasher) { - self.coro_id.hash_combine(hasher) -} - -///| -pub fn Coroutine::wake(self : Coroutine) -> Unit { - self.ready = true - scheduler.run_later.push_back(self) -} - -///| -pub fn Coroutine::run(self : Coroutine) -> Unit { - self.ready = true - scheduler.run_later.push_front(self) -} - -///| -pub fn Coroutine::is_done(self : Coroutine) -> Bool { - match self.state { - Done => true - Fail(_) => true - Running | Suspend(_) => false - } -} - -///| -pub fn is_being_cancelled() -> Bool { - current_coroutine().cancelled -} - -///| -pub fn current_coroutine_done() -> Bool { - guard scheduler.curr_coro is Some(coro) else { return true } - coro.is_done() -} - -///| -pub(all) suberror Cancelled derive(Show) - -///| -pub fn Coroutine::cancel(self : Coroutine) -> Unit { - self.cancelled = true - if not(self.shielded || self.ready) { - self.wake() - } -} - -///| -pub async fn pause() -> Unit { - guard scheduler.curr_coro is Some(coro) - if coro.cancelled && not(coro.shielded) { - raise Cancelled::Cancelled - } - async_suspend(fn(ok_cont, err_cont) { - guard coro.state is Running - coro.state = Suspend(ok_cont~, err_cont~) - coro.ready = true - scheduler.run_later.push_back(coro) - }) -} - -///| -pub async fn suspend() -> Unit { - guard scheduler.curr_coro is Some(coro) - if coro.cancelled && not(coro.shielded) { - raise Cancelled::Cancelled - } - scheduler.blocking += 1 - defer { - scheduler.blocking -= 1 - } - async_suspend(fn(ok_cont, err_cont) { - guard coro.state is Running - coro.state = Suspend(ok_cont~, err_cont~) - }) -} - -///| -pub fn spawn(f : async () -> Unit) -> Coroutine { - scheduler.coro_id += 1 - let coro = { - state: Running, - ready: true, - shielded: false, - downstream: {}, - coro_id: scheduler.coro_id, - cancelled: false, - } - fn run(_) { - run_async(fn() { - coro.shielded = false - try f() catch { - err => coro.state = Fail(err) - } noraise { - _ => coro.state = Done - } - for _, coro in coro.downstream { - coro.wake() - } - coro.downstream.clear() - }) - } - - coro.state = Suspend(ok_cont=run, err_cont=_ => ()) - scheduler.run_later.push_back(coro) - coro -} - -///| -pub fn Coroutine::unwrap(self : Coroutine) -> Unit raise { - match self.state { - Done => () - Fail(err) => raise err - Running | Suspend(_) => panic() - } -} - -///| -pub async fn Coroutine::wait(target : Coroutine) -> Unit { - guard scheduler.curr_coro is Some(coro) - guard not(physical_equal(coro, target)) - match target.state { - Done => return - Fail(err) => raise err - Running | Suspend(_) => () - } - target.downstream[coro.coro_id] = coro - try suspend() catch { - err => { - target.downstream.remove(coro.coro_id) - raise err - } - } noraise { - _ => target.unwrap() - } -} - -///| -pub async fn protect_from_cancel(f : async () -> Unit) -> Unit { - guard scheduler.curr_coro is Some(coro) - if coro.shielded { - // already in a shield, do nothing - f() - } else { - coro.shielded = true - defer { - coro.shielded = false - } - f() - if coro.cancelled { - raise Cancelled::Cancelled - } - } -} - -///| -priv struct Scheduler { - mut coro_id : Int - mut curr_coro : Coroutine? - mut blocking : Int - run_later : @deque.Deque[Coroutine] -} - -///| -let scheduler : Scheduler = { - coro_id: 0, - curr_coro: None, - blocking: 0, - run_later: @deque.new(), -} - -///| -pub fn current_coroutine() -> Coroutine { - scheduler.curr_coro.unwrap() -} - -///| -pub fn no_more_work() -> Bool { - scheduler.blocking == 0 && scheduler.run_later.is_empty() -} - -///| -pub fn rschedule() -> Unit { - while scheduler.run_later.pop_front() is Some(coro) { - coro.ready = false - guard coro.state is Suspend(ok_cont~, err_cont~) else { } - coro.state = Running - let last_coro = scheduler.curr_coro - scheduler.curr_coro = Some(coro) - if coro.cancelled && !coro.shielded { - err_cont(Cancelled::Cancelled) - } else { - ok_cont(()) - } - scheduler.curr_coro = last_coro - } -} diff --git a/crates/moonbit/src/ffi/ffi.mbt b/crates/moonbit/src/ffi/ffi.mbt deleted file mode 100644 index 86ad5fd4e..000000000 --- a/crates/moonbit/src/ffi/ffi.mbt +++ /dev/null @@ -1,213 +0,0 @@ -///| -pub extern "wasm" fn extend16(value : Int) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.extend16_s) - -///| -pub extern "wasm" fn extend8(value : Int) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.extend8_s) - -///| -pub extern "wasm" fn store8(offset : Int, value : Int) = - #|(func (param i32) (param i32) local.get 0 local.get 1 i32.store8) - -///| -pub extern "wasm" fn load8_u(offset : Int) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.load8_u) - -///| -pub extern "wasm" fn load8(offset : Int) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.load8_s) - -///| -pub extern "wasm" fn store16(offset : Int, value : Int) = - #|(func (param i32) (param i32) local.get 0 local.get 1 i32.store16) - -///| -pub extern "wasm" fn load16(offset : Int) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.load16_s) - -///| -pub extern "wasm" fn load16_u(offset : Int) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.load16_u) - -///| -pub extern "wasm" fn store32(offset : Int, value : Int) = - #|(func (param i32) (param i32) local.get 0 local.get 1 i32.store) - -///| -pub extern "wasm" fn load32(offset : Int) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.load) - -///| -pub extern "wasm" fn store64(offset : Int, value : Int64) = - #|(func (param i32) (param i64) local.get 0 local.get 1 i64.store) - -///| -pub extern "wasm" fn load64(offset : Int) -> Int64 = - #|(func (param i32) (result i64) local.get 0 i64.load) - -///| -pub extern "wasm" fn storef32(offset : Int, value : Float) = - #|(func (param i32) (param f32) local.get 0 local.get 1 f32.store) - -///| -pub extern "wasm" fn loadf32(offset : Int) -> Float = - #|(func (param i32) (result f32) local.get 0 f32.load) - -///| -pub extern "wasm" fn storef64(offset : Int, value : Double) = - #|(func (param i32) (param f64) local.get 0 local.get 1 f64.store) - -///| -pub extern "wasm" fn loadf64(offset : Int) -> Double = - #|(func (param i32) (result f64) local.get 0 f64.load) - -///| -pub extern "wasm" fn f32_to_i32(value : Float) -> Int = - #|(func (param f32) (result i32) local.get 0 f32.convert_i32_s) - -///| -pub extern "wasm" fn f32_to_i64(value : Float) -> Int64 = - #|(func (param f32) (result i64) local.get 0 f32.convert_i64_s) - -// set pseudo header; allocate extra bytes for string - -///| -pub extern "wasm" fn malloc(size : Int) -> Int = - #|(func (param i32) (result i32) (local i32) - #| local.get 0 i32.const 4 i32.add call $moonbit.gc.malloc - #| local.tee 1 i32.const 0 call $moonbit.init_array8 - #| local.get 1 i32.const 8 i32.add) - -///| -pub extern "wasm" fn free(position : Int) = - #|(func (param i32) local.get 0 i32.const 8 i32.sub call $moonbit.decref) - -///| -extern "wasm" fn copy(dest : Int, src : Int, len : Int) = - #|(func (param i32) (param i32) (param i32) local.get 0 local.get 1 local.get 2 memory.copy) - -///| -#owned(str) -pub extern "wasm" fn str2ptr(str : String) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -pub extern "wasm" fn ptr2str(ptr : Int, len : Int) -> String = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array16 - #| local.get 2) - -///| -#owned(bytes) -pub extern "wasm" fn bytes2ptr(bytes : FixedArray[Byte]) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -pub extern "wasm" fn ptr2bytes(ptr : Int, len : Int) -> FixedArray[Byte] = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array8 - #| local.get 2) - -///| -#owned(array) -pub extern "wasm" fn uint_array2ptr(array : FixedArray[UInt]) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -#owned(array) -pub extern "wasm" fn uint64_array2ptr(array : FixedArray[UInt64]) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -#owned(array) -pub extern "wasm" fn int_array2ptr(array : FixedArray[Int]) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -#owned(array) -pub extern "wasm" fn int64_array2ptr(array : FixedArray[Int64]) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -#owned(array) -pub extern "wasm" fn float_array2ptr(array : FixedArray[Float]) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -#owned(array) -pub extern "wasm" fn double_array2ptr(array : FixedArray[Double]) -> Int = - #|(func (param i32) (result i32) local.get 0 i32.const 8 i32.add) - -///| -pub extern "wasm" fn ptr2uint_array(ptr : Int, len : Int) -> FixedArray[UInt] = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array32 - #| local.get 2) - -///| -pub extern "wasm" fn ptr2int_array(ptr : Int, len : Int) -> FixedArray[Int] = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array32 - #| local.get 2) - -///| -pub extern "wasm" fn ptr2float_array(ptr : Int, len : Int) -> FixedArray[Float] = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array32 - #| local.get 2) - -///| -pub extern "wasm" fn ptr2uint64_array( - ptr : Int, - len : Int, -) -> FixedArray[UInt64] = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array64 - #| local.get 2) - -///| -pub extern "wasm" fn ptr2int64_array(ptr : Int, len : Int) -> FixedArray[Int64] = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array64 - #| local.get 2) - -///| -pub extern "wasm" fn ptr2double_array( - ptr : Int, - len : Int, -) -> FixedArray[Double] = - #|(func (param i32) (param i32) (result i32) (local i32) - #| local.get 0 i32.const 8 i32.sub local.tee 2 - #| local.get 1 call $moonbit.init_array64 - #| local.get 2) - -///| -pub fn cabi_realloc( - src_offset : Int, - src_size : Int, - _dst_alignment : Int, - dst_size : Int, -) -> Int { - // malloc - if src_offset == 0 && src_size == 0 { - return malloc(dst_size) - } - // free - if dst_size == 0 { - free(src_offset) - return 0 - } - // realloc - let dst = malloc(dst_size) - copy(dst, src_offset, if src_size < dst_size { src_size } else { dst_size }) - free(src_offset) - dst -} diff --git a/crates/moonbit/src/ffi/future.mbt b/crates/moonbit/src/ffi/future.mbt deleted file mode 100644 index b8b0c19a3..000000000 --- a/crates/moonbit/src/ffi/future.mbt +++ /dev/null @@ -1,540 +0,0 @@ -///| -pub struct FutureVTable[T] { - new : () -> UInt64 - read : (Int, Int) -> Int - write : (Int, Int) -> Int - cancel_read : (Int) -> Int - cancel_write : (Int) -> Int - drop_readable : (Int) -> Unit - drop_writable : (Int) -> Unit - malloc : (Int) -> Int - free : (Int) -> Unit - lift : (Int) -> T - lower : (T, Int) -> Unit -} - -///| -pub fn[T] FutureVTable::new( - new : () -> UInt64, - read : (Int, Int) -> Int, - write : (Int, Int) -> Int, - cancel_read : (Int) -> Int, - cancel_write : (Int) -> Int, - drop_readable : (Int) -> Unit, - drop_writable : (Int) -> Unit, - malloc : (Int) -> Int, - free : (Int) -> Unit, - lift : (Int) -> T, - lower : (T, Int) -> Unit, -) -> FutureVTable[T] { - { - new, - read, - write, - cancel_read, - cancel_write, - drop_readable, - drop_writable, - malloc, - free, - lift, - lower, - } -} - -///| -pub fn[T] new_future( - vtable : FutureVTable[T], -) -> (FutureReader[T], FutureWriter[T]) { - let handle = (vtable.new)() - let left_handle = handle.to_int() - let right_handle = (handle >> 32).to_int() - ( - FutureReader::new(left_handle, vtable), - FutureWriter::new(right_handle, vtable), - ) -} - -///| -pub struct FutureReader[T] { - handle : Int - vtable : FutureVTable[T] - mut code : Int? - mut dropped : Bool - memory_refs : Array[Int] -} - -///| -pub fn[T] FutureReader::new( - handle : Int, - vtable : FutureVTable[T], -) -> FutureReader[T] { - { handle, vtable, code: None, memory_refs: [], dropped: false } -} - -///| -pub impl[T] Waitable for FutureReader[T] with update(self, code~ : Int) -> Unit { - self.code = Some(code) -} - -///| -pub impl[T] Eq for FutureReader[T] with equal(self, other) -> Bool { - self.handle == other.handle -} - -///| -pub impl[T] Waitable for FutureReader[T] with handle(self) -> Int { - self.handle -} - -///| -pub impl[T] Waitable for FutureReader[T] with cancel(self) -> Unit { - if self.code is Some(code) && WaitableStatus::decode(code) is Cancelled(_) { - return - } - self.code = Some((self.vtable.cancel_read)(self.handle)) -} - -///| -pub impl[T] Waitable for FutureReader[T] with drop(self) -> Bool { - _async_debug("stream-reader-drop(\{self.handle})") - if self.dropped { - return false - } - (self.vtable.drop_readable)(self.handle) - self.dropped = true - for ptr in self.memory_refs { - self.free(ptr) - } - true -} - -///| -pub impl[T] Waitable for FutureReader[T] with done(self) -> Bool { - match self.code { - Some(c) => - match WaitableStatus::decode(c) { - Completed(_) | Dropped(_) | Cancelled(_) => true - Blocking => false - } - None => false - } -} - -///| -pub fn[T] FutureReader::malloc(self : FutureReader[T]) -> Int { - let ptr = (self.vtable.malloc)(1) - ptr -} - -///| -pub fn[T] FutureReader::free(self : FutureReader[T], ptr : Int) -> Unit { - (self.vtable.free)(ptr) -} - -///| -pub fn[T] FutureReader::lift(self : FutureReader[T], ptr : Int) -> T { - let res = (self.vtable.lift)(ptr) - res -} - -///| -pub fn[T] FutureReader::lower_read(self : FutureReader[T], ptr : Int) -> Int { - (self.vtable.read)(self.handle, ptr) -} - -///| -pub async fn[T] FutureReader::read(self : FutureReader[T]) -> T { - let buf_ptr = self.malloc() - self.memory_refs.push(buf_ptr) - self.code = Some(self.lower_read(buf_ptr)) - _async_debug("future-read(\{self.handle}) -> \{self.code.unwrap()}") - // register this waitable to the current task - let task = current_task() - task.add_waitable(self, current_coroutine()) - defer task.remove_waitable(self) - - // wait until ready - for { - let status = WaitableStatus::decode(self.code.unwrap()) - match status { - Cancelled(_) | Dropped(_) => raise Cancelled::Cancelled - Completed(_) => break - Blocking => suspend() - } - } - // when receive event, continue this coroutine - let value = self.lift(buf_ptr) - return value -} - -///| -pub struct FutureWriter[T] { - handle : Int - vtable : FutureVTable[T] - mut code : Int? - mut dropped : Bool - memory_refs : Array[Int] -} - -///| -pub fn[T] FutureWriter::new( - handle : Int, - vtable : FutureVTable[T], -) -> FutureWriter[T] { - { handle, vtable, code: None, memory_refs: [], dropped: false } -} - -///| -pub impl[T] Waitable for FutureWriter[T] with update(self, code~ : Int) -> Unit { - self.code = Some(code) -} - -///| -pub impl[T] Eq for FutureWriter[T] with equal(self, other) -> Bool { - self.handle == other.handle -} - -///| -pub impl[T] Waitable for FutureWriter[T] with handle(self) -> Int { - self.handle -} - -///| -pub impl[T] Waitable for FutureWriter[T] with cancel(self) -> Unit { - if self.code is Some(code) && WaitableStatus::decode(code) is Cancelled(_) { - return - } - self.code = Some((self.vtable.cancel_write)(self.handle)) -} - -///| -pub impl[T] Waitable for FutureWriter[T] with drop(self) -> Bool { - _async_debug("stream-writer-drop(\{self.handle})") - if self.dropped { - return false - } - (self.vtable.drop_writable)(self.handle) - self.dropped = true - for ptr in self.memory_refs { - self.free(ptr) - } - true -} - -///| -pub impl[T] Waitable for FutureWriter[T] with done(self) -> Bool { - match self.code { - Some(c) => - match WaitableStatus::decode(c) { - Completed(_) | Dropped(_) | Cancelled(_) => true - Blocking => false - } - None => false - } -} - -///| -pub fn[T] FutureWriter::malloc(self : FutureWriter[T]) -> Int { - (self.vtable.malloc)(1) -} - -///| -pub fn[T] FutureWriter::free(self : FutureWriter[T], ptr : Int) -> Unit { - (self.vtable.free)(ptr) -} - -///| -pub fn[T] FutureWriter::lower( - self : FutureWriter[T], - value : T, - ptr : Int, -) -> Unit { - (self.vtable.lower)(value, ptr) -} - -///| -pub fn[T] FutureWriter::lower_write(self : FutureWriter[T], ptr : Int) -> Int { - (self.vtable.write)(self.handle, ptr) -} - -///| -pub async fn[T] FutureWriter::write(self : FutureWriter[T], value : T) -> Unit { - // register this waitable to the current task - let task = current_task() - task.add_waitable(self, current_coroutine()) - defer task.remove_waitable(self) - let buf_ptr = self.malloc() - self.memory_refs.push(buf_ptr) - self.lower(value, buf_ptr) - self.code = Some(self.lower_write(buf_ptr)) - defer self.free(buf_ptr) - - // wait until ready - for { - let status = WaitableStatus::decode(self.code.unwrap()) - match status { - Cancelled(_) | Dropped(_) => raise Cancelled::Cancelled - Completed(_) => break - Blocking => suspend() - } - } - // when receive event, continue this coroutine - return -} - -///| -pub suberror StreamCancelled (Int, Cancelled) derive(Show) - -///| -pub struct StreamVTable[T] { - new : () -> UInt64 - read : (Int, Int, Int) -> Int - write : (Int, Int, Int) -> Int - cancel_read : (Int) -> Int - cancel_write : (Int) -> Int - drop_readable : (Int) -> Unit - drop_writable : (Int) -> Unit - malloc : (Int) -> Int - free : (Int) -> Unit - lift : (Int, Int) -> FixedArray[T] - lower : (FixedArray[T]) -> Int -} - -///| -pub fn[T] StreamVTable::new( - new : () -> UInt64, - read : (Int, Int, Int) -> Int, - write : (Int, Int, Int) -> Int, - cancel_read : (Int) -> Int, - cancel_write : (Int) -> Int, - drop_readable : (Int) -> Unit, - drop_writable : (Int) -> Unit, - malloc : (Int) -> Int, - free : (Int) -> Unit, - lift : (Int, Int) -> FixedArray[T], - lower : (FixedArray[T]) -> Int, -) -> StreamVTable[T] { - { - new, - read, - write, - cancel_read, - cancel_write, - drop_readable, - drop_writable, - malloc, - free, - lift, - lower, - } -} - -///| -pub fn[T] new_stream( - vtable : StreamVTable[T], -) -> (StreamReader[T], StreamWriter[T]) { - let handle = (vtable.new)() - let left_handle = handle.to_int() - let right_handle = (handle >> 32).to_int() - ( - StreamReader::new(left_handle, vtable), - StreamWriter::new(right_handle, vtable), - ) -} - -///| -pub struct StreamReader[T] { - handle : Int - vtable : StreamVTable[T] - mut code : Int? - mut dropped : Bool - memory_refs : Array[Int] -} - -///| -pub impl[T] Waitable for StreamReader[T] with update(self, code~ : Int) -> Unit { - self.code = Some(code) -} - -///| -pub impl[T] Eq for StreamReader[T] with equal(self, other) -> Bool { - self.handle == other.handle -} - -///| -pub impl[T] Waitable for StreamReader[T] with handle(self) -> Int { - self.handle -} - -///| -pub impl[T] Waitable for StreamReader[T] with cancel(self) -> Unit { - if self.code is Some(code) && WaitableStatus::decode(code) is Cancelled(_) { - return - } - self.code = Some((self.vtable.cancel_read)(self.handle)) -} - -///| -pub impl[T] Waitable for StreamReader[T] with drop(self) -> Bool { - _async_debug("stream-reader-drop(\{self.handle})") - if self.dropped { - return false - } - (self.vtable.drop_readable)(self.handle) - self.dropped = true - for ptr in self.memory_refs { - (self.vtable.free)(ptr) - } - true -} - -///| -pub impl[T] Waitable for StreamReader[T] with done(self) -> Bool { - match self.code { - Some(c) => - match WaitableStatus::decode(c) { - Completed(_) | Dropped(_) | Cancelled(_) => true - Blocking => false - } - None => false - } -} - -///| -pub fn[T] StreamReader::new( - handle : Int, - vtable : StreamVTable[T], -) -> StreamReader[T] { - { handle, vtable, code: None, memory_refs: [], dropped: false } -} - -///| -pub async fn[T] StreamReader::read( - self : StreamReader[T], - buffer : FixedArray[T], - offset? : Int = 0, - length : Int, -) -> Int { - // register this waitable to the current task - let task = current_task() - task.add_waitable(self, current_coroutine()) - defer task.remove_waitable(self) - let buf_ptr = (self.vtable.malloc)(length) - self.code = Some((self.vtable.read)(self.handle, buf_ptr, length)) - _async_debug("stream-read(\{self.handle}) -> \{self.code.unwrap()}") - for { - let status = WaitableStatus::decode(self.code.unwrap()) - match status { - Completed(n) => { - let read_result = (self.vtable.lift)(buf_ptr, n) - for i in 0.. { - let read_result = (self.vtable.lift)(buf_ptr, n) - for i in 0.. suspend() - } - } -} - -///| -pub struct StreamWriter[T] { - handle : Int - vtable : StreamVTable[T] - mut code : Int? - mut dropped : Bool - memory_refs : Array[Int] -} - -///| -pub impl[T] Waitable for StreamWriter[T] with update(self, code~ : Int) -> Unit { - self.code = Some(code) -} - -///| -pub impl[T] Eq for StreamWriter[T] with equal(self, other) -> Bool { - self.handle == other.handle -} - -///| -pub impl[T] Waitable for StreamWriter[T] with handle(self) -> Int { - self.handle -} - -///| -pub impl[T] Waitable for StreamWriter[T] with cancel(self) -> Unit { - if self.code is Some(code) && WaitableStatus::decode(code) is Cancelled(_) { - return - } - self.code = Some((self.vtable.cancel_write)(self.handle)) -} - -///| -pub impl[T] Waitable for StreamWriter[T] with drop(self) -> Bool { - _async_debug("stream-writer-drop(\{self.handle})") - let task = current_task() - let coro = task.children.get(self.handle) - if coro is Some((_, coro)) { - coro.cancel() - coro.wake() - } - if self.dropped { - return false - } - (self.vtable.drop_writable)(self.handle) - self.dropped = true - for ptr in self.memory_refs { - (self.vtable.free)(ptr) - } - true -} - -///| -pub impl[T] Waitable for StreamWriter[T] with done(self) -> Bool { - match self.code { - Some(c) => - match WaitableStatus::decode(c) { - Completed(_) | Dropped(_) | Cancelled(_) => true - Blocking => false - } - None => false - } -} - -///| -pub fn[T] StreamWriter::new( - handle : Int, - vtable : StreamVTable[T], -) -> StreamWriter[T] { - { handle, vtable, code: None, memory_refs: [], dropped: false } -} - -///| -pub async fn[T] StreamWriter::write( - self : StreamWriter[T], - buffer : FixedArray[T], -) -> Int { - // register this waitable to the current task - let task = current_task() - task.add_waitable(self, current_coroutine()) - defer task.remove_waitable(self) - let write_buf = (self.vtable.lower)(buffer) - self.code = Some((self.vtable.write)(self.handle, write_buf, buffer.length())) - for { - let status = WaitableStatus::decode(self.code.unwrap()) - match status { - Completed(n) => return n - Cancelled(n) | Dropped(n) => - raise StreamCancelled::StreamCancelled((n, Cancelled::Cancelled)) - Blocking => suspend() - } - } -} diff --git a/crates/moonbit/src/ffi/subtask.mbt b/crates/moonbit/src/ffi/subtask.mbt deleted file mode 100644 index a53471df0..000000000 --- a/crates/moonbit/src/ffi/subtask.mbt +++ /dev/null @@ -1,57 +0,0 @@ -///| -pub struct Subtask { - handle : Int - mut code : Int? - mut dropped : Bool -} - -///| -pub fn Subtask::from_handle(handle : Int, code? : Int) -> Subtask { - { handle, code, dropped: false } -} - -///| -pub impl Waitable for Subtask with update(self, code~ : Int) -> Unit { - self.code = Some(code) -} - -///| -pub impl Eq for Subtask with equal(self, other) -> Bool { - self.handle == other.handle -} - -///| -pub impl Waitable for Subtask with handle(self) -> Int { - self.handle -} - -///| -pub impl Waitable for Subtask with cancel(self) -> Unit { - if self.code is Some(code) && CallbackCode::decode(code) is Cancel(_) { - return - } - self.code = Some(subtask_cancel(self.handle)) -} - -///| -pub impl Waitable for Subtask with drop(self) -> Bool { - _async_debug("subtask-drop(\{self.handle})") - if self.done() || self.dropped { - return false - } - subtask_drop(self.handle) - self.dropped = true - true -} - -///| -pub impl Waitable for Subtask with done(self) -> Bool { - guard self.code is Some(code) else { return false } - match SubtaskStatus::decode(code) { - StartCancelled(_) => true - Returned(_) => true - Started(_) => false - Starting(_) => false - ReturnCancelled(_) => true - } -} diff --git a/crates/moonbit/src/ffi/waitable_task.mbt b/crates/moonbit/src/ffi/waitable_task.mbt deleted file mode 100644 index 2a1014f00..000000000 --- a/crates/moonbit/src/ffi/waitable_task.mbt +++ /dev/null @@ -1,311 +0,0 @@ -///| -priv enum TaskStatus { - Fail(Error) - Running - Done -} - -///| -/// A `Task` represents a waitable task context that can manage waitables and child coroutines. -/// -struct Task { - id : Int - children : Map[Int, (&Waitable, Coroutine)] - task_defer : Array[() -> Unit raise] - resources : @deque.Deque[Int] - mut task : Coroutine? - mut waiting : Int - mut status : TaskStatus -} - -///| -pub let task_map : Map[Int, Task] = {} - -///| -pub fn Task::new() -> Task { - let waitable_set = waitable_set_new() - _async_debug("waitable-set-new(\{waitable_set})") - context_set(waitable_set) - { - id: waitable_set, - children: {}, - resources: @deque.Deque::new(), - task_defer: [], - status: Running, - waiting: 0, - task: None, - } -} - -///| -pub fn Task::from_raw(raw : Int) -> Task { - guard raw != 0 - context_set(raw) - _async_debug("context-set(\{raw})") - { - id: raw, - children: {}, - resources: @deque.Deque::new(), - task_defer: [], - status: Running, - waiting: 0, - task: None, - } -} - -///| -/// Check if the task is failed and return the error -pub fn Task::is_fail(self : Self) -> Error? { - match self.status { - Fail(err) => Some(err) - _ => None - } -} - -///| -/// Check if all waitables are done -pub fn Task::no_wait(self : Self) -> Bool { - self.waiting == 0 -} - -///| -/// Check if the task is done or failed -pub fn Task::is_done(self : Self) -> Bool { - match self.status { - Done => true - Fail(_) => true - Running => false - } -} - -///| -pub fn Task::handle(self : Self) -> Int { - self.id -} - -///| -pub fn Task::blocking_wait(self : Self) -> (Int, Int, Int) { - let result : FixedArray[Int] = FixedArray::make(2, 0) - let result_ptr = int_array2ptr(result) - let event0 = waitable_set_wait(self.id, result_ptr) - _async_debug("waitable_set_wait(\{event0}, \{result[0]}, \{result[1]})") - (event0, result[0], result[1]) -} - -///| -pub fn Task::blocking_poll(self : Self) -> (Int, Int, Int) { - let result : FixedArray[Int] = FixedArray::make(2, 0) - let result_ptr = int_array2ptr(result) - let event0 = waitable_set_poll(self.id, result_ptr) - _async_debug("waitable-set-poll(\{event0}, \{result[0]}, \{result[1]})") - (event0, result[0], result[1]) -} - -///| -/// Add a waitable to the waitable set and increase the waiting count -pub fn[T : Waitable] Task::add_waitable( - self : Self, - waitable : T, - coro : Coroutine, -) -> Unit { - waitable_join(waitable.handle(), self.id) - self.children[waitable.handle()] = (waitable, coro) - self.resources.push_back(waitable.handle()) - _async_debug("waitable-set-join(\{waitable.handle()}, \{self.id})") - self.waiting += 1 -} - -///| -/// When a waitable is done will be removed from the waitable set -/// then waitable will be try to drop -pub fn[T : Waitable] Task::remove_waitable(self : Self, state : T) -> Unit { - _async_debug("waitable-set-join(\{state.handle()}, 0)") - waitable_join(state.handle(), 0) - self.waiting -= 1 -} - -///| -pub fn[T : Waitable] Task::drop_waitable(self : Self, state : T) -> Unit { - let _ = state.drop() - if self.resources.search(state.handle()) is Some(idx) { - let _ = self.resources.remove(idx) - - } - self.children.remove(state.handle()) -} - -///| -/// Cancel a waitable, remove it from the waitable set and force drop it -pub fn[T : Waitable] Task::cancel_waitable(self : Self, state : T) -> Unit { - waitable_join(state.handle(), 0) - _async_debug("waitable-set-join(\{state.handle()}, 0)") - self.waiting -= 1 - state.cancel() - let _ = state.drop() - self.children.remove(state.handle()) -} - -///| -/// set current task context to 0 and let runner drop the waitable set -pub fn Task::drop(self : Self) -> Unit { - context_set(0) - defer waitable_set_drop(self.id) - _async_debug("context-set(0)") -} - -///| -/// Spawns a coroutine to execute an async function and without waits for its completion -/// while managing the waitable state. -pub fn Task::spawn(_self : Self, f : async () -> Unit) -> Unit { - let _ = spawn(f) - // start the coroutine - rschedule() -} - -///| -/// This function spawns a coroutine to run the async function and waits for its completion -pub async fn Task::wait(_ : Self, f : async () -> Unit) -> Unit { - let coro = spawn(f) - // start the coroutine - rschedule() - Coroutine::wait(coro) -} - -///| -pub fn Task::add_defer(self : Self, f : () -> Unit raise) -> Unit { - self.task_defer.push(f) -} - -///| -pub fn callback(event : Int, waitable_id : Int, code : Int) -> Int { - let event = Event::decode(event) - _async_debug("callback(\{event}, \{waitable_id}, \{code})") - let task = match current_waitable_set() { - Some(task) => task - None => current_task() - } - // Handle the event for the current waitable task - match event { - FutureRead | FutureWrite | StreamRead | StreamWrite | Subtask => { - let (state, coro) = task.children[waitable_id] - state.update(code~) - // schedule next coroutine - coro.wake() - rschedule() - if task.no_wait() && task.task is Some(parent) { - // run the parent coroutine when all waitables are done - // parent coroutine may execute return/cancel - parent.wake() - rschedule() - return CallbackCode::Exit.encode() - } - return CallbackCode::Wait(task.id).encode() - } - TaskCancel => { - if task.task is Some(parent) { - parent.wake() - } - task.children - .values() - .each(child => { - let (state, coro) = child - task.cancel_waitable(state) - coro.cancel() - }) - rschedule() - return CallbackCode::Exit.encode() - } - None => { - rschedule() - return CallbackCode::Exit.encode() - } - } -} - -///| -pub fn Task::with_waitable_set( - self : Self, - f : async (Self) -> Unit, - is_drop? : Bool = false, -) -> Coroutine noraise { - let parent = spawn(async fn() -> Unit noraise { - self.status = Running - defer { - while self.resources.pop_front() is Some(handle) { - let state = self.children.get(handle) - if state is Some((state, _)) { - let _ = state.drop() - self.children.remove(handle) - } - } - if self.status is Running { - self.status = Done - } - task_map.remove(self.id) - - // this defer block recycles waitable task resources - while self.task_defer.pop() is Some(defer_block) { - defer_block() catch { - err => if self.status is Done { self.status = Fail(err) } - } - } - - // runner will drop the waitable set - // export async function needs to keep the waitable set - if is_drop { - self.drop() - } - } - f(self) catch { - err => if self.status is Running { self.status = Fail(err) } - } - if !self.no_wait() { - _async_debug("task-wait-loop(\{self.id})") - suspend() catch { - err => if self.status is Running { self.status = Fail(err) } - } - } - }) - self.task = Some(parent) - // start the parent coroutine - parent.run() - rschedule() - parent -} - -///| -fn current_waitable_set() -> Task? { - let ctx = context_get() - _async_debug("context-get(\{ctx})") - if ctx == 0 { - None - } else { - match task_map.get(ctx) { - Some(task) => Some(task) - None => { - let ctx = Task::from_raw(ctx) - task_map[ctx.id] = ctx - Some(ctx) - } - } - } -} - -///| -pub fn current_task() -> Task { - let ctx = context_get() - if ctx == 0 { - let ctx = Task::new() - task_map[ctx.id] = ctx - ctx - } else { - match task_map.get(ctx) { - Some(task) => task - None => { - let ctx = Task::from_raw(ctx) - task_map[ctx.id] = ctx - ctx - } - } - } -} diff --git a/crates/moonbit/src/ffi/wasm_primitive.mbt b/crates/moonbit/src/ffi/wasm_primitive.mbt deleted file mode 100644 index dd0badaac..000000000 --- a/crates/moonbit/src/ffi/wasm_primitive.mbt +++ /dev/null @@ -1,185 +0,0 @@ -///| -pub(open) trait Waitable { - update(Self, code~ : Int) -> Unit - cancel(Self) -> Unit - - // when the waitable is dropped, this function is called to free resources - drop(Self) -> Bool - done(Self) -> Bool - handle(Self) -> Int -} - -///| -pub(all) enum SubtaskStatus { - Starting(Int) - Started(Int) - Returned(Int) - StartCancelled(Int) - ReturnCancelled(Int) -} derive(Eq, Show) - -///| -pub fn SubtaskStatus::decode(int : Int) -> SubtaskStatus { - let handle = int >> 4 - match int & 0xf { - 0 => Starting(handle) - 1 => Started(handle) - 2 => Returned(handle) - 3 => StartCancelled(handle) - 4 => ReturnCancelled(handle) - _ => panic() - } -} - -///| -pub fn SubtaskStatus::handle(self : Self) -> Int { - match self { - Starting(handle) => handle - Started(handle) => handle - Returned(handle) => handle - StartCancelled(handle) => handle - ReturnCancelled(handle) => handle - } -} - -///| -pub(all) enum Event { - None - Subtask - StreamRead - StreamWrite - FutureRead - FutureWrite - TaskCancel -} derive(Eq, Show) - -///| -pub fn Event::decode(int : Int) -> Event { - match int { - 0 => None - 1 => Subtask - 2 => StreamRead - 3 => StreamWrite - 4 => FutureRead - 5 => FutureWrite - 6 => TaskCancel - _ => panic() - } -} - -///| -pub fn Event::encode(self : Self) -> Int { - match self { - None => 0 - Subtask => 1 - StreamRead => 2 - StreamWrite => 3 - FutureRead => 4 - FutureWrite => 5 - TaskCancel => 6 - } -} - -///| -pub(all) enum WaitableStatus { - Completed(Int) - Dropped(Int) - Cancelled(Int) - Blocking -} derive(Eq, Show) - -///| -let waitable_status_block : Int = 0xffff_ffff - -///| -pub fn WaitableStatus::decode(int : Int) -> WaitableStatus { - if int == waitable_status_block { - return Blocking - } - let amt = int >> 4 - match int & 0xf { - 0 => Completed(amt) - 1 => Dropped(amt) - 2 => Cancelled(amt) - _ => panic() - } -} - -///| -pub fn WaitableStatus::count(int : Int) -> Int { - int >> 4 -} - -///| -pub(all) enum CallbackCode { - Exit - Yield - Wait(Int) - Cancel(Int) -} derive(Eq, Show) - -///| -pub fn CallbackCode::encode(self : Self) -> Int { - match self { - Exit => 0 - Yield => 1 - Wait(id) => 2 | (id << 4) - Cancel(id) => 3 | (id << 4) - } -} - -///| -pub fn CallbackCode::decode(int : Int) -> CallbackCode { - let id = int >> 4 - match int & 0xf { - 0 => Exit - 1 => Yield - 2 => Wait(id) - 3 => Cancel(id) - _ => panic() - } -} - -///| -/// This function is empty, If you want to print debug info, you can hook it in your environment. -pub fn _async_debug(_msg : String) -> Unit { - -} - -// Component async primitives - -///| -pub fn yield_blocking() -> Bool = "$root" "[yield]" - -///| -pub fn backpressure_set() -> Int = "$root" "[backpressure-set]" - -///| -pub fn subtask_cancel(id : Int) -> Int = "$root" "[subtask-cancel]" - -///| -pub fn subtask_drop(id : Int) = "$root" "[subtask-drop]" - -///| -pub fn context_set(task : Int) = "$root" "[context-set-0]" - -///| -pub fn context_get() -> Int = "$root" "[context-get-0]" - -///| -pub fn task_cancel() = "[export]$root" "[task-cancel]" - -///| -pub fn waitable_set_new() -> Int = "$root" "[waitable-set-new]" - -///| -pub fn waitable_set_drop(set : Int) = "$root" "[waitable-set-drop]" - -///| -pub fn waitable_join(waitable : Int, set : Int) = "$root" "[waitable-join]" - -///| -pub fn waitable_set_wait(set : Int, result_ptr : Int) -> Int = "$root" "[waitable-set-wait]" - -///| -pub fn waitable_set_poll(set : Int, result_ptr : Int) -> Int = "$root" "[waitable-set-poll]" diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index 8635ecf71..211941b3d 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -11,12 +11,6 @@ struct LangConfig { path: String, #[serde(default)] pkg_config: Option, - #[serde(default = "default_encoding")] - encoding: String, -} - -fn default_encoding() -> String { - "utf16".to_string() } pub struct MoonBit; @@ -47,14 +41,6 @@ impl LanguageMethods for MoonBit { fn compile(&self, runner: &Runner, compile: &crate::Compile) -> anyhow::Result<()> { let config = compile.component.deserialize_lang_config::()?; - let encoding = match config.encoding.as_str() { - "utf8" | "utf16" | "compact-utf16" => config.encoding.as_str(), - other => { - bail!( - "unsupported MoonBit component encoding `{other}`; expected one of: utf8, utf16, compact-utf16" - ) - } - }; // Copy the file to the bindings directory if !config.path.is_empty() { let src_path = &compile.component.path; @@ -111,7 +97,7 @@ impl LanguageMethods for MoonBit { let mut cmd = Command::new("wasm-tools"); cmd.arg("component") .arg("embed") - .args(["--encoding", encoding]) + .args(["--encoding", "utf16"]) .args(["-o", embedded.to_str().unwrap()]) .args(["-w", &compile.component.bindgen.world]) .arg(manifest_dir) From 90a9740db749f64101c290e39d31facd1157486d Mon Sep 17 00:00:00 2001 From: yezihang Date: Thu, 5 Mar 2026 15:14:31 +0800 Subject: [PATCH 61/61] test(runtime-async): add rust test components for moonbit future/stream cases --- .../async/moonbit-future-write/test.rs | 45 +++++++++++++++++++ .../async/moonbit-stream-write/test.rs | 35 +++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 tests/runtime-async/async/moonbit-future-write/test.rs create mode 100644 tests/runtime-async/async/moonbit-stream-write/test.rs diff --git a/tests/runtime-async/async/moonbit-future-write/test.rs b/tests/runtime-async/async/moonbit-future-write/test.rs new file mode 100644 index 000000000..2217da3af --- /dev/null +++ b/tests/runtime-async/async/moonbit-future-write/test.rs @@ -0,0 +1,45 @@ +use wit_bindgen::FutureReader; + +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +impl crate::exports::my::test::i::Guest for Component { + async fn create_future_with_value(value: u32) -> FutureReader { + let (tx, rx) = wit_future::new(|| unreachable!()); + wit_bindgen::spawn(async move { + tx.write(value).await.unwrap(); + }); + rx + } + + async fn create_unit_future() -> FutureReader<()> { + let (tx, rx) = wit_future::new(|| unreachable!()); + wit_bindgen::spawn(async move { + tx.write(()).await.unwrap(); + }); + rx + } + + async fn create_nested_future(value: u32) -> FutureReader> { + let (inner_tx, inner_rx) = wit_future::new(|| unreachable!()); + let (outer_tx, outer_rx) = wit_future::new(|| unreachable!()); + wit_bindgen::spawn(async move { + outer_tx.write(inner_rx).await.unwrap(); + inner_tx.write(value).await.unwrap(); + }); + outer_rx + } + + async fn create_nested_future_record(value: u32) -> crate::exports::my::test::i::NestedFutureRecord { + let (inner_tx, inner_rx) = wit_future::new(|| unreachable!()); + let (outer_tx, outer_rx) = wit_future::new(|| unreachable!()); + wit_bindgen::spawn(async move { + outer_tx.write(inner_rx).await.unwrap(); + inner_tx.write(value).await.unwrap(); + }); + crate::exports::my::test::i::NestedFutureRecord { nested: outer_rx } + } +} diff --git a/tests/runtime-async/async/moonbit-stream-write/test.rs b/tests/runtime-async/async/moonbit-stream-write/test.rs new file mode 100644 index 000000000..726db5a22 --- /dev/null +++ b/tests/runtime-async/async/moonbit-stream-write/test.rs @@ -0,0 +1,35 @@ +use wit_bindgen::{StreamReader, StreamResult}; + +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +impl crate::exports::my::test::i::Guest for Component { + async fn create_stream_with_values(count: u32) -> StreamReader { + let (mut tx, rx) = wit_stream::new(); + wit_bindgen::spawn(async move { + for i in 0..count { + let (result, _rest) = tx.write(vec![i]).await; + if !matches!(result, StreamResult::Complete(1)) { + break; + } + } + }); + rx + } + + async fn create_unit_stream(count: u32) -> StreamReader<()> { + let (mut tx, rx) = wit_stream::new(); + wit_bindgen::spawn(async move { + for _ in 0..count { + let (result, _rest) = tx.write(vec![()]).await; + if !matches!(result, StreamResult::Complete(1)) { + break; + } + } + }); + rx + } +}