From c9ea12f893a211b48ebf12bd99036e96fa12b4ce Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 6 Mar 2026 11:39:11 -0800 Subject: [PATCH 01/55] Basic setup of `wit-bindgen-d` --- Cargo.lock | 15 + Cargo.toml | 6 +- crates/d/Cargo.toml | 33 ++ crates/d/LICENSE-APACHE | 1 + .../d/LICENSE-Apache-2.0_WITH_LLVM-exception | 1 + crates/d/LICENSE-MIT | 1 + crates/d/README.md | 17 + crates/d/src/lib.rs | 327 ++++++++++++++++++ src/bin/wit-bindgen.rs | 11 + 9 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 crates/d/Cargo.toml create mode 120000 crates/d/LICENSE-APACHE create mode 120000 crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception create mode 120000 crates/d/LICENSE-MIT create mode 100644 crates/d/README.md create mode 100644 crates/d/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ec2bd3b10..649c59a73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,6 +1396,7 @@ dependencies = [ "wit-bindgen-core", "wit-bindgen-cpp", "wit-bindgen-csharp", + "wit-bindgen-d", "wit-bindgen-go", "wit-bindgen-markdown", "wit-bindgen-moonbit", @@ -1447,6 +1448,20 @@ dependencies = [ "wit-parser", ] +[[package]] +name = "wit-bindgen-d" +version = "0.60.0" +dependencies = [ + "anyhow", + "clap", + "heck", + "indexmap", + "wasm-encoder 0.257.0", + "wasm-metadata 0.257.0", + "wit-bindgen-core", + "wit-component", +] + [[package]] name = "wit-bindgen-go" version = "0.60.0" diff --git a/Cargo.toml b/Cargo.toml index c1344cd1f..e8e1ccb7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ wit-bindgen-csharp = { path = 'crates/csharp', version = '0.60.0' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.60.0' } wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.60.0' } wit-bindgen-go = { path = 'crates/go', version = '0.60.0' } +wit-bindgen-d = { path = 'crates/d', version = '0.60.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.60.0', default-features = false } wit-bindgen-test = { path = 'crates/test', version = '0.60.0' } @@ -95,6 +96,7 @@ wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } wit-bindgen-go = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-d = { workspace = true, features = ['clap'], optional = true } wit-bindgen-test = { workspace = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } @@ -109,7 +111,8 @@ default = [ 'csharp', 'cpp', 'moonbit', - 'async', + 'd', + 'async' ] c = ['dep:wit-bindgen-c'] cpp = ['dep:wit-bindgen-cpp'] @@ -119,4 +122,5 @@ go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] +d = ['dep:wit-bindgen-d'] async = [] diff --git a/crates/d/Cargo.toml b/crates/d/Cargo.toml new file mode 100644 index 000000000..e90a308c9 --- /dev/null +++ b/crates/d/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "wit-bindgen-d" +authors = ["Demetrius Kanios "] +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +homepage = 'https://github.com/bytecodealliance/wit-bindgen' +description = """ +D bindings generator for WIT and the component model, typically used through the +`wit-bindgen-cli` crate. +""" + +[lints] +workspace = true + +[lib] +doctest = false +test = false + +[dependencies] +wit-bindgen-core = { workspace = true } +wit-component = { workspace = true } +wasm-encoder = { workspace = true } +wasm-metadata = { workspace = true } +anyhow = { workspace = true } +heck = { workspace = true } +clap = { workspace = true, optional = true } +indexmap = { workspace = true } + +[features] +clap = ['dep:clap', 'wit-bindgen-core/clap'] diff --git a/crates/d/LICENSE-APACHE b/crates/d/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/crates/d/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception b/crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception new file mode 120000 index 000000000..3a28a354e --- /dev/null +++ b/crates/d/LICENSE-Apache-2.0_WITH_LLVM-exception @@ -0,0 +1 @@ +../../LICENSE-Apache-2.0_WITH_LLVM-exception \ No newline at end of file diff --git a/crates/d/LICENSE-MIT b/crates/d/LICENSE-MIT new file mode 120000 index 000000000..b2cfbdc7b --- /dev/null +++ b/crates/d/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/crates/d/README.md b/crates/d/README.md new file mode 100644 index 000000000..ebb79098b --- /dev/null +++ b/crates/d/README.md @@ -0,0 +1,17 @@ +# `wit-bindgen` D Bindings Generator + +This tool generates [D](https://dlang.org) bindings for a chosen WIT world. + +## Usage + +To generate bindings with this crate, issue the `d` subcommand to `wit-bindgen`: + +```bash +$ wit-bindgen d [OPTIONS] +``` + +See the output of `wit-bindgen help d` for available options. + +------- + +TODO: Flesh out fuller docs (ownership, more usage, examples, etc.) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs new file mode 100644 index 000000000..162728983 --- /dev/null +++ b/crates/d/src/lib.rs @@ -0,0 +1,327 @@ +use anyhow::Result; +use heck::*; +use std::collections::{BTreeSet, HashMap}; +use std::path::PathBuf; +use wit_bindgen_core::{Files, Source, WorldGenerator, wit_parser::*}; + +#[derive(Default)] +struct D { + world_src: Source, + opts: Opts, + + cur_world_fqn: String, + interfaces: HashMap, +} + +#[derive(Default)] +struct InterfaceSource { + fqn: String, + src: Source, + imported: bool, + exported: bool, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "clap", derive(clap::Parser))] +pub struct Opts { + /// Where to place output files + #[cfg_attr(feature = "clap", arg(skip))] + out_dir: Option, +} + +impl Opts { + pub fn build(mut self, out_dir: Option<&PathBuf>) -> Box { + let mut r = D::default(); + self.out_dir = out_dir.cloned(); + r.opts = self.clone(); + Box::new(r) + } +} + +fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { + let mut ns = String::new(); + + let pkg = &resolve.packages[id]; + ns.push_str("wit."); + ns.push_str(&pkg.name.namespace.to_snake_case()); + ns.push_str("."); + ns.push_str(&pkg.name.name.to_snake_case()); + ns.push_str("."); + let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { + p.name.namespace == pkg.name.namespace + && p.name.name == pkg.name.name + && p.name.version != pkg.name.version + }); + if pkg_has_multiple_versions { + if let Some(version) = &pkg.name.version { + let version = version + .to_string() + .replace('.', "_") + .replace('-', "_") + .replace('+', "_"); + ns.push_str(&version); + ns.push_str("."); + } + } + ns +} + +fn get_interface_fqn( + interface_id: &WorldKey, + cur_world_fqn: &String, + resolve: &Resolve, + is_export: bool, +) -> String { + let mut ns = String::new(); + match interface_id { + WorldKey::Name(name) => { + ns.push_str(cur_world_fqn); + if is_export { + ns.push_str(".exports") + } else { + ns.push_str(".imports") + } + ns.push_str("."); + ns.push_str(&name.to_snake_case()) + } + WorldKey::Interface(id) => { + let iface = &resolve.interfaces[*id]; + ns.push_str(&get_package_fqn(iface.package.unwrap(), resolve)); + ns.push_str(&iface.name.as_ref().unwrap().to_snake_case()) + } + } + ns +} + +fn get_world_fqn(id: WorldId, resolve: &Resolve) -> String { + let mut ns = String::new(); + + let world = &resolve.worlds[id]; + ns.push_str(&get_package_fqn(world.package.unwrap(), resolve)); + ns.push_str(&world.name.to_snake_case()); + ns +} + +impl D { + fn prepare_interface_bindings( + &self, + id: InterfaceId, + fqn: &String, + cur_world_fqn: &String, + resolve: &Resolve, + ) -> Source { + let mut src = Source::default(); + let interface = &resolve.interfaces[id]; + + match &interface.docs.contents { + Some(docs) => src.push_str(&format!("/++\n{docs}\n+/\n")), + None => {} + } + + src.push_str(&format!("module {};\n\n", fqn)); + + let mut deps = BTreeSet::new(); + + for dep_id in resolve.interface_direct_deps(id) { + deps.insert(dep_id); + } + + for dep_id in deps { + let wrapped_dep_id = WorldKey::Interface(dep_id); + src.push_str(&format!( + "import {};\n", + get_interface_fqn(&wrapped_dep_id, cur_world_fqn, resolve, false) + )); + } + + src.push_str("\n// Type defines\n"); + + for (name, id) in &interface.types { + src.push_str(&format!("// Define type: {name}\n")); + } + + src + } +} + +impl WorldGenerator for D { + fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { + self.cur_world_fqn = get_world_fqn(world, resolve); + + let world = &resolve.worlds[world]; + match &world.docs.contents { + Some(docs) => self.world_src.push_str(&format!("/++\n{docs}\n+/\n")), + None => {} + } + self.world_src + .push_str(&format!("module {};\n\n", self.cur_world_fqn)); + + self.world_src.push_str("// Interface imports\n"); + } + + fn import_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + _files: &mut Files, + ) -> Result<()> { + let interface_src = match self.interfaces.get_mut(&id) { + Some(src) => src, + None => { + let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, false); + let new_src = + self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); + + let mut result = InterfaceSource::default(); + result.fqn = new_fqn; + result.src = new_src; + + self.interfaces.insert(id, result); + self.interfaces.get_mut(&id).unwrap() + } + }; + + if interface_src.imported { + return Ok(()); + } + interface_src.imported = true; + + self.world_src + .push_str(&format!("public import {}\n", &self.interfaces[&id].fqn)); + + Ok(()) + } + + fn import_types( + &mut self, + resolve: &Resolve, + world: WorldId, + types: &[(&str, TypeId)], + _files: &mut Files, + ) { + self.world_src.push_str(&format!("\n// Type imports\n")); + for (name, id) in types { + self.world_src + .push_str(&format!("// Define type: {name}\n")); + } + } + + fn import_funcs( + &mut self, + resolve: &Resolve, + world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + self.world_src.push_str(&format!("\n// Function imports\n")); + for (name, func) in funcs { + self.world_src + .push_str(&format!("// Import function: {name}\n")); + } + } + + fn pre_export_interface(&mut self, resolve: &Resolve, files: &mut Files) -> Result<()> { + self.world_src.push_str("\n// Interface exports\n"); + self.world_src + .push_str("mixin template Exports(alias Impl) {\n"); + self.world_src.indent(1); + + Ok(()) + } + + fn export_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + _files: &mut Files, + ) -> Result<()> { + let interface = &resolve.interfaces[id]; + let interface_src = match self.interfaces.get_mut(&id) { + Some(src) => src, + None => { + let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, true); + let new_src = + self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); + + let mut result = InterfaceSource::default(); + result.fqn = new_fqn; + result.src = new_src; + + self.interfaces.insert(id, result); + + self.interfaces.get_mut(&id).unwrap() + } + }; + + if interface_src.exported { + return Ok(()); + } + interface_src.exported = true; + + self.world_src.push_str(&format!( + "mixin imported!\"{}\".Exports!Impl;\n", + interface_src.fqn + )); + + interface_src + .src + .push_str("\nmixin template Exports(alias Impl) {\n"); + interface_src.src.indent(1); + + interface_src + .src + .push_str(&format!("// Function exports\n")); + for (name, func) in &interface.functions { + interface_src + .src + .push_str(&format!("// Export function: {name}\n")); + } + + interface_src.src.deindent(1); + interface_src.src.push_str("}\n"); + + Ok(()) + } + + fn export_funcs( + &mut self, + resolve: &Resolve, + world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) -> Result<()> { + self.world_src.push_str(&format!("\n// Function exports\n")); + for (name, func) in funcs { + self.world_src + .push_str(&format!("// Export function: {name}\n")); + } + Ok(()) + } + + fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { + // Close out interface exports + self.world_src.deindent(1); + self.world_src.push_str("}\n"); + + let mut world_filepath = PathBuf::from_iter(get_world_fqn(id, resolve).split(".")); + world_filepath.push("package.d"); + + files.push( + world_filepath.to_str().unwrap(), + self.world_src.as_str().as_bytes(), + ); + + for (_, interface_src) in &self.interfaces { + let mut interface_filepath = PathBuf::from_iter(interface_src.fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push( + interface_filepath.to_str().unwrap(), + interface_src.src.as_bytes(), + ); + } + Ok(()) + } +} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 04e38f7f0..6f0ff8fed 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -74,6 +74,15 @@ enum Opt { args: Common, }, + /// Generates bindings for D guest modules. + #[cfg(feature = "d")] + D { + #[clap(flatten)] + opts: wit_bindgen_d::Opts, + #[clap(flatten)] + args: Common, + }, + // doc-comments are present on `wit_bindgen_test::Opts` for clap to use. Test { #[clap(flatten)] @@ -150,6 +159,8 @@ fn main() -> Result<()> { Opt::Go { opts, args } => (opts.build(), args), #[cfg(feature = "csharp")] Opt::Csharp { opts, args } => (opts.build(), args), + #[cfg(feature = "d")] + Opt::D { opts, args } => (opts.build(args.out_dir.as_ref()), args), Opt::Test { opts } => return opts.run(std::env::args_os().nth(0).unwrap().as_ref()), }; From c03aa43b189870444d6de6492ab75feb324cb4f0 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 9 Mar 2026 23:12:29 -0700 Subject: [PATCH 02/55] Initial support for most types. --- crates/d/src/lib.rs | 609 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 573 insertions(+), 36 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 162728983..7187c6ccb 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1,5 +1,6 @@ use anyhow::Result; use heck::*; +use std::borrow::Cow; use std::collections::{BTreeSet, HashMap}; use std::path::PathBuf; use wit_bindgen_core::{Files, Source, WorldGenerator, wit_parser::*}; @@ -11,6 +12,8 @@ struct D { cur_world_fqn: String, interfaces: HashMap, + + cur_interface: Option, } #[derive(Default)] @@ -38,14 +41,141 @@ impl Opts { } } +fn escape_d_identifier(name: &str) -> &str { + match name { + // Escape D keywords. + // Source: https://dlang.org/spec/lex.html#keywords + "abstract" => "abstract_", + "alias" => "alias_", + "align" => "align_", + "asm" => "asm_", + "assert" => "assert_", + "auto" => "auto_", + + "body" => "body_", + "bool" => "bool_", + "break" => "break_", + "byte" => "byte_", + + "case" => "case_", + "cast" => "cast_", + "catch" => "catch_", + "cdouble" => "cdouble_", + "cent" => "cent_", + "cfloat" => "cfloat_", + "char" => "char_", + "class" => "class_", + "const" => "const_", + "continue" => "continue_", + "creal" => "creal_", + + "dchar" => "dchar_", + "debug" => "debug_", + "default" => "default_", + "delegate" => "delegate_", + "delete" => "delete_", + "deprecated" => "deprecated_", + "do" => "do_", + "double" => "double_", + + "else" => "else_", + "enum" => "enum_", + "export" => "export_", + "extern" => "extern_", + + "false" => "false_", + "final" => "final_", + "finally" => "finally_", + "float" => "float_", + "for" => "for_", + "foreach" => "foreach_", + "foreach_reverse" => "foreach_reverse_", + "function" => "function_", + + "goto" => "goto_", + + "idouble" => "idouble_", + "if" => "if_", + "ifloat" => "ifloat_", + "immutable" => "immutable_", + "import" => "import_", + "in" => "in_", + "inout" => "inout_", + "int" => "int_", + "interface" => "interface_", + "invariant" => "invariant_", + "ireal" => "ireal_", + "is" => "is_", + + "lazy" => "lazy_", + "long" => "long_", + + "macro" => "macro_", + "mixin" => "mixin_", + "module" => "module_", + + "new" => "new_", + "nothrow" => "nothrow_", + "null" => "null_", + + "out" => "out_", + "override" => "override_", + + "package" => "package_", + "pragma" => "pragma_", + "private" => "private_", + "protected" => "protected_", + "public" => "public_", + "pure" => "pure_", + + "real" => "real_", + "ref" => "ref_", + "return" => "return_", + + "scope" => "scope_", + "shared" => "shared_", + "short" => "short_", + "static" => "static_", + "struct" => "struct_", + "super" => "super_", + "switch" => "switch_", + "synchronized" => "synchronized_", + + "template" => "template_", + "this" => "this_", + "throw" => "throw_", + "true" => "true_", + "try" => "try_", + "typeid" => "typeid_", + "typeof" => "typeof_", + + "ubyte" => "ubyte_", + "ucent" => "ucent_", + "uint" => "uint_", + "ulong" => "ulong_", + "union" => "union_", + "unittest" => "unittest_", + "ushort" => "ushort_", + + "version" => "version_", + "void" => "void_", + + "wchar" => "wchar_", + "while" => "while_", + "with" => "with_", + + s => s, + } +} + fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { let mut ns = String::new(); let pkg = &resolve.packages[id]; ns.push_str("wit."); - ns.push_str(&pkg.name.namespace.to_snake_case()); + ns.push_str(escape_d_identifier(&pkg.name.namespace.to_snake_case())); ns.push_str("."); - ns.push_str(&pkg.name.name.to_snake_case()); + ns.push_str(escape_d_identifier(&pkg.name.name.to_snake_case())); ns.push_str("."); let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { p.name.namespace == pkg.name.namespace @@ -82,12 +212,14 @@ fn get_interface_fqn( ns.push_str(".imports") } ns.push_str("."); - ns.push_str(&name.to_snake_case()) + ns.push_str(escape_d_identifier(&name.to_snake_case())) } WorldKey::Interface(id) => { let iface = &resolve.interfaces[*id]; ns.push_str(&get_package_fqn(iface.package.unwrap(), resolve)); - ns.push_str(&iface.name.as_ref().unwrap().to_snake_case()) + ns.push_str(escape_d_identifier( + &iface.name.as_ref().unwrap().to_snake_case(), + )) } } ns @@ -98,11 +230,42 @@ fn get_world_fqn(id: WorldId, resolve: &Resolve) -> String { let world = &resolve.worlds[id]; ns.push_str(&get_package_fqn(world.package.unwrap(), resolve)); - ns.push_str(&world.name.to_snake_case()); + ns.push_str(escape_d_identifier(&world.name.to_snake_case())); ns } impl D { + fn get_type_fqn(&self, name: &str, owner: &TypeOwner) -> String { + match owner { + TypeOwner::None => String::from(name), + TypeOwner::Interface(id) => { + format!( + "{}.{}", + self.interfaces[id].fqn, + escape_d_identifier(&name.to_upper_camel_case()) + ) + } + TypeOwner::World(_) => format!( + "{}.{}", + self.cur_world_fqn, + escape_d_identifier(&name.to_upper_camel_case()) + ), + } + } + + fn get_type_name(&self, name: &str, owner: &TypeOwner) -> String { + match &owner { + TypeOwner::Interface(id) => Some(id), + _ => None, + } + .zip(self.cur_interface.as_ref()) + .filter(|(id, cur_id)| id == cur_id) + .map_or_else( + || self.get_type_fqn(name, owner), + |_| escape_d_identifier(&name.to_upper_camel_case()).into(), + ) + } + fn prepare_interface_bindings( &self, id: InterfaceId, @@ -113,13 +276,15 @@ impl D { let mut src = Source::default(); let interface = &resolve.interfaces[id]; - match &interface.docs.contents { - Some(docs) => src.push_str(&format!("/++\n{docs}\n+/\n")), - None => {} - } + src.push_str(&format!( + "/++\n{}\n+/\n", + interface.docs.contents.as_deref().unwrap_or_default() + )); src.push_str(&format!("module {};\n\n", fqn)); + src.push_str("import wit.common;\n\n"); + let mut deps = BTreeSet::new(); for dep_id in resolve.interface_direct_deps(id) { @@ -129,7 +294,7 @@ impl D { for dep_id in deps { let wrapped_dep_id = WorldKey::Interface(dep_id); src.push_str(&format!( - "import {};\n", + "static import {};\n", get_interface_fqn(&wrapped_dep_id, cur_world_fqn, resolve, false) )); } @@ -137,25 +302,384 @@ impl D { src.push_str("\n// Type defines\n"); for (name, id) in &interface.types { - src.push_str(&format!("// Define type: {name}\n")); + let type_src = self.generate_type_declaration(name, *id, resolve); + src.append_src(&type_src); } src } + + fn generate_type_use(&self, r#type: &Type, resolve: &Resolve) -> Cow<'static, str> { + match r#type { + Type::Bool => Cow::Borrowed("bool"), + Type::U8 => Cow::Borrowed("ubyte"), + Type::U16 => Cow::Borrowed("ushort"), + Type::U32 => Cow::Borrowed("uint"), + Type::U64 => Cow::Borrowed("ulong"), + Type::S8 => Cow::Borrowed("byte"), + Type::S16 => Cow::Borrowed("short"), + Type::S32 => Cow::Borrowed("int"), + Type::S64 => Cow::Borrowed("long"), + Type::F32 => Cow::Borrowed("float"), + Type::F64 => Cow::Borrowed("double"), + Type::Char => Cow::Borrowed("dchar"), + Type::String => Cow::Borrowed("String"), + Type::ErrorContext => { + todo!("use of `error_context`!"); + } + Type::Id(id) => { + let typedef = &resolve.types[*id]; + match &typedef.owner { + TypeOwner::None => match &typedef.kind { + TypeDefKind::Handle(handle) => todo!("use of `TypeDefKind::Handle`"), + TypeDefKind::Tuple(tuple) => Cow::Owned(format!( + "Tuple!({})", + tuple + .types + .iter() + .map(|ty| self.generate_type_use(ty, resolve).into_owned()) + .collect::>() + .join(", ") + )), + TypeDefKind::Option(opt_type) => Cow::Owned(format!( + "Option!({})", + self.generate_type_use(opt_type, resolve) + )), + TypeDefKind::Result(result) => Cow::Owned(format!( + "Result!({}, {})", + match result.ok { + Some(ok_type) => self.generate_type_use(&ok_type, resolve), + None => Cow::Borrowed("void"), + }, + match result.err { + Some(err_type) => self.generate_type_use(&err_type, resolve), + None => Cow::Borrowed("void"), + } + )), + TypeDefKind::List(list_type) => Cow::Owned(format!( + "List!({})", + self.generate_type_use(list_type, resolve) + )), + TypeDefKind::Map(_, _) => todo!("use of `TypeDefKind::Map`"), + TypeDefKind::FixedLengthList(list_type, length) => Cow::Owned(format!( + "{}[{length}]", + self.generate_type_use(list_type, resolve) + )), + TypeDefKind::Future(future_type) => todo!("use of `TypeDefKind::Future`"), + TypeDefKind::Stream(stream_type) => todo!("use of `TypeDefKind::Stream`"), + TypeDefKind::Type(target_type) => { + self.generate_type_use(target_type, resolve) + } + TypeDefKind::Unknown => { + panic!("Trying to emit type use for `TypeDefKind::Unknown`?"); + } + unhandled => { + panic!( + "Encountered unexpected use of ownerless typedef: {unhandled:?}." + ); + } + }, + _ => Cow::Owned( + self.get_type_name(typedef.name.as_ref().unwrap(), &typedef.owner), + ), + } + } + } + } + + fn generate_type_declaration(&self, name: &str, id: TypeId, resolve: &Resolve) -> Source { + let mut src = Source::default(); + + let typedef = &resolve.types[id]; + + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + src.push_str(&format!( + "\n/++\n{}\n+/\n", + typedef.docs.contents.as_deref().unwrap_or_default() + )); + match &typedef.kind { + TypeDefKind::Record(record) => { + src.push_str(&format!("struct {escaped_name} {{\n")); + + let mut is_first = true; + for field in &record.fields { + if is_first { + is_first = false; + } else { + src.push_str("\n"); + } + + src.push_str(&format!( + "/++\n{}\n+/\n", + field.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{} {};\n", + self.generate_type_use(&field.ty, resolve), + field.name.to_lower_camel_case() + )); + } + + src.push_str("}\n"); + } + TypeDefKind::Resource => { + //src.push_str(&format!("// TODO: def of resource - {name}")) + todo!("def of `TypeDefKind::Resource`"); + } + TypeDefKind::Handle(handle) => { + todo!("def of `TypeDefKind::Handle`"); + } + TypeDefKind::Flags(flags) => { + let storage_type = match flags.repr() { + FlagsRepr::U8 => "ubyte", + FlagsRepr::U16 => "ushort", + FlagsRepr::U32(1) => "uint", + FlagsRepr::U32(2) => "ulong", + repr => todo!("flags {repr:?}"), + }; + + src.push_str(&format!("enum {escaped_name}_ : {storage_type} {{\n")); + for (index, flag) in flags.flags.iter().enumerate() { + if index != 0 { + src.push_str("\n"); + } + src.push_str(&format!( + "/++\n{}\n+/\n", + flag.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{} = 1 << {index},\n", + escape_d_identifier(&flag.name.to_lower_camel_case()) + )); + } + src.push_str(&format!( + "}}\n/// ditto\nalias {escaped_name} = Flags!{escaped_name}_;" + )); + } + TypeDefKind::Tuple(tuple) => src.push_str(&format!( + "alias {escaped_name} = Tuple!({});", + tuple + .types + .iter() + .map(|ty| self.generate_type_use(ty, resolve).into_owned()) + .collect::>() + .join(", ") + )), + TypeDefKind::Variant(variant) => { + let storage_type = match variant.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", + }; + + src.push_str(&format!("struct {escaped_name} {{\n")); + src.deindent(1); + src.push_str(&format!("@safe @nogc nothrow:\n")); + src.indent(1); + + src.push_str(&format!("enum Tag : {storage_type} {{\n")); + + let mut is_first = true; + for case in &variant.cases { + if is_first { + is_first = false; + } else { + src.push_str("\n"); + } + src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + + src.push_str("}\n"); + + src.deindent(1); + src.push_str(&format!("\nprivate:\n")); + src.indent(1); + + if variant.cases.iter().any(|case| case.ty.is_some()) { + src.push_str(&format!("union Storage {{\n")); + src.push_str("ubyte __zeroinit = 0;\n"); + for case in &variant.cases { + if let Some(ty) = &case.ty { + src.push_str(&format!( + "{} {};\n", + self.generate_type_use(ty, resolve), + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + } + + src.push_str("}\n\n"); + + src.push_str("Tag _tag;\n"); + src.push_str("Storage _storage;\n\n"); + } else { + src.push_str("Tag _tag;\n\n"); + } + + src.push_str("@disable this();\n"); + src.push_str("this(Tag tag, Storage storage = Storage.init) {\n"); + src.push_str("_tag = tag;\n"); + src.push_str("_storage = storage;\n"); + src.push_str("}\n"); + + src.deindent(1); + src.push_str(&format!("\npublic:\n")); + src.indent(1); + + src.push_str("Tag tag() => _tag;\n"); + + for case in &variant.cases { + src.push_str(&format!( + "\n/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + let upper_case_name = case.name.to_upper_camel_case(); + let escaped_upper_case_name = escape_d_identifier(&upper_case_name); + + let lower_case_name = case.name.to_lower_camel_case(); + let escaped_lower_case_name = escape_d_identifier(&lower_case_name); + + if let Some(ty) = &case.ty { + src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}({} val) {{\n", + self.generate_type_use(ty, resolve) + )); + src.push_str("Storage storage;\n"); + src.push_str(&format!("storage.{escaped_lower_case_name} = val;\n")); + src.push_str(&format!( + "return {escaped_name}(Tag.{escaped_lower_case_name}, storage);\n" + )); + src.push_str("}\n"); + + src.push_str(&format!( + "/// ditto\n ref inout({}) get{escaped_upper_case_name}() inout return\n", + self.generate_type_use(ty, resolve) + )); + + src.push_str(&format!("in (is{escaped_upper_case_name}) ")); + src.push_str(&format!( + "do {{ return _storage.{escaped_lower_case_name}; }}\n" + )); + } else { + src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}() => {escaped_name}(Tag.{escaped_lower_case_name});\n", + )); + } + src.push_str(&format!( + "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", + )); + } + + src.push_str("}\n"); + } + TypeDefKind::Enum(r#enum) => { + let storage_type = match r#enum.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", + }; + + src.push_str(&format!("enum {escaped_name} : {storage_type} {{\n")); + + let mut is_first = true; + for case in &r#enum.cases { + if is_first { + is_first = false; + } else { + src.push_str("\n"); + } + src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + + src.push_str(&format!("}}")); + } + TypeDefKind::Option(opt_type) => src.push_str(&format!( + "alias {escaped_name} = Option!({});", + self.generate_type_use(opt_type, resolve) + )), + TypeDefKind::Result(result) => src.push_str(&format!( + "alias {escaped_name} = Result!({}, {});", + match result.ok { + Some(ok_type) => self.generate_type_use(&ok_type, resolve), + None => Cow::Borrowed("void"), + }, + match result.err { + Some(err_type) => self.generate_type_use(&err_type, resolve), + None => Cow::Borrowed("void"), + } + )), + TypeDefKind::List(list_type) => src.push_str(&format!( + "alias {escaped_name} = List!({});", + self.generate_type_use(list_type, resolve) + )), + TypeDefKind::Map(_, _) => { + todo!("def of `TypeDefKind::Map`"); + } + TypeDefKind::FixedLengthList(list_type, length) => { + src.push_str(&format!( + "alias {escaped_name} = {}[{length}];", + self.generate_type_use(&list_type, resolve), + )); + } + TypeDefKind::Future(future_type) => { + todo!("def of `TypeDefKind::Future`"); + } + TypeDefKind::Stream(stream_type) => { + todo!("def of `TypeDefKind::Stream`"); + } + TypeDefKind::Type(target_type) => { + src.push_str(&format!( + "alias {escaped_name} = {};", + self.generate_type_use(&target_type, resolve), + )); + } + TypeDefKind::Unknown => { + panic!("Trying to emit type declaration for `TypeDefKind::Unknown`?"); + } + } + src.push_str("\n"); + src + } } impl WorldGenerator for D { + fn uses_nominal_type_ids(&self) -> bool { + false + } + fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { self.cur_world_fqn = get_world_fqn(world, resolve); let world = &resolve.worlds[world]; - match &world.docs.contents { - Some(docs) => self.world_src.push_str(&format!("/++\n{docs}\n+/\n")), - None => {} - } + + self.world_src.push_str(&format!( + "/++\n{}\n+/\n", + world.docs.contents.as_deref().unwrap_or_default() + )); + self.world_src .push_str(&format!("module {};\n\n", self.cur_world_fqn)); + self.world_src.push_str("import wit.common;\n\n"); + self.world_src.push_str("// Interface imports\n"); } @@ -166,19 +690,27 @@ impl WorldGenerator for D { id: InterfaceId, _files: &mut Files, ) -> Result<()> { + self.cur_interface = Some(id); let interface_src = match self.interfaces.get_mut(&id) { Some(src) => src, None => { + eprintln!("Import {id:?}"); let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, false); - let new_src = - self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); - let mut result = InterfaceSource::default(); - result.fqn = new_fqn; - result.src = new_src; + let mut result_init = InterfaceSource::default(); + result_init.fqn = new_fqn; + self.interfaces.insert(id, result_init); + + let new_src = self.prepare_interface_bindings( + id, + &self.interfaces.get(&id).unwrap().fqn, + &self.cur_world_fqn, + resolve, + ); - self.interfaces.insert(id, result); - self.interfaces.get_mut(&id).unwrap() + let result = self.interfaces.get_mut(&id).unwrap(); + result.src = new_src; + result } }; @@ -188,8 +720,9 @@ impl WorldGenerator for D { interface_src.imported = true; self.world_src - .push_str(&format!("public import {}\n", &self.interfaces[&id].fqn)); + .push_str(&format!("public import {};\n", &self.interfaces[&id].fqn)); + self.cur_interface = None; Ok(()) } @@ -202,8 +735,8 @@ impl WorldGenerator for D { ) { self.world_src.push_str(&format!("\n// Type imports\n")); for (name, id) in types { - self.world_src - .push_str(&format!("// Define type: {name}\n")); + let type_src = self.generate_type_declaration(name, *id, resolve); + self.world_src.append_src(&type_src); } } @@ -225,7 +758,6 @@ impl WorldGenerator for D { self.world_src.push_str("\n// Interface exports\n"); self.world_src .push_str("mixin template Exports(alias Impl) {\n"); - self.world_src.indent(1); Ok(()) } @@ -237,21 +769,28 @@ impl WorldGenerator for D { id: InterfaceId, _files: &mut Files, ) -> Result<()> { + self.cur_interface = Some(id); let interface = &resolve.interfaces[id]; let interface_src = match self.interfaces.get_mut(&id) { Some(src) => src, None => { + eprintln!("Export {id:?}"); let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, true); - let new_src = - self.prepare_interface_bindings(id, &new_fqn, &self.cur_world_fqn, resolve); - let mut result = InterfaceSource::default(); - result.fqn = new_fqn; - result.src = new_src; + let mut result_init = InterfaceSource::default(); + result_init.fqn = new_fqn; + self.interfaces.insert(id, result_init); - self.interfaces.insert(id, result); + let new_src = self.prepare_interface_bindings( + id, + &self.interfaces.get(&id).unwrap().fqn, + &self.cur_world_fqn, + resolve, + ); - self.interfaces.get_mut(&id).unwrap() + let result = self.interfaces.get_mut(&id).unwrap(); + result.src = new_src; + result } }; @@ -268,7 +807,6 @@ impl WorldGenerator for D { interface_src .src .push_str("\nmixin template Exports(alias Impl) {\n"); - interface_src.src.indent(1); interface_src .src @@ -279,9 +817,9 @@ impl WorldGenerator for D { .push_str(&format!("// Export function: {name}\n")); } - interface_src.src.deindent(1); interface_src.src.push_str("}\n"); + self.cur_interface = None; Ok(()) } @@ -302,7 +840,6 @@ impl WorldGenerator for D { fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { // Close out interface exports - self.world_src.deindent(1); self.world_src.push_str("}\n"); let mut world_filepath = PathBuf::from_iter(get_world_fqn(id, resolve).split(".")); From 4fdcb12d640d18235a5d64ba4f6305323f2c48d2 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 10 Mar 2026 16:43:36 -0700 Subject: [PATCH 03/55] Basic implementation of `wit.common` type templates. [skip ci] --- crates/d/src/lib.rs | 2 + crates/d/src/wit_common.d | 275 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 crates/d/src/wit_common.d diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 7187c6ccb..4d6e8cead 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -850,6 +850,8 @@ impl WorldGenerator for D { self.world_src.as_str().as_bytes(), ); + files.push("wit/common.d", include_bytes!("wit_common.d")); + for (_, interface_src) in &self.interfaces { let mut interface_filepath = PathBuf::from_iter(interface_src.fqn.split(".")); interface_filepath.add_extension("d"); diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d new file mode 100644 index 000000000..f5d3f67f5 --- /dev/null +++ b/crates/d/src/wit_common.d @@ -0,0 +1,275 @@ +module wit.common; + +/// Thin CABI compliant wrapper over `T[]` +struct List(T) { +@safe @nogc pure nothrow: + T* ptr; + size_t length; + + this(T[] slice) @trusted { + this = slice; + } + + void opAssign(T[] slice) @trusted { + ptr = slice.ptr; + length = slice.length; + } + + alias asSlice this; + inout(T)[] asSlice() @trusted inout { + return (ptr && length) ? ptr[0..length] : null; + } +} + +// WIT ABI for string matches List, +// except list in WIT is actually List!(dchar) +// +// We assume UTF-8 data (as D native strings are UTF-8) +alias String = List!(immutable char); + +// TODO: split this file up and give Tuple a full port of the Phobos version? +/// adapted from Phobos std.typecons.Tuple +/// No support for naming members. +struct Tuple(Types...) if (is(Types)) { + Types expand; + alias expand this; +} + +/// adapted from Phobos std.bitmanip.BitFlags +struct Flags(Enum) if (is(Enum == enum)) { +@safe @nogc pure nothrow: + public alias E = Enum; + +private: + template allAreBaseEnum(T...) + { + static foreach (Ti; T) + { + static if (!is(typeof(allAreBaseEnum) == bool) && // not yet defined + !is(Ti : E)) + { + enum allAreBaseEnum = false; + } + } + static if (!is(typeof(allAreBaseEnum) == bool)) // if not yet defined + { + enum allAreBaseEnum = true; + } + } + + static if (is(E U == enum)) { + alias Base = U; + } else static assert(0); + + Base mValue; + +public: + this(E flag) + { + this = flag; + } + + this(T...)(T flags) + if (allAreBaseEnum!(T)) + { + this = flags; + } + + bool opCast(B: bool)() const + { + return mValue != 0; + } + + Base opCast(B)() const + if (is(Base : B)) + { + return mValue; + } + + auto opUnary(string op)() const + if (op == "~") + { + return WitFlags(cast(E) cast(Base) ~mValue); + } + + auto ref opAssign(T...)(T flags) + if (allAreBaseEnum!(T)) + { + mValue = 0; + foreach (E flag; flags) + { + mValue |= flag; + } + return this; + } + + auto ref opAssign(E flag) + { + mValue = flag; + return this; + } + + auto ref opOpAssign(string op: "|")(WitFlags flags) + { + mValue |= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "&")(WitFlags flags) + { + mValue &= flags.mValue; + return this; + } + + auto ref opOpAssign(string op: "|")(E flag) + { + mValue |= flag; + return this; + } + + auto ref opOpAssign(string op: "&")(E flag) + { + mValue &= flag; + return this; + } + + auto opBinary(string op)(WitFlags flags) const + if (op == "|" || op == "&") + { + WitFlags result = this; + result.opOpAssign!op(flags); + return result; + } + + auto opBinary(string op)(E flag) const + if (op == "|" || op == "&") + { + WitFlags result = this; + result.opOpAssign!op(flag); + return result; + } + + auto opBinaryRight(string op)(E flag) const + if (op == "|" || op == "&") + { + return opBinary!op(flag); + } + + bool opDispatch(string name)() const + if (__traits(hasMember, E, name)) + { + enum e = __traits(getMember, E, name); + return (mValue & e) == e; + } + + void opDispatch(string name)(bool set) + if (__traits(hasMember, E, name)) + { + enum e = __traits(getMember, E, name); + if (set) + mValue |= e; + else + mValue &= ~e; + } +} + +/// Based on Rust's Option +struct Option(T) { +private: + bool present = false; + T value; + + @disable this(); + this(bool present, T value = T.init) @safe @nogc nothrow { + this.present = present; + this.value = value; + } +public: + static Option some(T value) @safe @nogc nothrow { + return Option(true, value); + } + + static Option none() @safe @nogc nothrow { + return Option(false); + } + + bool isSome() const @safe @nogc nothrow => present; + alias isSome this; // implicit conversion to bool + + bool isNone() const @safe @nogc nothrow => !present; + + ref inout(T) unwrap() inout @safe @nogc nothrow return + in (present) do { return value; } + + T unwrapOr(T fallback) @safe @nogc nothrow => present ? value : fallback; + + T unwrapOrElse(D)(scope D fallback) + if (is(D R == return) && is(R : T) && is(D == __parameters)) + { return present ? value : fallback(); } +} + +/// Based on Rust's Result +struct Result(T, E) { +private: + bool hasError; + union Storage { + ubyte __zeroinit = 0; + static if (!is(T == void)) { + T value; + } + static if (!is(E == void)) { + E error; + } + } + Storage storage; + + @disable this(); + this(bool hasError, Storage storage) @safe @nogc nothrow { + this.hasError = hasError; + this.storage = storage; + } + +public: + static if (is(T == void)) { + static Result ok() @safe @nogc nothrow => Result(false, Storage()); + } else { + static Result ok(T value) @safe @nogc nothrow { + Storage newStorage; + newStorage.value = value; + + return Result(false, newStorage); + } + } + + static if (is(E == void)) { + static Result err() @safe @nogc nothrow => Result(true, Storage()); + } else { + static Result err(E error) @safe @nogc nothrow { + Storage newStorage; + newStorage.error = error; + + return Result(true, newStorage); + } + } + + bool isOk() const @safe @nogc nothrow => !hasError; + + bool isErr() const @safe @nogc nothrow => hasError; + alias isErr this; // implicit conversion to bool + + static if (!is(T == void)) { + ref inout(T) unwrap() inout @safe @nogc nothrow return + in (isOk) do { return storage.value; } + + T unwrapOr(T fallback) @safe @nogc nothrow => isOk ? storage.value : fallback; + + T unwrapOrElse(D)(scope D fallback) + if (is(D R == return) && is(R : T) && is(D == __parameters)) + { return isOk ? storage.value : fallback(); } + } + + static if (!is(E == void)) { + ref inout(E) unwrapErr() inout @safe @nogc nothrow return + in (isErr) do { return storage.error; } + } +} From ddfb9f631fb882fcc9aaa6930f20581e5f420efb Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 12 Mar 2026 16:10:09 -0700 Subject: [PATCH 04/55] Refactor type generation [skip ci] --- crates/d/src/lib.rs | 1440 +++++++++++++++++++++++-------------- crates/d/src/wit_common.d | 140 +--- 2 files changed, 900 insertions(+), 680 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 4d6e8cead..e39a1efaa 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1,27 +1,38 @@ use anyhow::Result; use heck::*; use std::borrow::Cow; -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::mem::take; use std::path::PathBuf; -use wit_bindgen_core::{Files, Source, WorldGenerator, wit_parser::*}; +use wit_bindgen_core::{Direction, Types}; +use wit_bindgen_core::{Files, InterfaceGenerator, Source, WorldGenerator, wit_parser::*}; #[derive(Default)] struct D { - world_src: Source, + used_interfaces: HashSet<(WorldKey, InterfaceId)>, + + interface_imports: Vec, + interface_exports: Vec, + type_imports_src: Source, + function_imports_src: Source, + function_exports_src: Source, + opts: Opts, - cur_world_fqn: String, - interfaces: HashMap, + world_id: Option, + world_fqn: String, + interface_fqns: HashMap, cur_interface: Option, + + types: Types, } -#[derive(Default)] -struct InterfaceSource { - fqn: String, - src: Source, - imported: bool, - exported: bool, +#[derive(Default, Debug)] +struct InterfaceFQNSet { + import: Option, + export: Option, + common: Option, } #[derive(Default, Debug, Clone)] @@ -164,703 +175,1022 @@ fn escape_d_identifier(name: &str) -> &str { "while" => "while_", "with" => "with_", + // Symbols we define as part of the bindings we want to avoid creating conflicts with + "WitList" => "WitList_", + "WitString" => "WitString_", + "WitFlags" => "WitFlags_", + "Option" => "Option_", + "Result" => "Result_", + "bits" => "bits_", // part of WitFlags + s => s, } } fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { - let mut ns = String::new(); - let pkg = &resolve.packages[id]; - ns.push_str("wit."); - ns.push_str(escape_d_identifier(&pkg.name.namespace.to_snake_case())); - ns.push_str("."); - ns.push_str(escape_d_identifier(&pkg.name.name.to_snake_case())); - ns.push_str("."); let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { p.name.namespace == pkg.name.namespace && p.name.name == pkg.name.name && p.name.version != pkg.name.version }); - if pkg_has_multiple_versions { - if let Some(version) = &pkg.name.version { - let version = version - .to_string() - .replace('.', "_") - .replace('-', "_") - .replace('+', "_"); - ns.push_str(&version); - ns.push_str("."); + + format!( + "wit.{}.{}{}", + escape_d_identifier(&pkg.name.namespace.to_snake_case()), + escape_d_identifier(&pkg.name.name.to_snake_case()), + if pkg_has_multiple_versions { + if let Some(version) = &pkg.name.version { + let version = version + .to_string() + .replace('.', "_") + .replace('-', "_") + .replace('+', "_"); + format!(".{version}") + } else { + String::default() + } + } else { + String::default() } - } - ns + ) } fn get_interface_fqn( interface_id: &WorldKey, - cur_world_fqn: &String, + world_fqn: &str, resolve: &Resolve, - is_export: bool, + direction: Option, ) -> String { - let mut ns = String::new(); match interface_id { WorldKey::Name(name) => { - ns.push_str(cur_world_fqn); - if is_export { - ns.push_str(".exports") - } else { - ns.push_str(".imports") - } - ns.push_str("."); - ns.push_str(escape_d_identifier(&name.to_snake_case())) + format!( + "{}.{}.{}", + world_fqn, + match direction { + None => panic!( + "Inline interfaces can only generate `import` or `export` module variant" + ), + Some(Direction::Import) => "imports", + Some(Direction::Export) => "exports", + }, + escape_d_identifier(&name.to_snake_case()) + ) } WorldKey::Interface(id) => { let iface = &resolve.interfaces[*id]; - ns.push_str(&get_package_fqn(iface.package.unwrap(), resolve)); - ns.push_str(escape_d_identifier( - &iface.name.as_ref().unwrap().to_snake_case(), - )) + + format!( + "{}.{}.{}", + get_package_fqn(iface.package.unwrap(), resolve), + escape_d_identifier(&iface.name.as_ref().unwrap().to_snake_case()), + match direction { + None => "common", + Some(Direction::Import) => "imports", + Some(Direction::Export) => "exports", + }, + ) } } - ns } fn get_world_fqn(id: WorldId, resolve: &Resolve) -> String { - let mut ns = String::new(); - let world = &resolve.worlds[id]; - ns.push_str(&get_package_fqn(world.package.unwrap(), resolve)); - ns.push_str(escape_d_identifier(&world.name.to_snake_case())); - ns + format!( + "{}.{}", + get_package_fqn(world.package.unwrap(), resolve), + escape_d_identifier(&world.name.to_snake_case()) + ) } impl D { - fn get_type_fqn(&self, name: &str, owner: &TypeOwner) -> String { - match owner { - TypeOwner::None => String::from(name), - TypeOwner::Interface(id) => { - format!( - "{}.{}", - self.interfaces[id].fqn, - escape_d_identifier(&name.to_upper_camel_case()) - ) - } - TypeOwner::World(_) => format!( - "{}.{}", - self.cur_world_fqn, - escape_d_identifier(&name.to_upper_camel_case()) - ), + fn interface<'a>( + &'a mut self, + resolve: &'a Resolve, + direction: Option, + name: Option<&'a WorldKey>, + wasm_import_module: Option<&'a str>, + ) -> DInterfaceGenerator<'a> { + let mut sizes = SizeAlign::default(); + sizes.fill(resolve); + + DInterfaceGenerator { + src: Source::default(), + fqn: "", + r#gen: self, + resolve, + interface: None, + name: name, + sizes, + direction, + + wasm_import_module, } } - fn get_type_name(&self, name: &str, owner: &TypeOwner) -> String { - match &owner { - TypeOwner::Interface(id) => Some(id), - _ => None, + fn lookup_interface_fqn(&self, id: InterfaceId, direction: Option) -> Option<&str> { + let all_fqns = &self.interface_fqns[&id]; + match direction { + None => all_fqns.common.as_deref(), + Some(Direction::Import) => all_fqns.import.as_deref(), + Some(Direction::Export) => all_fqns.export.as_deref(), + } + } +} + +impl WorldGenerator for D { + fn uses_nominal_type_ids(&self) -> bool { + false + } + + fn preprocess(&mut self, resolve: &Resolve, world_id: WorldId) { + self.world_fqn = get_world_fqn(world_id, resolve); + self.world_id = Some(world_id); + self.types.analyze(resolve); + + let world = &resolve.worlds[world_id]; + + for (name, import) in world.imports.iter() { + match import { + WorldItem::Interface { id, .. } => { + let fqns = self.interface_fqns.entry(*id).or_insert_with(|| { + let mut result = InterfaceFQNSet::default(); + + match name { + WorldKey::Interface(_) => { + result.common = + Some(get_interface_fqn(&name, &self.world_fqn, resolve, None)); + } + WorldKey::Name(_) => { + // For anonymous/inline imports, the common types are in the same file as the imports + result.common = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Import), + )); + } + } + + result + }); + (*fqns).import = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Import), + )) + } + _ => {} + } + } + + for (name, export) in world.exports.iter() { + match export { + WorldItem::Interface { id, .. } => { + let fqns = self.interface_fqns.entry(*id).or_insert_with(|| { + let mut result = InterfaceFQNSet::default(); + + match name { + WorldKey::Interface(_) => { + result.common = + Some(get_interface_fqn(&name, &self.world_fqn, resolve, None)); + } + WorldKey::Name(_) => { + // For anonymous/inline exports, the common types are in the same file as the exports + result.common = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Export), + )); + } + } + + result + }); + (*fqns).export = Some(get_interface_fqn( + &name, + &self.world_fqn, + resolve, + Some(Direction::Export), + )) + } + _ => {} + } } - .zip(self.cur_interface.as_ref()) - .filter(|(id, cur_id)| id == cur_id) - .map_or_else( - || self.get_type_fqn(name, owner), - |_| escape_d_identifier(&name.to_upper_camel_case()).into(), - ) } - fn prepare_interface_bindings( - &self, + fn import_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, id: InterfaceId, - fqn: &String, - cur_world_fqn: &String, + files: &mut Files, + ) -> Result<()> { + self.used_interfaces.insert((name.clone(), id)); + + self.cur_interface = Some(id); + + let fqn = self.interface_fqns[&id].import.as_ref().unwrap().clone(); + + self.interface_imports.push(fqn.clone()); + + let wasm_import_module = resolve.name_world_key(name); + let mut r#gen = self.interface( + resolve, + Some(Direction::Import), + Some(name), + Some(&wasm_import_module), + ); + r#gen.fqn = &fqn; + r#gen.interface = Some(id); + r#gen.prologue(); + + if let WorldKey::Name(_) = name { + // We have an inline interface imported in a world. + // Emit the "common" types as well + + r#gen.direction = None; + r#gen.types(id); + r#gen.direction = Some(Direction::Import); + } + + r#gen.types(id); + + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); + + //self.interface_imports.push(interface_src.fqn.clone()); + //interface_src.src.push_str("\n// Function imports\n"); + //interface_src.src.append_src(&tmp_src); + + self.cur_interface = None; + Ok(()) + } + + fn import_types( + &mut self, resolve: &Resolve, - ) -> Source { - let mut src = Source::default(); - let interface = &resolve.interfaces[id]; + _world: WorldId, + types: &[(&str, TypeId)], + _files: &mut Files, + ) { + let mut r#gen = self.interface(resolve, Some(Direction::Import), None, Some("$root")); + for (name, id) in types.iter() { + r#gen.define_type(name, *id); + } - src.push_str(&format!( - "/++\n{}\n+/\n", - interface.docs.contents.as_deref().unwrap_or_default() - )); + let src = take(&mut r#gen.src); + self.type_imports_src.append_src(&src); + } - src.push_str(&format!("module {};\n\n", fqn)); + fn import_funcs( + &mut self, + _resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + let _name = WorldKey::Name("$root".to_string()); + //let wasm_import_module = resolve.name_world_key(&name); - src.push_str("import wit.common;\n\n"); + for (name, _func) in funcs { + self.function_imports_src + .push_str(&format!("// Import function - {name}\n")); + } + } - let mut deps = BTreeSet::new(); + fn export_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + files: &mut Files, + ) -> Result<()> { + self.used_interfaces.insert((name.clone(), id)); - for dep_id in resolve.interface_direct_deps(id) { - deps.insert(dep_id); + self.cur_interface = Some(id); + + let fqn = self.interface_fqns[&id].export.as_ref().unwrap().clone(); + + self.interface_exports.push(fqn.clone()); + + let wasm_import_module = resolve.name_world_key(name); + let mut r#gen = self.interface( + resolve, + Some(Direction::Export), + Some(name), + Some(&wasm_import_module), + ); + r#gen.interface = Some(id); + r#gen.prologue(); + + if let WorldKey::Name(_) = name { + // We have an inline interface exported in a world. + // Emit the "common" types as well + + r#gen.direction = None; + r#gen.types(id); + r#gen.direction = Some(Direction::Export); } - for dep_id in deps { - let wrapped_dep_id = WorldKey::Interface(dep_id); - src.push_str(&format!( - "static import {};\n", - get_interface_fqn(&wrapped_dep_id, cur_world_fqn, resolve, false) - )); + r#gen.types(id); + + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); + + self.cur_interface = None; + Ok(()) + } + + fn export_funcs( + &mut self, + _resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) -> Result<()> { + for (name, _func) in funcs { + self.function_exports_src + .push_str(&format!("// Export function: {name}\n")); } + Ok(()) + } + + fn finish(&mut self, resolve: &Resolve, world_id: WorldId, files: &mut Files) -> Result<()> { + for (name, id) in take(&mut self.used_interfaces) { + if let WorldKey::Interface(_) = name { + let fqn = self.interface_fqns[&id].common.as_ref().unwrap().clone(); - src.push_str("\n// Type defines\n"); + let wasm_import_module = resolve.name_world_key(&name); + let mut r#gen = + self.interface(resolve, None, Some(&name), Some(&wasm_import_module)); + r#gen.interface = Some(id); + r#gen.prologue(); + r#gen.types(id); - for (name, id) in &interface.types { - let type_src = self.generate_type_declaration(name, *id, resolve); - src.append_src(&type_src); + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + interface_filepath.add_extension("d"); + + files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); + } } - src + let mut world_src = Source::default(); + + let world = &resolve.worlds[world_id]; + + world_src.push_str(&format!( + "/++\n{}\n+/\n", + world.docs.contents.as_deref().unwrap_or_default() + )); + + world_src.push_str(&format!("module {};\n\n", self.world_fqn)); + world_src.push_str("import wit.common;\n\n"); + world_src.push_str("// Interface imports\n"); + world_src.push_str( + &self + .interface_imports + .iter() + .map(|fqn| format!("public import {fqn};")) + .collect::>() + .join("\n"), + ); + + world_src.push_str("\n\n// Type imports\n"); + world_src.append_src(&self.type_imports_src); + + world_src.push_str("\n// Function imports\n"); + world_src.append_src(&self.function_imports_src); + + world_src.push_str("\n// Interface exports\n"); + world_src.push_str( + &self + .interface_exports + .iter() + .map(|fqn| format!("public import {fqn};")) + .collect::>() + .join("\n"), + ); + + world_src.push_str("\n\nprivate alias AliasSeq(T...) = T;\n"); + world_src.push_str("template Exports(Impl...) {\n"); + world_src.push_str("// Interface exports\n"); + + world_src.push_str("alias InterfaceExports = AliasSeq!(\n"); + world_src.indent(1); + world_src.push_str( + &self + .interface_exports + .iter() + .map(|fqn| format!("{fqn}.Exports!Impl")) + .collect::>() + .join(",\n"), + ); + world_src.deindent(1); + world_src.push_str("\n);\n"); + + world_src.push_str("\n// Function exports\n"); + world_src.append_src(&self.function_exports_src); + world_src.push_str("}\n"); + + let mut world_filepath = PathBuf::from_iter(get_world_fqn(world_id, resolve).split(".")); + world_filepath.push("package.d"); + + files.push(world_filepath.to_str().unwrap(), world_src.as_bytes()); + + files.push("wit/common.d", include_bytes!("wit_common.d")); + Ok(()) } +} + +struct DInterfaceGenerator<'a> { + src: Source, + direction: Option, + r#gen: &'a mut D, + resolve: &'a Resolve, + interface: Option, + name: Option<&'a WorldKey>, + wasm_import_module: Option<&'a str>, + fqn: &'a str, + + sizes: SizeAlign, +} - fn generate_type_use(&self, r#type: &Type, resolve: &Resolve) -> Cow<'static, str> { - match r#type { +impl<'a> DInterfaceGenerator<'a> { + fn scoped_type_name(&self, id: TypeId, from_module_fqn: &str) -> String { + let ty = &self.resolve.types[id]; + + let owner_fqn = self.type_owner_fqn(&ty.owner).unwrap(); + + let upper_name = ty.name.as_ref().unwrap().to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + if from_module_fqn == owner_fqn { + escaped_name.into() + } else { + format!("{owner_fqn}.{escaped_name}") + } + } + fn type_name(&self, ty: &Type, from_module_fqn: &str) -> Cow<'static, str> { + match ty { Type::Bool => Cow::Borrowed("bool"), + Type::Char => Cow::Borrowed("dchar"), Type::U8 => Cow::Borrowed("ubyte"), - Type::U16 => Cow::Borrowed("ushort"), - Type::U32 => Cow::Borrowed("uint"), - Type::U64 => Cow::Borrowed("ulong"), Type::S8 => Cow::Borrowed("byte"), + Type::U16 => Cow::Borrowed("ushort"), Type::S16 => Cow::Borrowed("short"), + Type::U32 => Cow::Borrowed("uint"), Type::S32 => Cow::Borrowed("int"), + Type::U64 => Cow::Borrowed("ulong"), Type::S64 => Cow::Borrowed("long"), Type::F32 => Cow::Borrowed("float"), Type::F64 => Cow::Borrowed("double"), - Type::Char => Cow::Borrowed("dchar"), - Type::String => Cow::Borrowed("String"), - Type::ErrorContext => { - todo!("use of `error_context`!"); - } + Type::String => Cow::Borrowed("WitString"), Type::Id(id) => { - let typedef = &resolve.types[*id]; - match &typedef.owner { + let typedef = &self.resolve.types[*id]; + + match typedef.owner { TypeOwner::None => match &typedef.kind { - TypeDefKind::Handle(handle) => todo!("use of `TypeDefKind::Handle`"), - TypeDefKind::Tuple(tuple) => Cow::Owned(format!( + TypeDefKind::Record(_) => { + Cow::Owned(self.scoped_type_name(*id, from_module_fqn)) + } + TypeDefKind::Resource => { + Cow::Owned(self.scoped_type_name(*id, from_module_fqn)) + } + TypeDefKind::Handle(Handle::Own(_id)) => { + Cow::Borrowed("/* todo - type_name of `own` */") + } + TypeDefKind::Handle(Handle::Borrow(_id)) => { + Cow::Borrowed("/* todo - type_name of `borrow` */") + } + TypeDefKind::Tuple(t) => Cow::Owned(format!( "Tuple!({})", - tuple - .types + t.types .iter() - .map(|ty| self.generate_type_use(ty, resolve).into_owned()) + .map(|ty| self.type_name(ty, from_module_fqn).into_owned()) .collect::>() .join(", ") )), - TypeDefKind::Option(opt_type) => Cow::Owned(format!( - "Option!({})", - self.generate_type_use(opt_type, resolve) - )), - TypeDefKind::Result(result) => Cow::Owned(format!( + TypeDefKind::Option(o) => { + Cow::Owned(format!("Option!({})", self.type_name(o, from_module_fqn))) + } + TypeDefKind::Result(r) => Cow::Owned(format!( "Result!({}, {})", - match result.ok { - Some(ok_type) => self.generate_type_use(&ok_type, resolve), + match r.ok { + Some(ok_type) => self.type_name(&ok_type, from_module_fqn), None => Cow::Borrowed("void"), }, - match result.err { - Some(err_type) => self.generate_type_use(&err_type, resolve), + match r.err { + Some(err_type) => self.type_name(&err_type, from_module_fqn), None => Cow::Borrowed("void"), } )), - TypeDefKind::List(list_type) => Cow::Owned(format!( - "List!({})", - self.generate_type_use(list_type, resolve) + TypeDefKind::List(ty) => Cow::Owned(format!( + "WitList!({})", + self.type_name(&ty, from_module_fqn) )), - TypeDefKind::Map(_, _) => todo!("use of `TypeDefKind::Map`"), - TypeDefKind::FixedLengthList(list_type, length) => Cow::Owned(format!( - "{}[{length}]", - self.generate_type_use(list_type, resolve) - )), - TypeDefKind::Future(future_type) => todo!("use of `TypeDefKind::Future`"), - TypeDefKind::Stream(stream_type) => todo!("use of `TypeDefKind::Stream`"), - TypeDefKind::Type(target_type) => { - self.generate_type_use(target_type, resolve) + TypeDefKind::Future(_) => { + Cow::Borrowed("/* todo - type_name of `future` */") + } + TypeDefKind::Stream(_) => { + Cow::Borrowed("/* todo - type_name of `stream` */") } - TypeDefKind::Unknown => { - panic!("Trying to emit type use for `TypeDefKind::Unknown`?"); + TypeDefKind::FixedLengthList(ty, size) => { + Cow::Owned(format!("{}[{size}]", self.type_name(ty, from_module_fqn))) } + TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Unknown => unimplemented!(), unhandled => { panic!( - "Encountered unexpected use of ownerless typedef: {unhandled:?}." + "Encountered unexpected `type_name` invocation of ownerless typedef: {unhandled:?}." ); } }, - _ => Cow::Owned( - self.get_type_name(typedef.name.as_ref().unwrap(), &typedef.owner), - ), + _ => Cow::Owned(self.scoped_type_name(*id, from_module_fqn)), } } + Type::ErrorContext => todo!(), } } - fn generate_type_declaration(&self, name: &str, id: TypeId, resolve: &Resolve) -> Source { - let mut src = Source::default(); + fn type_owner_fqn(&self, owner: &TypeOwner) -> Option<&str> { + match &owner { + TypeOwner::None => None, + TypeOwner::Interface(interface_id) => match self.direction { + Some(_) => self + .r#gen + .lookup_interface_fqn(*interface_id, self.direction) + .or_else(|| self.r#gen.lookup_interface_fqn(*interface_id, None)), + None => self.r#gen.lookup_interface_fqn(*interface_id, None), + }, + TypeOwner::World(world_id) => { + if *world_id != self.r#gen.world_id.unwrap() { + panic!("Dealing with type from different world?"); + } - let typedef = &resolve.types[id]; + Some(&self.r#gen.world_fqn) + } + } + } - let upper_name = name.to_upper_camel_case(); - let escaped_name = escape_d_identifier(&upper_name); + fn prologue(&mut self) { + let id = self.interface.unwrap(); - src.push_str(&format!( - "\n/++\n{}\n+/\n", - typedef.docs.contents.as_deref().unwrap_or_default() - )); - match &typedef.kind { - TypeDefKind::Record(record) => { - src.push_str(&format!("struct {escaped_name} {{\n")); - - let mut is_first = true; - for field in &record.fields { - if is_first { - is_first = false; - } else { - src.push_str("\n"); - } + let fqn = self.r#gen.lookup_interface_fqn(id, self.direction).unwrap(); - src.push_str(&format!( - "/++\n{}\n+/\n", - field.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{} {};\n", - self.generate_type_use(&field.ty, resolve), - field.name.to_lower_camel_case() - )); - } + let interface = &self.resolve.interfaces[self.interface.unwrap()]; - src.push_str("}\n"); - } - TypeDefKind::Resource => { - //src.push_str(&format!("// TODO: def of resource - {name}")) - todo!("def of `TypeDefKind::Resource`"); - } - TypeDefKind::Handle(handle) => { - todo!("def of `TypeDefKind::Handle`"); - } - TypeDefKind::Flags(flags) => { - let storage_type = match flags.repr() { - FlagsRepr::U8 => "ubyte", - FlagsRepr::U16 => "ushort", - FlagsRepr::U32(1) => "uint", - FlagsRepr::U32(2) => "ulong", - repr => todo!("flags {repr:?}"), - }; + self.src.push_str(&format!( + "/++\n{}\n+/\n", + interface.docs.contents.as_deref().unwrap_or_default() + )); - src.push_str(&format!("enum {escaped_name}_ : {storage_type} {{\n")); - for (index, flag) in flags.flags.iter().enumerate() { - if index != 0 { - src.push_str("\n"); - } - src.push_str(&format!( - "/++\n{}\n+/\n", - flag.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{} = 1 << {index},\n", - escape_d_identifier(&flag.name.to_lower_camel_case()) - )); - } - src.push_str(&format!( - "}}\n/// ditto\nalias {escaped_name} = Flags!{escaped_name}_;" - )); - } - TypeDefKind::Tuple(tuple) => src.push_str(&format!( - "alias {escaped_name} = Tuple!({});", - tuple - .types - .iter() - .map(|ty| self.generate_type_use(ty, resolve).into_owned()) - .collect::>() - .join(", ") - )), - TypeDefKind::Variant(variant) => { - let storage_type = match variant.tag() { - Int::U8 => "ubyte", - Int::U16 => "ushort", - Int::U32 => "uint", - Int::U64 => "ulong", - }; + self.src.push_str(&format!("module {};\n\n", fqn)); - src.push_str(&format!("struct {escaped_name} {{\n")); - src.deindent(1); - src.push_str(&format!("@safe @nogc nothrow:\n")); - src.indent(1); + self.src.push_str("import wit.common;\n\n"); + if self.direction.is_some() + && let Some(WorldKey::Interface(_)) = self.name + { + self.src.push_str("public import "); + self.src + .push_str(self.r#gen.lookup_interface_fqn(id, None).unwrap()); + self.src.push_str(";\n\n"); + } - src.push_str(&format!("enum Tag : {storage_type} {{\n")); + let mut deps = BTreeSet::new(); - let mut is_first = true; - for case in &variant.cases { - if is_first { - is_first = false; - } else { - src.push_str("\n"); - } - src.push_str(&format!( - "/++\n{}\n+/\n", - case.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{},\n", - escape_d_identifier(&case.name.to_lower_camel_case()) - )); - } + for dep_id in self.resolve.interface_direct_deps(id) { + deps.insert(dep_id); + } - src.push_str("}\n"); - - src.deindent(1); - src.push_str(&format!("\nprivate:\n")); - src.indent(1); - - if variant.cases.iter().any(|case| case.ty.is_some()) { - src.push_str(&format!("union Storage {{\n")); - src.push_str("ubyte __zeroinit = 0;\n"); - for case in &variant.cases { - if let Some(ty) = &case.ty { - src.push_str(&format!( - "{} {};\n", - self.generate_type_use(ty, resolve), - escape_d_identifier(&case.name.to_lower_camel_case()) - )); - } + for dep_id in deps { + let common_fqn = self.r#gen.lookup_interface_fqn(dep_id, None).unwrap(); + let directional_fqn = self.r#gen.lookup_interface_fqn(dep_id, self.direction); + + if let Some(WorldKey::Interface(_)) = self.name { + self.src.push_str(&format!( + "static import {};\n", + match self.direction { + Some(_) => directional_fqn.unwrap_or(common_fqn), + None => common_fqn, } + )); + } else { + self.src + .push_str(&format!("static import {};\n", common_fqn)); - src.push_str("}\n\n"); - - src.push_str("Tag _tag;\n"); - src.push_str("Storage _storage;\n\n"); - } else { - src.push_str("Tag _tag;\n\n"); - } - - src.push_str("@disable this();\n"); - src.push_str("this(Tag tag, Storage storage = Storage.init) {\n"); - src.push_str("_tag = tag;\n"); - src.push_str("_storage = storage;\n"); - src.push_str("}\n"); + if let Some(fqn) = directional_fqn { + self.src.push_str(&format!("static import {};\n", fqn)); + }; + } + } + self.src.push_str("\n"); + } - src.deindent(1); - src.push_str(&format!("\npublic:\n")); - src.indent(1); + fn type_is_direction_sensitive(&self, id: TypeId) -> bool { + let type_info = &self.r#gen.types.get(id); - src.push_str("Tag tag() => _tag;\n"); + type_info.has_resource + } +} - for case in &variant.cases { - src.push_str(&format!( - "\n/++\n{}\n+/\n", - case.docs.contents.as_deref().unwrap_or_default() - )); - let upper_case_name = case.name.to_upper_camel_case(); - let escaped_upper_case_name = escape_d_identifier(&upper_case_name); - - let lower_case_name = case.name.to_lower_camel_case(); - let escaped_lower_case_name = escape_d_identifier(&lower_case_name); - - if let Some(ty) = &case.ty { - src.push_str(&format!( - "static {escaped_name} {escaped_lower_case_name}({} val) {{\n", - self.generate_type_use(ty, resolve) - )); - src.push_str("Storage storage;\n"); - src.push_str(&format!("storage.{escaped_lower_case_name} = val;\n")); - src.push_str(&format!( - "return {escaped_name}(Tag.{escaped_lower_case_name}, storage);\n" - )); - src.push_str("}\n"); - - src.push_str(&format!( - "/// ditto\n ref inout({}) get{escaped_upper_case_name}() inout return\n", - self.generate_type_use(ty, resolve) - )); - - src.push_str(&format!("in (is{escaped_upper_case_name}) ")); - src.push_str(&format!( - "do {{ return _storage.{escaped_lower_case_name}; }}\n" - )); - } else { - src.push_str(&format!( - "static {escaped_name} {escaped_lower_case_name}() => {escaped_name}(Tag.{escaped_lower_case_name});\n", - )); - } - src.push_str(&format!( - "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", - )); - } +impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { + fn resolve(&self) -> &'a Resolve { + self.resolve + } - src.push_str("}\n"); + // Override `types` to filter by `self.direction` + fn types(&mut self, iface: InterfaceId) { + let iface = &self.resolve().interfaces[iface]; + for (name, id) in iface.types.iter() { + if self.direction.is_some() == self.type_is_direction_sensitive(*id) { + self.define_type(name, *id); } - TypeDefKind::Enum(r#enum) => { - let storage_type = match r#enum.tag() { - Int::U8 => "ubyte", - Int::U16 => "ushort", - Int::U32 => "uint", - Int::U64 => "ulong", - }; + } + } - src.push_str(&format!("enum {escaped_name} : {storage_type} {{\n")); + fn type_record(&mut self, id: TypeId, name: &str, record: &Record, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - let mut is_first = true; - for case in &r#enum.cases { - if is_first { - is_first = false; - } else { - src.push_str("\n"); - } - src.push_str(&format!( - "/++\n{}\n+/\n", - case.docs.contents.as_deref().unwrap_or_default() - )); - src.push_str(&format!( - "{},\n", - escape_d_identifier(&case.name.to_lower_camel_case()) - )); - } + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner) + .unwrap() + .to_string(); - src.push_str(&format!("}}")); - } - TypeDefKind::Option(opt_type) => src.push_str(&format!( - "alias {escaped_name} = Option!({});", - self.generate_type_use(opt_type, resolve) - )), - TypeDefKind::Result(result) => src.push_str(&format!( - "alias {escaped_name} = Result!({}, {});", - match result.ok { - Some(ok_type) => self.generate_type_use(&ok_type, resolve), - None => Cow::Borrowed("void"), - }, - match result.err { - Some(err_type) => self.generate_type_use(&err_type, resolve), - None => Cow::Borrowed("void"), - } - )), - TypeDefKind::List(list_type) => src.push_str(&format!( - "alias {escaped_name} = List!({});", - self.generate_type_use(list_type, resolve) - )), - TypeDefKind::Map(_, _) => { - todo!("def of `TypeDefKind::Map`"); - } - TypeDefKind::FixedLengthList(list_type, length) => { - src.push_str(&format!( - "alias {escaped_name} = {}[{length}];", - self.generate_type_use(&list_type, resolve), - )); - } - TypeDefKind::Future(future_type) => { - todo!("def of `TypeDefKind::Future`"); - } - TypeDefKind::Stream(stream_type) => { - todo!("def of `TypeDefKind::Stream`"); - } - TypeDefKind::Type(target_type) => { - src.push_str(&format!( - "alias {escaped_name} = {};", - self.generate_type_use(&target_type, resolve), - )); - } - TypeDefKind::Unknown => { - panic!("Trying to emit type declaration for `TypeDefKind::Unknown`?"); + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!("struct {escaped_name} {{\n")); + + let mut is_first = true; + for field in &record.fields { + if is_first { + is_first = false; + } else { + self.src.push_str("\n"); } + + self.src.push_str(&format!( + "/++\n{}\n+/\n", + field.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "{} {};\n", + self.type_name(&field.ty, &owner_fqn), + field.name.to_lower_camel_case() + )); } - src.push_str("\n"); - src + + self.src.push_str("}\n"); } -} -impl WorldGenerator for D { - fn uses_nominal_type_ids(&self) -> bool { - false + fn type_resource(&mut self, _id: TypeId, name: &str, _docs: &Docs) { + self.src + .push_str(&format!("// TODO: def of `resource` - {name}\n")) + //todo!("def of `resource`") } - fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { - self.cur_world_fqn = get_world_fqn(world, resolve); + fn type_tuple(&mut self, id: TypeId, name: &str, tuple: &Tuple, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - let world = &resolve.worlds[world]; + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); - self.world_src.push_str(&format!( - "/++\n{}\n+/\n", - world.docs.contents.as_deref().unwrap_or_default() + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Tuple!({});", + tuple + .types + .iter() + .map(|ty| self.type_name(ty, owner_fqn).into_owned()) + .collect::>() + .join(", ") )); + } - self.world_src - .push_str(&format!("module {};\n\n", self.cur_world_fqn)); + fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - self.world_src.push_str("import wit.common;\n\n"); + let storage_type = match flags.repr() { + FlagsRepr::U8 => "ubyte", + FlagsRepr::U16 => "ushort", + FlagsRepr::U32(1) => "uint", + FlagsRepr::U32(2) => "ulong", + repr => todo!("flags {repr:?}"), + }; - self.world_src.push_str("// Interface imports\n"); - } + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!("struct {escaped_name} {{\n")); - fn import_interface( - &mut self, - resolve: &Resolve, - name: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - self.cur_interface = Some(id); - let interface_src = match self.interfaces.get_mut(&id) { - Some(src) => src, - None => { - eprintln!("Import {id:?}"); - let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, false); - - let mut result_init = InterfaceSource::default(); - result_init.fqn = new_fqn; - self.interfaces.insert(id, result_init); - - let new_src = self.prepare_interface_bindings( - id, - &self.interfaces.get(&id).unwrap().fqn, - &self.cur_world_fqn, - resolve, - ); - - let result = self.interfaces.get_mut(&id).unwrap(); - result.src = new_src; - result + self.src + .push_str(&format!("mixin WitFlags!{storage_type};\n\n")); + + for (index, flag) in flags.flags.iter().enumerate() { + if index != 0 { + self.src.push_str("\n"); } + self.src.push_str(&format!( + "/++\n{}\n+/\n", + flag.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "enum {} = {escaped_name}[{index}];\n", + escape_d_identifier(&flag.name.to_lower_camel_case()) + )); + } + self.src.push_str(&format!("}}\n")); + } + + fn type_variant(&mut self, id: TypeId, name: &str, variant: &Variant, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + let storage_type = match variant.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", }; - if interface_src.imported { - return Ok(()); + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner) + .unwrap() + .to_string(); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!("struct {escaped_name} {{\n")); + self.src.deindent(1); + self.src.push_str("@safe @nogc nothrow:\n"); + self.src.indent(1); + + self.src + .push_str(&format!("enum Tag : {storage_type} {{\n")); + + let mut is_first = true; + for case in &variant.cases { + if is_first { + is_first = false; + } else { + self.src.push_str("\n"); + } + self.src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); } - interface_src.imported = true; - self.world_src - .push_str(&format!("public import {};\n", &self.interfaces[&id].fqn)); + self.src.push_str("}\n"); - self.cur_interface = None; - Ok(()) - } + self.src.deindent(1); + self.src.push_str("\nprivate:\n"); + self.src.indent(1); - fn import_types( - &mut self, - resolve: &Resolve, - world: WorldId, - types: &[(&str, TypeId)], - _files: &mut Files, - ) { - self.world_src.push_str(&format!("\n// Type imports\n")); - for (name, id) in types { - let type_src = self.generate_type_declaration(name, *id, resolve); - self.world_src.append_src(&type_src); + if variant.cases.iter().any(|case| case.ty.is_some()) { + self.src.push_str("union Storage {\n"); + self.src.push_str("ubyte __zeroinit = 0;\n"); + for case in &variant.cases { + if let Some(ty) = &case.ty { + self.src.push_str(&format!( + "{} {};\n", + self.type_name(ty, &owner_fqn), + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + } + + self.src.push_str("}\n\n"); + + self.src.push_str("Tag _tag;\n"); + self.src.push_str("Storage _storage;\n\n"); + } else { + self.src.push_str("Tag _tag;\n\n"); } - } - fn import_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) { - self.world_src.push_str(&format!("\n// Function imports\n")); - for (name, func) in funcs { - self.world_src - .push_str(&format!("// Import function: {name}\n")); + self.src.push_str("@disable this();\n"); + self.src + .push_str("this(Tag tag, Storage storage = Storage.init) {\n"); + self.src.push_str("_tag = tag;\n"); + self.src.push_str("_storage = storage;\n"); + self.src.push_str("}\n"); + + self.src.deindent(1); + self.src.push_str("\npublic:\n"); + self.src.indent(1); + + self.src.push_str("Tag tag() => _tag;\n"); + + for case in &variant.cases { + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + let upper_case_name = case.name.to_upper_camel_case(); + let escaped_upper_case_name = escape_d_identifier(&upper_case_name); + + let lower_case_name = case.name.to_lower_camel_case(); + let escaped_lower_case_name = escape_d_identifier(&lower_case_name); + + if let Some(ty) = &case.ty { + self.src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}({} val) {{\n", + self.type_name(ty, &owner_fqn) + )); + self.src.push_str("Storage storage;\n"); + self.src + .push_str(&format!("storage.{escaped_lower_case_name} = val;\n")); + self.src.push_str(&format!( + "return {escaped_name}(Tag.{escaped_lower_case_name}, storage);\n" + )); + self.src.push_str("}\n"); + + self.src.push_str(&format!( + "/// ditto\n ref inout({}) get{escaped_upper_case_name}() inout return\n", + self.type_name(ty, &owner_fqn) + )); + + self.src + .push_str(&format!("in (is{escaped_upper_case_name}) ")); + self.src.push_str(&format!( + "do {{ return _storage.{escaped_lower_case_name}; }}\n" + )); + } else { + self.src.push_str(&format!( + "static {escaped_name} {escaped_lower_case_name}() => {escaped_name}(Tag.{escaped_lower_case_name});\n", + )); + } + self.src.push_str(&format!( + "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", + )); } + + self.src.push_str("}\n"); } - fn pre_export_interface(&mut self, resolve: &Resolve, files: &mut Files) -> Result<()> { - self.world_src.push_str("\n// Interface exports\n"); - self.world_src - .push_str("mixin template Exports(alias Impl) {\n"); + fn type_option(&mut self, id: TypeId, name: &str, payload: &Type, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - Ok(()) + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Option!({});", + self.type_name(payload, owner_fqn) + )); } - fn export_interface( - &mut self, - resolve: &Resolve, - name: &WorldKey, - id: InterfaceId, - _files: &mut Files, - ) -> Result<()> { - self.cur_interface = Some(id); - let interface = &resolve.interfaces[id]; - let interface_src = match self.interfaces.get_mut(&id) { - Some(src) => src, - None => { - eprintln!("Export {id:?}"); - let new_fqn = get_interface_fqn(name, &self.cur_world_fqn, resolve, true); - - let mut result_init = InterfaceSource::default(); - result_init.fqn = new_fqn; - self.interfaces.insert(id, result_init); - - let new_src = self.prepare_interface_bindings( - id, - &self.interfaces.get(&id).unwrap().fqn, - &self.cur_world_fqn, - resolve, - ); - - let result = self.interfaces.get_mut(&id).unwrap(); - result.src = new_src; - result + fn type_result(&mut self, id: TypeId, name: &str, result: &Result_, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Result!({}, {});", + match result.ok { + Some(ok_type) => self.type_name(&ok_type, owner_fqn), + None => Cow::Borrowed("void"), + }, + match result.err { + Some(err_type) => self.type_name(&err_type, owner_fqn), + None => Cow::Borrowed("void"), } + )); + } + + fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + let storage_type = match enum_.tag() { + Int::U8 => "ubyte", + Int::U16 => "ushort", + Int::U32 => "uint", + Int::U64 => "ulong", }; - if interface_src.exported { - return Ok(()); + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src + .push_str(&format!("enum {escaped_name} : {storage_type} {{\n")); + + let mut is_first = true; + for case in &enum_.cases { + if is_first { + is_first = false; + } else { + self.src.push_str("\n"); + } + self.src.push_str(&format!( + "/++\n{}\n+/\n", + case.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "{},\n", + escape_d_identifier(&case.name.to_lower_camel_case()) + )); } - interface_src.exported = true; - self.world_src.push_str(&format!( - "mixin imported!\"{}\".Exports!Impl;\n", - interface_src.fqn - )); + self.src.push_str("}"); + } - interface_src - .src - .push_str("\nmixin template Exports(alias Impl) {\n"); + fn type_alias(&mut self, id: TypeId, name: &str, alias_ty: &Type, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - interface_src - .src - .push_str(&format!("// Function exports\n")); - for (name, func) in &interface.functions { - interface_src - .src - .push_str(&format!("// Export function: {name}\n")); - } + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); - interface_src.src.push_str("}\n"); + let typename = self.type_name( + alias_ty, + self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(), + ); - self.cur_interface = None; - Ok(()) + self.src + .push_str(&format!("alias {escaped_name} = {typename};\n")); } - fn export_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) -> Result<()> { - self.world_src.push_str(&format!("\n// Function exports\n")); - for (name, func) in funcs { - self.world_src - .push_str(&format!("// Export function: {name}\n")); - } - Ok(()) + fn type_list(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = WitList!({});", + self.type_name(ty, owner_fqn) + )); } - fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { - // Close out interface exports - self.world_src.push_str("}\n"); + fn type_fixed_length_list( + &mut self, + id: TypeId, + name: &str, + ty: &Type, + size: u32, + docs: &Docs, + ) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); - let mut world_filepath = PathBuf::from_iter(get_world_fqn(id, resolve).split(".")); - world_filepath.push("package.d"); + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); - files.push( - world_filepath.to_str().unwrap(), - self.world_src.as_str().as_bytes(), - ); + let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = {}[{size}];", + self.type_name(ty, owner_fqn) + )); + } - files.push("wit/common.d", include_bytes!("wit_common.d")); + fn type_future(&mut self, _id: TypeId, name: &str, _ty: &Option, _docs: &Docs) { + todo!("def of `future` - {name}"); + } - for (_, interface_src) in &self.interfaces { - let mut interface_filepath = PathBuf::from_iter(interface_src.fqn.split(".")); - interface_filepath.add_extension("d"); + fn type_stream(&mut self, _id: TypeId, name: &str, _ty: &Option, _docs: &Docs) { + todo!("def of `stream` - {name}"); + } - files.push( - interface_filepath.to_str().unwrap(), - interface_src.src.as_bytes(), - ); - } - Ok(()) + fn type_builtin(&mut self, _id: TypeId, name: &str, _ty: &Type, _docs: &Docs) { + todo!("def of `builtin` - {name}"); } } diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index f5d3f67f5..755caeca3 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -1,7 +1,7 @@ module wit.common; /// Thin CABI compliant wrapper over `T[]` -struct List(T) { +struct WitList(T) { @safe @nogc pure nothrow: T* ptr; size_t length; @@ -25,7 +25,7 @@ struct List(T) { // except list in WIT is actually List!(dchar) // // We assume UTF-8 data (as D native strings are UTF-8) -alias String = List!(immutable char); +alias WitString = List!(char); // TODO: split this file up and give Tuple a full port of the Phobos version? /// adapted from Phobos std.typecons.Tuple @@ -35,142 +35,32 @@ struct Tuple(Types...) if (is(Types)) { alias expand this; } -/// adapted from Phobos std.bitmanip.BitFlags -struct Flags(Enum) if (is(Enum == enum)) { -@safe @nogc pure nothrow: - public alias E = Enum; - -private: - template allAreBaseEnum(T...) - { - static foreach (Ti; T) - { - static if (!is(typeof(allAreBaseEnum) == bool) && // not yet defined - !is(Ti : E)) - { - enum allAreBaseEnum = false; - } - } - static if (!is(typeof(allAreBaseEnum) == bool)) // if not yet defined - { - enum allAreBaseEnum = true; - } - } - - static if (is(E U == enum)) { - alias Base = U; - } else static assert(0); - - Base mValue; - -public: - this(E flag) - { - this = flag; - } - - this(T...)(T flags) - if (allAreBaseEnum!(T)) - { - this = flags; - } +mixin template WitFlags(T) if (__traits(isUnsigned, T)) { + private alias F = typeof(this); - bool opCast(B: bool)() const - { - return mValue != 0; - } + T bits; - Base opCast(B)() const - if (is(Base : B)) - { - return mValue; - } + @safe nothrow @nogc pure: - auto opUnary(string op)() const - if (op == "~") - { - return WitFlags(cast(E) cast(Base) ~mValue); - } + static typeof(this) opIndex(size_t i) + in(i < T.sizeof*8) => F(cast(T)(1 << i)); - auto ref opAssign(T...)(T flags) - if (allAreBaseEnum!(T)) - { - mValue = 0; - foreach (E flag; flags) - { - mValue |= flag; - } - return this; - } + auto opUnary(string op : "~")() const => F(~bits); - auto ref opAssign(E flag) + auto ref opOpAssign(string op)(F rhs) + if (op == "|" || op == "&" || op == "^") { - mValue = flag; + mixin("bits "~op~"= rhs.bits;"); return this; } - auto ref opOpAssign(string op: "|")(WitFlags flags) + auto opBinary(string op)(F flags) const + if (op == "|" || op == "&" || op == "^") { - mValue |= flags.mValue; - return this; - } - - auto ref opOpAssign(string op: "&")(WitFlags flags) - { - mValue &= flags.mValue; - return this; - } - - auto ref opOpAssign(string op: "|")(E flag) - { - mValue |= flag; - return this; - } - - auto ref opOpAssign(string op: "&")(E flag) - { - mValue &= flag; - return this; - } - - auto opBinary(string op)(WitFlags flags) const - if (op == "|" || op == "&") - { - WitFlags result = this; + F result = this; result.opOpAssign!op(flags); return result; } - - auto opBinary(string op)(E flag) const - if (op == "|" || op == "&") - { - WitFlags result = this; - result.opOpAssign!op(flag); - return result; - } - - auto opBinaryRight(string op)(E flag) const - if (op == "|" || op == "&") - { - return opBinary!op(flag); - } - - bool opDispatch(string name)() const - if (__traits(hasMember, E, name)) - { - enum e = __traits(getMember, E, name); - return (mValue & e) == e; - } - - void opDispatch(string name)(bool set) - if (__traits(hasMember, E, name)) - { - enum e = __traits(getMember, E, name); - if (set) - mValue |= e; - else - mValue &= ~e; - } } /// Based on Rust's Option From b0e48e25d45a431a7f7aac8b007bc05aa670d1e2 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sun, 15 Mar 2026 15:39:51 -0700 Subject: [PATCH 05/55] Resource import handles, WitVariant, and raw function imports. --- crates/d/src/lib.rs | 301 ++++++++++++++++++++++++++++---------- crates/d/src/wit_common.d | 124 ++++++++++++---- 2 files changed, 321 insertions(+), 104 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index e39a1efaa..bb719a054 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -4,8 +4,10 @@ use std::borrow::Cow; use std::collections::{BTreeSet, HashMap, HashSet}; use std::mem::take; use std::path::PathBuf; -use wit_bindgen_core::{Direction, Types}; -use wit_bindgen_core::{Files, InterfaceGenerator, Source, WorldGenerator, wit_parser::*}; +use wit_bindgen_core::{ + Direction, Files, InterfaceGenerator, Source, Types, WorldGenerator, abi::WasmType, + wit_parser::*, +}; #[derive(Default)] struct D { @@ -181,12 +183,26 @@ fn escape_d_identifier(name: &str) -> &str { "WitFlags" => "WitFlags_", "Option" => "Option_", "Result" => "Result_", - "bits" => "bits_", // part of WitFlags + "bits" => "bits_", // part of WitFlags + "borrow" => "borrow_", // part of the expansion of `resource` + "drop" => "drop_", // part of the expansion of `resource` s => s, } } +pub fn wasm_type(ty: WasmType) -> &'static str { + match ty { + WasmType::I32 => "uint", + WasmType::I64 => "ulong", + WasmType::F32 => "float", + WasmType::F64 => "double", + WasmType::Pointer => "void*", + WasmType::PointerOrI64 => "ulong", + WasmType::Length => "size_t", + } +} + fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { let pkg = &resolve.packages[id]; let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { @@ -407,6 +423,7 @@ impl WorldGenerator for D { r#gen.interface = Some(id); r#gen.prologue(); + r#gen.src.push_str("// Types"); if let WorldKey::Name(_) = name { // We have an inline interface imported in a world. // Emit the "common" types as well @@ -418,6 +435,16 @@ impl WorldGenerator for D { r#gen.types(id); + r#gen.src.push_str("\n// Functions\n"); + for (_name, func) in &resolve.interfaces[id].functions { + match func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { + r#gen.import_func(func); + } + _ => {} + } + } + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); interface_filepath.add_extension("d"); @@ -663,11 +690,11 @@ impl<'a> DInterfaceGenerator<'a> { TypeDefKind::Resource => { Cow::Owned(self.scoped_type_name(*id, from_module_fqn)) } - TypeDefKind::Handle(Handle::Own(_id)) => { - Cow::Borrowed("/* todo - type_name of `own` */") + TypeDefKind::Handle(Handle::Own(id)) => { + Cow::Owned(self.scoped_type_name(*id, from_module_fqn)) } TypeDefKind::Handle(Handle::Borrow(_id)) => { - Cow::Borrowed("/* todo - type_name of `borrow` */") + Cow::Owned(self.scoped_type_name(*id, from_module_fqn) + ".Borrow") } TypeDefKind::Tuple(t) => Cow::Owned(format!( "Tuple!({})", @@ -798,6 +825,76 @@ impl<'a> DInterfaceGenerator<'a> { type_info.has_resource } + + fn import_func(&mut self, func: &Function) { + match &func.kind { + FunctionKind::Freestanding => {} + FunctionKind::Method(_) => {} + kind => { + self.src + .push_str(&format!("// TODO: Import {kind:?} - {}\n", func.name)); + return; + } + } + + let sig = self + .resolve + .wasm_signature(abi::AbiVariant::GuestImport, func); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + func.docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!( + "@wasmImport!(\"{}\", \"{}\")\n", + self.wasm_import_module.unwrap(), + func.name + )); + // The mangle is not important, as long as it won't conflict with other symbols + // WebAssembly symbol identifiers are much more permissive than C (can be any UTF-8). + // Yet, LDC before 1.42 don't allow full use of this fact. We make some substitutions. + self.src.push_str(&format!( + "pragma(mangle, \"__wit_import_{}__{}\")\n", + self.wasm_import_module + .unwrap() + .replace("/", "__") + .replace("-", "_"), + func.name + .replace("-", "_") + .replace("[", ":") + .replace("]", ":") + )); + + let split_name = match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &func.name, + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => { + self.src.push_str("static "); + + func.name.split(".").skip(1).next().unwrap() + } + }; + + let lower_name = split_name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + self.src.push_str(&format!( + "/*private*/ extern(C) {} __import_{escaped_name}({});\n", + match sig.results.len() { + 0 => "void", + 1 => wasm_type(sig.results[0]), + _ => unimplemented!("multi-value return not supported"), + }, + sig.params + .iter() + .map(|param| wasm_type(*param)) + .collect::>() + .join(", ") + )); + } } impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { @@ -852,9 +949,99 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str("}\n"); } - fn type_resource(&mut self, _id: TypeId, name: &str, _docs: &Docs) { - self.src - .push_str(&format!("// TODO: def of `resource` - {name}\n")) + fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) { + let upper_name = name.to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + let ty = &self.resolve.types[id]; + + match self.direction { + None => panic!("Resources can only be generated for imports, or exports. Not common."), + Some(Direction::Import) => match ty.owner { + TypeOwner::Interface(owner_id) => { + if let Some(cur_interface) = self.interface + && cur_interface == owner_id + { + } else { + panic!("Emitting resource from `interface` outside that interface?"); + } + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + self.src.push_str(&format!( + "struct {escaped_name} {{ + package(wit) int __handle = 0; + + package(wit) this(int handle) {{ + __handle = handle; + }} + + @disable this(); + + // TODO: make RAII? disable copy for the own + + + auto borrow() => Borrow(__handle); + alias borrow this; + + " + )); + + for (_, func) in &self.resolve.interfaces[owner_id].functions { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(_) => false, + FunctionKind::Static(mid) => *mid == id, + FunctionKind::Constructor(mid) => *mid == id, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => false, + FunctionKind::AsyncStatic(_) => todo!(), + } { + self.import_func(func); + } + } + + self.src.push_str(&format!( + "struct Borrow {{ + package(wit) int __handle = 0; + + package(wit) this(int handle) {{ + __handle = handle; + }} + + @disable this(); + + " + )); + + for (_, func) in &self.resolve.interfaces[owner_id].functions { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(mid) => *mid == id, + FunctionKind::Static(_) => false, + FunctionKind::Constructor(_) => false, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => todo!(), + FunctionKind::AsyncStatic(_) => false, + } { + self.import_func(func); + } + } + + self.src.push_str("}\n"); + + self.src.push_str("}\n"); + } + TypeOwner::World(_) => todo!("resources in worlds"), + TypeOwner::None => { + panic!("Resource definition without owner?"); + } + }, + Some(Direction::Export) => todo!("export of resource"), + } //todo!("def of `resource`") } @@ -937,8 +1124,30 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { docs.contents.as_deref().unwrap_or_default() )); self.src.push_str(&format!("struct {escaped_name} {{\n")); + + self.src.push_str("mixin WitVariant!(\n"); + self.src.indent(1); + + for case in &variant.cases { + self.src.push_str(&format!( + "{}, // {}\n", + match &case.ty { + None => Cow::Borrowed("void"), + Some(ty) => self.type_name(ty, &owner_fqn), + }, + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + self.src.deindent(1); - self.src.push_str("@safe @nogc nothrow:\n"); + self.src.push_str(");\n"); + + self.src.deindent(1); + //self.src.push_str("@safe @nogc nothrow:\n"); + self.src.indent(1); + + self.src.deindent(1); + self.src.push_str("\npublic:\n"); self.src.indent(1); self.src @@ -963,42 +1172,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str("}\n"); - self.src.deindent(1); - self.src.push_str("\nprivate:\n"); - self.src.indent(1); - - if variant.cases.iter().any(|case| case.ty.is_some()) { - self.src.push_str("union Storage {\n"); - self.src.push_str("ubyte __zeroinit = 0;\n"); - for case in &variant.cases { - if let Some(ty) = &case.ty { - self.src.push_str(&format!( - "{} {};\n", - self.type_name(ty, &owner_fqn), - escape_d_identifier(&case.name.to_lower_camel_case()) - )); - } - } - - self.src.push_str("}\n\n"); - - self.src.push_str("Tag _tag;\n"); - self.src.push_str("Storage _storage;\n\n"); - } else { - self.src.push_str("Tag _tag;\n\n"); - } - - self.src.push_str("@disable this();\n"); - self.src - .push_str("this(Tag tag, Storage storage = Storage.init) {\n"); - self.src.push_str("_tag = tag;\n"); - self.src.push_str("_storage = storage;\n"); - self.src.push_str("}\n"); - - self.src.deindent(1); - self.src.push_str("\npublic:\n"); - self.src.indent(1); - self.src.push_str("Tag tag() => _tag;\n"); for case in &variant.cases { @@ -1012,39 +1185,19 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { let lower_case_name = case.name.to_lower_camel_case(); let escaped_lower_case_name = escape_d_identifier(&lower_case_name); - if let Some(ty) = &case.ty { - self.src.push_str(&format!( - "static {escaped_name} {escaped_lower_case_name}({} val) {{\n", - self.type_name(ty, &owner_fqn) - )); - self.src.push_str("Storage storage;\n"); - self.src - .push_str(&format!("storage.{escaped_lower_case_name} = val;\n")); - self.src.push_str(&format!( - "return {escaped_name}(Tag.{escaped_lower_case_name}, storage);\n" - )); - self.src.push_str("}\n"); - - self.src.push_str(&format!( - "/// ditto\n ref inout({}) get{escaped_upper_case_name}() inout return\n", - self.type_name(ty, &owner_fqn) - )); + self.src.push_str(&format!( + "alias {escaped_lower_case_name} = _create!(Tag.{escaped_lower_case_name});\n", + )); + self.src.push_str(&format!( + "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", + )); - self.src - .push_str(&format!("in (is{escaped_upper_case_name}) ")); - self.src.push_str(&format!( - "do {{ return _storage.{escaped_lower_case_name}; }}\n" - )); - } else { + if let Some(ty) = &case.ty { self.src.push_str(&format!( - "static {escaped_name} {escaped_lower_case_name}() => {escaped_name}(Tag.{escaped_lower_case_name});\n", + "///ditto\nalias get{escaped_upper_case_name} = _get!(Tag.{escaped_lower_case_name});\n", )); } - self.src.push_str(&format!( - "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", - )); } - self.src.push_str("}\n"); } diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 755caeca3..e254125ed 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -1,5 +1,16 @@ module wit.common; +import core.attribute : mustuse; +import ldc.attributes : llvmAttr; + +// from std.meta +package(wit) alias AliasSeq(T...) = T; + +alias wasmImport(string mod, string name) = AliasSeq!( + llvmAttr("wasm-import-module", mod), + llvmAttr("wasm-import-name", name) +); + /// Thin CABI compliant wrapper over `T[]` struct WitList(T) { @safe @nogc pure nothrow: @@ -20,12 +31,14 @@ struct WitList(T) { return (ptr && length) ? ptr[0..length] : null; } } +auto witList(T : U[], U)(T slice) => WitList!U(slice); + // WIT ABI for string matches List, // except list in WIT is actually List!(dchar) // // We assume UTF-8 data (as D native strings are UTF-8) -alias WitString = List!(char); +alias WitString = WitList!(char); // TODO: split this file up and give Tuple a full port of the Phobos version? /// adapted from Phobos std.typecons.Tuple @@ -63,16 +76,67 @@ mixin template WitFlags(T) if (__traits(isUnsigned, T)) { } } + +mixin template WitVariant(Types...) { +private: + static assert(is(typeof(this).Tag)); + static assert(is(Tag U == enum) && __traits(isIntegral, U)); + + static assert(__traits(allMembers, Tag).length == Types.length); + static foreach (i, M; __traits(allMembers, Tag)) { + static assert(i == __traits(getMember, Tag, M)); + } + + union Storage { + template ReplacedTypes() { + alias ReplacedTypes = AliasSeq!(); + + static foreach (T; Types) { + static if (is(T == void)) + ReplacedTypes = AliasSeq!(ReplacedTypes, void[0]); + else + ReplacedTypes = AliasSeq!(ReplacedTypes, T); + } + } + + ubyte __zeroinit = 0; + ReplacedTypes!() members; + } + + Tag _tag; + Storage _storage; + + + @disable this(); + + this(Tag tag, Storage storage = Storage.init) { + _tag = tag; + _storage = storage; + } + + + static auto _create(Tag tag)() if (is(Types[tag] == void)) { + return typeof(this)(tag); + } + static auto _create(Tag tag)(Types[tag] val) if (!is(Types[tag] == void)) { + Storage storage = Storage.init; + storage.tupleof[tag+1] = val; + return typeof(this)(tag, storage); + } + + ref auto _get(Tag tag)() inout return if (!is(Types[tag] == void)) + in (_tag == tag) do { return cast(inout)_storage.tupleof[tag+1]; } +} + /// Based on Rust's Option struct Option(T) { private: - bool present = false; - T value; + bool _present = false; + T _value; - @disable this(); this(bool present, T value = T.init) @safe @nogc nothrow { - this.present = present; - this.value = value; + _present = present; + _value = value; } public: static Option some(T value) @safe @nogc nothrow { @@ -83,25 +147,26 @@ public: return Option(false); } - bool isSome() const @safe @nogc nothrow => present; + bool isSome() const @safe @nogc nothrow => _present; alias isSome this; // implicit conversion to bool - bool isNone() const @safe @nogc nothrow => !present; + bool isNone() const @safe @nogc nothrow => !_present; ref inout(T) unwrap() inout @safe @nogc nothrow return - in (present) do { return value; } + in (_present) do { return _value; } - T unwrapOr(T fallback) @safe @nogc nothrow => present ? value : fallback; + T unwrapOr(T fallback) @safe @nogc nothrow => _present ? _value : fallback; T unwrapOrElse(D)(scope D fallback) if (is(D R == return) && is(R : T) && is(D == __parameters)) - { return present ? value : fallback(); } + { return _present ? _value : fallback(); } } /// Based on Rust's Result +@mustuse struct Result(T, E) { private: - bool hasError; + bool _hasError; union Storage { ubyte __zeroinit = 0; static if (!is(T == void)) { @@ -111,20 +176,19 @@ private: E error; } } - Storage storage; + Storage _storage; - @disable this(); this(bool hasError, Storage storage) @safe @nogc nothrow { - this.hasError = hasError; - this.storage = storage; + _hasError = hasError; + _storage = storage; } public: static if (is(T == void)) { - static Result ok() @safe @nogc nothrow => Result(false, Storage()); + static Result ok() @safe @nogc nothrow => Result(false, Storage.init); } else { - static Result ok(T value) @safe @nogc nothrow { - Storage newStorage; + static Result ok(T value) @trusted @nogc nothrow { + Storage newStorage = Storage.init; newStorage.value = value; return Result(false, newStorage); @@ -132,34 +196,34 @@ public: } static if (is(E == void)) { - static Result err() @safe @nogc nothrow => Result(true, Storage()); + static Result err() @safe @nogc nothrow => Result(true, Storage.init); } else { - static Result err(E error) @safe @nogc nothrow { - Storage newStorage; + static Result err(E error) @trusted @nogc nothrow { + Storage newStorage = Storage.init; newStorage.error = error; return Result(true, newStorage); } } - bool isOk() const @safe @nogc nothrow => !hasError; + bool isOk() const @safe @nogc nothrow => !_hasError; - bool isErr() const @safe @nogc nothrow => hasError; + bool isErr() const @safe @nogc nothrow => _hasError; alias isErr this; // implicit conversion to bool static if (!is(T == void)) { - ref inout(T) unwrap() inout @safe @nogc nothrow return - in (isOk) do { return storage.value; } + ref inout(T) unwrap() inout @trusted @nogc nothrow return + in (isOk) do { return _storage.value; } - T unwrapOr(T fallback) @safe @nogc nothrow => isOk ? storage.value : fallback; + T unwrapOr(T fallback) @safe @nogc nothrow => isOk ? _storage.value : fallback; T unwrapOrElse(D)(scope D fallback) if (is(D R == return) && is(R : T) && is(D == __parameters)) - { return isOk ? storage.value : fallback(); } + { return isOk ? _storage.value : fallback(); } } static if (!is(E == void)) { - ref inout(E) unwrapErr() inout @safe @nogc nothrow return - in (isErr) do { return storage.error; } + ref inout(E) unwrapErr() inout @trusted @nogc nothrow return + in (isErr) do { return _storage.error; } } } From 47ad1f88a45e200a33619cd79487d79ce44289e5 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 18 Mar 2026 00:54:30 -0700 Subject: [PATCH 06/55] Initial pass implementing of `FunctionBindgen` for imports. [skip-ci] --- crates/d/src/lib.rs | 949 ++++++++++++++++++++++++++++++++++++-- crates/d/src/wit_common.d | 18 +- 2 files changed, 914 insertions(+), 53 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index bb719a054..b0de7ddc8 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -2,13 +2,27 @@ use anyhow::Result; use heck::*; use std::borrow::Cow; use std::collections::{BTreeSet, HashMap, HashSet}; -use std::mem::take; +use std::mem::{replace, take}; use std::path::PathBuf; use wit_bindgen_core::{ - Direction, Files, InterfaceGenerator, Source, Types, WorldGenerator, abi::WasmType, + Direction, Files, InterfaceGenerator, Source, Types, WorldGenerator, + abi::{self, Bindgen, WasmType}, wit_parser::*, }; +type DType = String; +#[derive(Default, Debug)] +struct DSig { + const_member: bool, + static_member: bool, + result: DType, + arguments: Vec<(String, DType)>, + name: String, + //namespace: Vec, + implicit_self: bool, + post_return: bool, +} + #[derive(Default)] struct D { used_interfaces: HashSet<(WorldKey, InterfaceId)>, @@ -693,7 +707,7 @@ impl<'a> DInterfaceGenerator<'a> { TypeDefKind::Handle(Handle::Own(id)) => { Cow::Owned(self.scoped_type_name(*id, from_module_fqn)) } - TypeDefKind::Handle(Handle::Borrow(_id)) => { + TypeDefKind::Handle(Handle::Borrow(id)) => { Cow::Owned(self.scoped_type_name(*id, from_module_fqn) + ".Borrow") } TypeDefKind::Tuple(t) => Cow::Owned(format!( @@ -709,14 +723,8 @@ impl<'a> DInterfaceGenerator<'a> { } TypeDefKind::Result(r) => Cow::Owned(format!( "Result!({}, {})", - match r.ok { - Some(ok_type) => self.type_name(&ok_type, from_module_fqn), - None => Cow::Borrowed("void"), - }, - match r.err { - Some(err_type) => self.type_name(&err_type, from_module_fqn), - None => Cow::Borrowed("void"), - } + self.optional_type_name(r.ok.as_ref(), from_module_fqn), + self.optional_type_name(r.err.as_ref(), from_module_fqn), )), TypeDefKind::List(ty) => Cow::Owned(format!( "WitList!({})", @@ -746,6 +754,13 @@ impl<'a> DInterfaceGenerator<'a> { } } + fn optional_type_name(&self, ty: Option<&Type>, from_module_fqn: &str) -> Cow<'static, str> { + match ty { + Some(ty) => self.type_name(ty, from_module_fqn), + None => Cow::Borrowed("void"), + } + } + fn type_owner_fqn(&self, owner: &TypeOwner) -> Option<&str> { match &owner { TypeOwner::None => None, @@ -826,6 +841,67 @@ impl<'a> DInterfaceGenerator<'a> { type_info.has_resource } + fn get_d_signature(&mut self, func: &Function) -> DSig { + match &func.kind { + FunctionKind::Freestanding | FunctionKind::Method(_) => {} + + FunctionKind::AsyncFreestanding + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => { + todo!() + } + } + + let mut res = DSig::default(); + + let split_name = match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &func.name, + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => func.name.split(".").skip(1).next().unwrap(), + }; + + let lower_name = split_name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + res.name = escaped_name.into(); + + res.result + .push_str(&(self.optional_type_name(func.result.as_ref(), self.fqn))); + + for ( + i, + Param { + name, ty: param, .. + }, + ) in func.params.iter().enumerate() + { + if i == 0 && name == "self" { + match &func.kind { + FunctionKind::Method(_) => { + res.implicit_self = true; + continue; + } + _ => {} + } + } + + let lower_param_name = name.to_lower_camel_case(); + let escaped_param_name = escape_d_identifier(&lower_param_name); + + res.arguments.push(( + escaped_param_name.into(), + self.type_name(¶m, self.fqn).into(), + )); + } + + res + } + fn import_func(&mut self, func: &Function) { match &func.kind { FunctionKind::Freestanding => {} @@ -837,22 +913,68 @@ impl<'a> DInterfaceGenerator<'a> { } } - let sig = self + let wasm_sig = self .resolve .wasm_signature(abi::AbiVariant::GuestImport, func); + let d_sig = self.get_d_signature(func); + self.src.push_str(&format!( "\n/++\n{}\n+/\n", func.docs.contents.as_deref().unwrap_or_default() )); + + if d_sig.static_member { + self.src.push_str("static "); + } + self.src.push_str(&format!( + "{} {}({}) {{\n", + d_sig.result, + d_sig.name, + d_sig + .arguments + .iter() + .map(|(name, ty)| "in ".to_owned() + ty + " " + name) + .collect::>() + .join(", ") + )); + + let mut params = Vec::new(); + + if d_sig.implicit_self { + params.push("this"); + } + for (arg, ty) in &d_sig.arguments { + params.push(arg); + } + + let mut f = FunctionBindgen::new(self, ¶ms); + abi::call( + f.r#gen.resolve, + abi::AbiVariant::GuestImport, + abi::LiftLower::LowerArgsLiftResults, + func, + &mut f, + false, + ); + let ret_area_decl = f.emit_ret_area_if_needed(); + + let FunctionBindgen { src, .. } = f; + self.src.push_str(&ret_area_decl); + self.src.push_str(&src.to_string()); + + self.src.push_str("}\n"); + + self.src.push_str("/// ditto\n"); self.src.push_str(&format!( "@wasmImport!(\"{}\", \"{}\")\n", self.wasm_import_module.unwrap(), func.name )); + // The mangle is not important, as long as it won't conflict with other symbols // WebAssembly symbol identifiers are much more permissive than C (can be any UTF-8). - // Yet, LDC before 1.42 don't allow full use of this fact. We make some substitutions. + // Yet, LDC before 1.42 doesn't allow full use of this fact. We make some substitutions. self.src.push_str(&format!( "pragma(mangle, \"__wit_import_{}__{}\")\n", self.wasm_import_module @@ -865,30 +987,16 @@ impl<'a> DInterfaceGenerator<'a> { .replace("]", ":") )); - let split_name = match &func.kind { - FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &func.name, - FunctionKind::Method(_) - | FunctionKind::Static(_) - | FunctionKind::Constructor(_) - | FunctionKind::AsyncMethod(_) - | FunctionKind::AsyncStatic(_) => { - self.src.push_str("static "); - - func.name.split(".").skip(1).next().unwrap() - } - }; - - let lower_name = split_name.to_lower_camel_case(); - let escaped_name = escape_d_identifier(&lower_name); - self.src.push_str(&format!( - "/*private*/ extern(C) {} __import_{escaped_name}({});\n", - match sig.results.len() { + "static private extern(C) {} __import_{}({});\n", + match wasm_sig.results.len() { 0 => "void", - 1 => wasm_type(sig.results[0]), + 1 => wasm_type(wasm_sig.results[0]), _ => unimplemented!("multi-value return not supported"), }, - sig.params + d_sig.name, + wasm_sig + .params .iter() .map(|param| wasm_type(*param)) .collect::>() @@ -973,9 +1081,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "struct {escaped_name} {{ - package(wit) int __handle = 0; + package(wit) uint __handle = 0; - package(wit) this(int handle) {{ + package(wit) this(uint handle) {{ __handle = handle; }} @@ -1004,11 +1112,34 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } } + self.src + .push_str("void drop() {\n__import__drop(__handle);\n}\n"); + + self.src.push_str(&format!( + "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", + self.wasm_import_module.unwrap(), + name + )); + + // The mangle is not important, as long as it won't conflict with other symbols + // WebAssembly symbol identifiers are much more permissive than C (can be any UTF-8). + // Yet, LDC before 1.42 doesn't allow full use of this fact. We make some substitutions. + self.src.push_str(&format!( + "pragma(mangle, \"__wit_import_{}__:resource_drop:{}\")\n", + self.wasm_import_module + .unwrap() + .replace("/", "__") + .replace("-", "_"), + name.replace("-", "_") + )); + self.src + .push_str("static private extern(C) void __import__drop(uint);\n\n"); + self.src.push_str(&format!( "struct Borrow {{ - package(wit) int __handle = 0; + package(wit) uint __handle = 0; - package(wit) this(int handle) {{ + package(wit) this(uint handle) {{ __handle = handle; }} @@ -1131,10 +1262,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { for case in &variant.cases { self.src.push_str(&format!( "{}, // {}\n", - match &case.ty { - None => Cow::Borrowed("void"), - Some(ty) => self.type_name(ty, &owner_fqn), - }, + self.optional_type_name(case.ty.as_ref(), &owner_fqn), escape_d_identifier(&case.name.to_lower_camel_case()) )); } @@ -1172,7 +1300,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str("}\n"); - self.src.push_str("Tag tag() => _tag;\n"); + self.src.push_str("Tag tag() const => _tag;\n"); for case in &variant.cases { self.src.push_str(&format!( @@ -1229,14 +1357,8 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); self.src.push_str(&format!( "alias {escaped_name} = Result!({}, {});", - match result.ok { - Some(ok_type) => self.type_name(&ok_type, owner_fqn), - None => Cow::Borrowed("void"), - }, - match result.err { - Some(err_type) => self.type_name(&err_type, owner_fqn), - None => Cow::Borrowed("void"), - } + self.optional_type_name(result.ok.as_ref(), owner_fqn), + self.optional_type_name(result.err.as_ref(), owner_fqn), )); } @@ -1347,3 +1469,730 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { todo!("def of `builtin` - {name}"); } } + +struct FunctionBindgen<'a, 'b> { + r#gen: &'b mut DInterfaceGenerator<'a>, + params: &'b [&'b str], + tmp: usize, + src: Source, + block_storage: Vec, + /// intermediate calculations for contained objects + blocks: Vec<(String, Vec)>, + payloads: Vec, + return_pointer_area_size: ArchitectureSize, + return_pointer_area_align: Alignment, +} + +fn tempname(base: &str, idx: usize) -> String { + format!("{base}{idx}") +} + +impl<'a, 'b> FunctionBindgen<'a, 'b> { + fn new(r#gen: &'b mut DInterfaceGenerator<'a>, params: &'b [&'b str]) -> Self { + Self { + r#gen, + params, + tmp: 0, + src: Default::default(), + block_storage: Default::default(), + blocks: Default::default(), + payloads: Default::default(), + return_pointer_area_size: Default::default(), + return_pointer_area_align: Default::default(), + } + } + + fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + ret + } + + fn push_str(&mut self, s: &str) { + self.src.push_str(s); + } + + fn load( + &mut self, + ty: &str, + offset: ArchitectureSize, + operands: &[String], + results: &mut Vec, + ) { + results.push(format!( + "*(cast({}*)({} + {}))", + ty, + operands[0], + offset.format("size_t.sizeof") + )); + } + + fn load_ext( + &mut self, + ty: &str, + offset: ArchitectureSize, + operands: &[String], + results: &mut Vec, + ) { + self.load(ty, offset, operands, results); + let result = results.pop().unwrap(); + results.push(format!("cast(uint)({result})")); + } + + fn store(&mut self, ty: &str, offset: ArchitectureSize, operands: &[String]) { + self.push_str(&format!( + "*(cast({ty}*)({} + {})) = cast({ty})({});\n", + operands[1], + offset.format("size_t.sizeof"), + operands[0] + )); + } + + /// Emits a shared return area declaration if needed by this function. + /// + /// During code generation, `return_pointer()` may be called multiple times for: + /// - Indirect parameter storage (when too many/large params) + /// - Return value storage (when return type is too large) + /// + /// **Safety:** This is safe because return pointers are used sequentially: + /// 1. Parameter marshaling (before call) + /// 2. Function execution + /// 3. Return value unmarshaling (after call) + /// + /// The scratch space is reused but never accessed simultaneously. + fn emit_ret_area_if_needed(&self) -> String { + if !self.return_pointer_area_size.is_empty() { + format!( + "align({}) void[{}] _retArea = void;\n", + self.return_pointer_area_align.format("size_t.sizeof"), + self.return_pointer_area_size.format("size_t.sizeof") + ) + } else { + String::new() + } + } +} + +impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { + type Operand = String; + + fn emit( + &mut self, + _resolve: &Resolve, + inst: &wit_bindgen_core::abi::Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let mut top_as = |cvt: &str| { + results.push(format!("(cast({cvt})({}))", operands.pop().unwrap())); + }; + + match inst { + abi::Instruction::GetArg { nth } => { + if *nth == 0 && &self.params[0] == &"self" { + results.push("this".into()); + } else { + results.push(self.params[*nth].into()); + } + } + abi::Instruction::I32Const { val } => results.push(val.to_string()), + abi::Instruction::ConstZero { tys } => { + for _ in tys.iter() { + results.push("0".to_string()); + } + } + abi::Instruction::ListCanonLower { .. } | abi::Instruction::StringLower { .. } => { + results.push(format!("cast(void*)({}.ptr)", operands[0])); + results.push(format!("{}.length", operands[0])); + } + abi::Instruction::ListCanonLift { element, ty, .. } => { + let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + let elem_name = self.r#gen.type_name(element, self.r#gen.fqn); + let tmp = self.tmp(); + + let ptr = tempname("_ptr", tmp); + let len = tempname("_len", tmp); + + self.push_str(&format!( + "auto {ptr} = cast({elem_name}*)({}); + auto {len} = {}; + ", + operands[0], operands[1] + )); + + results.push(format!("{}({ptr}[0..{len}])", list_name)); + } + + abi::Instruction::IterElem { .. } => results.push("_elem".into()), + abi::Instruction::IterBasePointer => results.push("_base".into()), + abi::Instruction::ListLower { element, .. } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + + let size = self.r#gen.sizes.size(element); + let size_str = size.format("size_t.sizeof"); + + let list = tempname("_list", tmp); + let list_src = tempname("_listSrc", tmp); + + self.push_str(&format!( + "auto {list_src} = {}; + auto {list} = wit.common.malloc({list_src}.length * ({size_str})); + scope(exit) {{ wit.common.free({list}); }}\n", + operands[0] + )); + + self.push_str(&format!("foreach (i, const ref _elem; {list_src}) {{\n")); + self.push_str(&format!("auto _base = {list} + i * {size_str};\n")); + self.push_str(&body.0); + //self.push_str(&format!("_targetElem = {};", body.1[0])); + self.push_str("\n}\n"); + + results.push(format!("{list}")); + results.push(format!("{}.length", operands[0])); + } + abi::Instruction::ListLift { ty, element, .. } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.r#gen.sizes.size(element); + let size_str = size.format("size_t.sizeof"); + let elem_type_name = self.r#gen.type_name(element, self.r#gen.fqn); + + let list = tempname("_list", tmp); + let list_len = tempname("_listLen", tmp); + let list_src = tempname("_listSrcPtr", tmp); + self.push_str(&format!("auto {list_src} = {};\n", operands[0])); + self.push_str(&format!("auto {list_len} = {};\n", operands[1])); + self.push_str(&format!( + "auto {list} = wit.common.mallocSlice!({elem_type_name})({list_len});\n", + )); + + self.push_str(&format!("foreach (i, ref _elem; {list}) {{\n",)); + self.push_str(&format!( + "const auto _base = {list_src} + i * {size_str};\n" + )); + self.push_str(&body.0); + self.push_str(&format!("_elem = {};", body.1[0])); + self.push_str("\n}\n"); + + let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + results.push(format!("{list_name}({list})")); + } + abi::Instruction::RecordLower { record, .. } => { + for field in record.fields.iter() { + let lower_name = field.name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + results.push(format!("{}.{escaped_name}", operands[0])); + } + } + abi::Instruction::RecordLift { ty, record, .. } => { + let name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + + let mut tmpvar = tempname("_record", self.tmp()); + + self.push_str(&format!("{name} {tmpvar} = {{\n")); + for (field, op) in record.fields.iter().zip(operands.iter()) { + let lower_name = field.name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + self.push_str(&format!("{escaped_name}: {op},\n")); + } + self.push_str("};\n"); + + results.push(tmpvar); + } + + abi::Instruction::TupleLower { tuple, .. } => { + for i in 0..tuple.types.len() { + results.push(format!("{}[{i}]", &operands[0])); + } + } + + abi::Instruction::TupleLift { tuple, ty, .. } => { + let name = tempname("_tuple", self.tmp()); + self.push_str(&format!( + "auto {name} = {}(\n", + self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn), + )); + self.src.indent(1); + for op in operands.iter() { + self.push_str(op); + self.push_str(",\n"); + } + self.src.deindent(1); + self.push_str(");\n"); + results.push(name); + } + + abi::Instruction::StringLift { .. } => { + let tmp = self.tmp(); + + let ptr = tempname("_ptr", tmp); + let len = tempname("_len", tmp); + + self.push_str(&format!( + "auto {ptr} = cast(char*)({}); + auto {len} = {}; + ", + operands[0], operands[1] + )); + + results.push(format!("WitString({ptr}[0..{len}])")); + } + + abi::Instruction::VariantPayloadName => { + let name = tempname("_payload", self.tmp()); + results.push(name.clone()); + self.payloads.push(name); + } + abi::Instruction::VariantLower { + ty, + variant, + results: result_types, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let payloads = self + .payloads + .drain(self.payloads.len() - variant.cases.len()..) + .collect::>(); + + let mut variant_results = Vec::with_capacity(result_types.len()); + for res_ty in result_types.iter() { + let name = tempname("_variantPart", self.tmp()); + results.push(name.clone()); + self.src + .push_str(&format!("{} {name} = void;\n", wasm_type(*res_ty))); + variant_results.push(name); + } + + let ty_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + + self.push_str(&format!( + "final switch ({}.tag) with ({ty_name}.Tag) {{\n", + operands[0] + )); + + for (i, ((case, (block, block_results)), payload)) in + variant.cases.iter().zip(blocks).zip(payloads).enumerate() + { + let lower_name = case.name.to_lower_camel_case(); + let lower_escaped_name = escape_d_identifier(&lower_name); + + let uppper_name = case.name.to_upper_camel_case(); + let upper_escaped_name = escape_d_identifier(&uppper_name); + + self.push_str(&format!("case {lower_escaped_name}: {{\n")); + if let Some(ty) = case.ty.as_ref() { + let ty_name = self.r#gen.type_name(ty, self.r#gen.fqn); + self.push_str(&format!( + "const ref {ty_name} {payload} = {}.get{upper_escaped_name}();\n", + operands[0], + )); + } + self.src.push_str(&block); + + for (name, result) in variant_results.iter().zip(&block_results) { + self.push_str(&format!("{name} = {result};\n")); + } + self.src.push_str("break;\n}\n"); + } + + self.src.push_str("}\n"); + } + abi::Instruction::VariantLift { variant, ty, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + + let ty = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + + let tmp = self.tmp(); + let result = tempname("_variant", tmp); + let tag = tempname("_tag", tmp); + + self.push_str(&format!("{ty} {result} = void;\n")); + self.push_str(&format!("auto {tag} = {};\n", operands[0])); + self.push_str(&format!( + "final switch (cast({ty}.Tag){tag}) with ({ty}.Tag) {{\n" + )); + for (i, (case, (block, block_results))) in + variant.cases.iter().zip(blocks).enumerate() + { + let lower_name = case.name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + let payload = tempname("_payload", self.tmp()); + + self.push_str(&format!("case {escaped_name}: {{\n")); + self.src.push_str(&block); + assert!(block_results.len() == (case.ty.is_some() as usize)); + + let val = if let Some(_) = case.ty.as_ref() { + self.push_str(&format!("auto {payload} = {};\n", block_results[0])); + &payload + } else { + "" + }; + self.push_str(&format!("{result} = {ty}.{escaped_name}({val});\n")); + self.src.push_str("break;\n}\n"); + } + self.src.push_str("}\n"); + results.push(result); + } + abi::Instruction::OptionLower { + results: result_types, + .. + } => { + let (mut some, some_results) = self.blocks.pop().unwrap(); + let (mut none, none_results) = self.blocks.pop().unwrap(); + let some_payload = self.payloads.pop().unwrap(); + let _none_payload = self.payloads.pop().unwrap(); + + for (i, ty) in result_types.iter().enumerate() { + let name = tempname("_option", self.tmp()); + results.push(name.clone()); + self.push_str(&format!("{} {name} = void;\n", wasm_type(*ty))); + let some_result = &some_results[i]; + some.push_str(&format!("{name} = {some_result};\n")); + let none_result = &none_results[i]; + none.push_str(&format!("{name} = {none_result};\n")); + } + + let bind_some = format!("ref {some_payload} = {}.unwrap();", operands[0]); + + self.push_str(&format!( + "\ + if ({}.isSome) {{ + {bind_some} + {some}}} else {{ + {none}}} + ", + operands[0] + )); + } + abi::Instruction::OptionLift { ty, .. } => { + let (some, some_results) = self.blocks.pop().unwrap(); + let (_none, none_results) = self.blocks.pop().unwrap(); + assert!(none_results.is_empty()); + assert!(some_results.len() == 1); + + let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + let op0 = &operands[0]; + + let tmp = self.tmp(); + let resultname = tempname("_option", tmp); + let is_some = tempname("_isSome", tmp); + let some_value = &some_results[0]; + self.push_str(&format!( + "{type_name} {resultname} = void; + bool {is_some} = ({op0}) != 0; + if ({is_some}) {{ + {some} + {resultname} = {type_name}.some({some_value}); + }} else {{ + {resultname} = {type_name}.none; + }} + " + )); + results.push(format!("{resultname}")); + } + + abi::Instruction::ResultLower { + results: result_types, + result, + .. + } => { + let (mut err, err_results) = self.blocks.pop().unwrap(); + let (mut ok, ok_results) = self.blocks.pop().unwrap(); + let err_payload = self.payloads.pop().unwrap(); + let ok_payload = self.payloads.pop().unwrap(); + + for (i, ty) in result_types.iter().enumerate() { + let tmp = self.tmp(); + let name = tempname("_resultPart", tmp); + results.push(name.clone()); + self.src.push_str(wasm_type(*ty)); + self.src.push_str(" "); + self.src.push_str(&name); + self.src.push_str(";\n"); + let ok_result = &ok_results[i]; + ok.push_str(&format!("{name} = {ok_result};\n")); + let err_result = &err_results[i]; + err.push_str(&format!("{name} = {err_result};\n")); + } + + let op0 = &operands[0]; + let bind_ok = if let Some(_ok) = result.ok.as_ref() { + format!("ref {ok_payload} = {op0}.unwrap();") + } else { + String::new() + }; + let bind_err = if let Some(_err) = result.err.as_ref() { + format!("ref {err_payload} = {op0}.unwrapErr();") + } else { + String::new() + }; + + self.push_str(&format!( + "\ + if ({op0}.isErr) {{ + {bind_err} + {err}}} else {{ + {bind_ok} + {ok}}} + " + )); + } + + abi::Instruction::ResultLift { result, ty, .. } => { + let (err, err_results) = self.blocks.pop().unwrap(); + assert!(err_results.len() == (result.err.is_some() as usize)); + let (ok, ok_results) = self.blocks.pop().unwrap(); + assert!(ok_results.len() == (result.ok.is_some() as usize)); + + let full_type = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + let op0 = &operands[0]; + + let tmp = self.tmp(); + let resultname = tempname("_result", tmp); + let is_err = tempname("_isErr", tmp); + + let ok_value = if result.ok.is_some() { + &ok_results[0] + } else { + "" + }; + + let err_value = if result.err.is_some() { + &err_results[0] + } else { + "" + }; + + self.push_str(&format!( + "{full_type} {resultname} = void; + bool {is_err} = ({op0}) != 0; + if ({is_err}) {{ + {err} + {resultname} = {full_type}.err({err_value}); + }} else {{ + {ok} + {resultname} = {full_type}.ok({ok_value}); + }}\n" + )); + results.push(resultname); + } + + abi::Instruction::EnumLower { .. } => { + results.push(format!("cast(uint)({})", operands[0])) + } + abi::Instruction::EnumLift { ty, .. } => { + let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + results.push(format!("cast({})({})", type_name, operands.pop().unwrap())) + } + + abi::Instruction::FlagsLower { flags, .. } => match flags.repr() { + FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => { + results.push(format!("cast(uint)({}.bits)", operands.pop().unwrap())); + } + FlagsRepr::U32(2) => { + let tempname = tempname("_flags", self.tmp()); + + self.push_str(&format!("auto {tempname} = {};", operands[0])); + results.push(format!("cast(uint)({tempname}.bits & 0xffffffff)")); + results.push(format!("cast(uint)(({tempname}.bits >> 32) & 0xffffffff)")); + } + _ => todo!(), + }, + abi::Instruction::FlagsLift { flags, ty, .. } => { + let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + + match flags.repr() { + FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => { + results.push(format!("{type_name}({})", operands.pop().unwrap())); + } + FlagsRepr::U32(2) => { + results.push(format!( + "({type_name}({}) | {type_name}({} << 32))", + operands[0], operands[1] + )); + } + _ => todo!(), + } + } + + abi::Instruction::HandleLower { .. } => { + let op = &operands[0]; + results.push(format!("{op}.__handle")) + } + + abi::Instruction::HandleLift { ty, .. } => { + let name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + results.push(format!("{name}({})", operands[0])); + } + + abi::Instruction::CallWasm { name, sig } => { + let split_name = if name.contains('.') { + name.split(".").skip(1).next().unwrap() + } else { + name + }; + + let lower_name = split_name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + if !sig.results.is_empty() { + self.src.push_str("auto _ret = "); + results.push("_ret".to_string()); + } + self.push_str(&format!( + "__import_{escaped_name}({});\n", + operands + .iter() + .map(|op| { op.clone() }) + .collect::>() + .join(", ") + )); + } + abi::Instruction::Return { amt, func } => match amt { + 0 => {} + _ => { + assert!(*amt == operands.len()); + + if *amt == 1 { + self.push_str("return "); + self.src.push_str(&operands[0]); + self.push_str(";\n"); + } else { + todo!(); + } + } + }, + abi::Instruction::I32Load { offset } => self.load("uint", *offset, operands, results), + abi::Instruction::I64Load { offset } => self.load("ulong", *offset, operands, results), + abi::Instruction::F32Load { offset } => self.load("float", *offset, operands, results), + abi::Instruction::F64Load { offset } => self.load("double", *offset, operands, results), + abi::Instruction::PointerLoad { offset } => { + self.load("void*", *offset, operands, results) + } + abi::Instruction::LengthLoad { offset } => { + self.load("size_t", *offset, operands, results) + } + abi::Instruction::I32Store { offset } => self.store("uint", *offset, operands), + abi::Instruction::I64Store { offset } => self.store("ulong", *offset, operands), + abi::Instruction::F32Store { offset } => self.store("float", *offset, operands), + abi::Instruction::F64Store { offset } => self.store("double", *offset, operands), + abi::Instruction::I32Store8 { offset } => self.store("ubyte", *offset, operands), + abi::Instruction::I32Store16 { offset } => self.store("ushort", *offset, operands), + abi::Instruction::PointerStore { offset } => self.store("void*", *offset, operands), + abi::Instruction::LengthStore { offset } => self.store("size_t", *offset, operands), + abi::Instruction::I32Load8U { offset } => { + self.load_ext("ubyte", *offset, operands, results) + } + abi::Instruction::I32Load8S { offset } => { + self.load_ext("byte", *offset, operands, results) + } + abi::Instruction::I32Load16U { offset } => { + self.load_ext("ushort", *offset, operands, results) + } + abi::Instruction::I32Load16S { offset } => { + self.load_ext("short", *offset, operands, results) + } + abi::Instruction::I32FromChar + | abi::Instruction::I32FromBool + | abi::Instruction::I32FromU8 + | abi::Instruction::I32FromS8 + | abi::Instruction::I32FromU16 + | abi::Instruction::I32FromS16 + | abi::Instruction::I32FromU32 + | abi::Instruction::I32FromS32 => top_as("uint"), + abi::Instruction::I64FromU64 | abi::Instruction::I64FromS64 => top_as("ulong"), + abi::Instruction::F32FromCoreF32 => top_as("float"), + abi::Instruction::F64FromCoreF64 => top_as("double"), + abi::Instruction::S8FromI32 => top_as("byte"), + abi::Instruction::U8FromI32 => top_as("ubyte"), + abi::Instruction::S16FromI32 => top_as("short"), + abi::Instruction::U16FromI32 => top_as("ushort"), + abi::Instruction::S32FromI32 => top_as("int"), + abi::Instruction::U32FromI32 => top_as("uint"), + abi::Instruction::S64FromI64 => top_as("long"), + abi::Instruction::U64FromI64 => top_as("ulong"), + abi::Instruction::CharFromI32 => top_as("dchar"), + abi::Instruction::CoreF32FromF32 => top_as("float"), + abi::Instruction::CoreF64FromF64 => top_as("double"), + abi::Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])), + + abi::Instruction::Flush { amt } => { + for op in operands.iter().take(*amt) { + let result = tempname("_flush", self.tmp()); + self.push_str(&format!("auto {result} = {};\n", op)); + results.push(result); + } + } + unk => todo!("emit instruction: {unk:?}"), + } + } + + fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> Self::Operand { + // Track maximum return area requirements + self.return_pointer_area_size = self.return_pointer_area_size.max(size); + self.return_pointer_area_align = self.return_pointer_area_align.max(align); + + "_retArea.ptr".into() + } + + fn push_block(&mut self) { + let prev = take(&mut self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = replace(&mut self.src, to_restore); + self.blocks.push((src.into(), take(operands))); + } + + fn sizes(&self) -> &SizeAlign { + &self.r#gen.sizes + } + + fn is_list_canonical(&self, _resolve: &Resolve, ty: &Type) -> bool { + self.r#gen.resolve.all_bits_valid(ty) + } +} + +/// This describes the common ABI function referenced or implemented, the C++ side might correspond to a different type +enum SpecialMethod { + None, + ResourceDrop, // ([export]) [resource-drop] + ResourceNew, // [export][resource-new] + ResourceRep, // [export][resource-rep] + Dtor, // [dtor] (guest export only) + Allocate, // internal: allocate new object (called from generated code) +} + +fn is_special_method(func: &Function) -> SpecialMethod { + if matches!(func.kind, FunctionKind::Static(_)) { + if func.name.starts_with("[resource-drop]") { + SpecialMethod::ResourceDrop + } else if func.name.starts_with("[resource-new]") { + SpecialMethod::ResourceNew + } else if func.name.starts_with("[resource-rep]") { + SpecialMethod::ResourceRep + } else if func.name.starts_with("[dtor]") { + SpecialMethod::Dtor + } else if func.name == "$alloc" { + SpecialMethod::Allocate + } else { + SpecialMethod::None + } + } else { + SpecialMethod::None + } +} diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index e254125ed..cc8ea75d8 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -3,6 +3,18 @@ module wit.common; import core.attribute : mustuse; import ldc.attributes : llvmAttr; +package(wit) extern(C) { +void* malloc(size_t size); +void free(void* ptr); +} + +package(wit) auto mallocSlice(T)(size_t count) { + auto ptr = malloc(count*T.sizeof); + if (ptr is null) return null; + + return (cast(T*)ptr)[0..count]; +} + // from std.meta package(wit) alias AliasSeq(T...) = T; @@ -152,10 +164,10 @@ public: bool isNone() const @safe @nogc nothrow => !_present; - ref inout(T) unwrap() inout @safe @nogc nothrow return + ref inout(T) unwrap() inout @trusted @nogc nothrow return in (_present) do { return _value; } - T unwrapOr(T fallback) @safe @nogc nothrow => _present ? _value : fallback; + T unwrapOr(T fallback) @trusted @nogc nothrow => _present ? _value : fallback; T unwrapOrElse(D)(scope D fallback) if (is(D R == return) && is(R : T) && is(D == __parameters)) @@ -215,7 +227,7 @@ public: ref inout(T) unwrap() inout @trusted @nogc nothrow return in (isOk) do { return _storage.value; } - T unwrapOr(T fallback) @safe @nogc nothrow => isOk ? _storage.value : fallback; + T unwrapOr(T fallback) @trusted @nogc nothrow => isOk ? _storage.value : fallback; T unwrapOrElse(D)(scope D fallback) if (is(D R == return) && is(R : T) && is(D == __parameters)) From 18ec4bf1c21443d776a509c9861a9376816e454f Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 19 Mar 2026 20:41:11 -0700 Subject: [PATCH 07/55] Basic export support. --- crates/d/src/lib.rs | 314 +++++++++++++++++++++++++++++++++++++- crates/d/src/wit_common.d | 75 +++++++-- 2 files changed, 371 insertions(+), 18 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index b0de7ddc8..749de5562 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -540,6 +540,63 @@ impl WorldGenerator for D { r#gen.types(id); + r#gen + .src + .push_str("\npackage(wit) template Exports(Impl...) {\n"); + + for (_name, func) in &resolve.interfaces[id].functions { + match func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { + r#gen.export_func(func); + } + _ => {} + } + } + + for (type_name, type_id) in &resolve.interfaces[id].types { + let ty = &resolve.types[*type_id]; + + match &ty.kind { + TypeDefKind::Resource => { + let upper_name = ty.name.as_ref().unwrap().to_upper_camel_case(); + let escaped_name = escape_d_identifier(&upper_name); + + r#gen.src.push_str(&format!( + "\n/++\n{}\n+/\n", + ty.docs.contents.as_deref().unwrap_or_default() + )); + + r#gen + .src + .push_str(&format!("/// ditto\nstruct {escaped_name}_Wrappers {{\n")); + + r#gen.src.push_str(&format!( + "alias _Resource_Impl = findWitExportResource!(\"{}\", \"{}\", Impl);\n", + wasm_import_module, type_name + )); + + for (_, func) in &resolve.interfaces[id].functions { + match func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {} + FunctionKind::Method(owner) + | FunctionKind::AsyncMethod(owner) + | FunctionKind::Constructor(owner) + | FunctionKind::Static(owner) + | FunctionKind::AsyncStatic(owner) => { + if owner == *type_id { + r#gen.export_func(func); + } + } + } + } + r#gen.src.push_str("}\n"); + } + _ => {} + } + } + + r#gen.src.push_str("}\n"); + let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); interface_filepath.add_extension("d"); @@ -987,8 +1044,11 @@ impl<'a> DInterfaceGenerator<'a> { .replace("]", ":") )); + if d_sig.implicit_self || d_sig.static_member { + self.src.push_str("static "); + } self.src.push_str(&format!( - "static private extern(C) {} __import_{}({});\n", + "private extern(C) {} __import_{}({});\n", match wasm_sig.results.len() { 0 => "void", 1 => wasm_type(wasm_sig.results[0]), @@ -1003,6 +1063,128 @@ impl<'a> DInterfaceGenerator<'a> { .join(", ") )); } + + fn export_func(&mut self, func: &Function) { + match &func.kind { + FunctionKind::Freestanding => {} + FunctionKind::Method(_) => {} + kind => { + self.src + .push_str(&format!("// TODO: Export {kind:?} - {}\n", func.name)); + return; + } + } + + let wasm_sig = self + .resolve + .wasm_signature(abi::AbiVariant::GuestExport, func); + + let d_sig = self.get_d_signature(func); + + let mut params_data = Vec::new(); + let mut params = Vec::new(); + + if d_sig.implicit_self { + params.push("self"); + } + for (arg, _ty) in wasm_sig.params.iter().enumerate() { + params_data.push(format!("arg{arg}")); + } + for param in ¶ms_data { + params.push(¶m); + } + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + func.docs.contents.as_deref().unwrap_or_default() + )); + + self.src.push_str(&format!( + "alias {}_Sig = {} {}({});\n", + d_sig.name, + d_sig.result, + if d_sig.implicit_self { + "delegate" + } else { + "function" + }, + d_sig + .arguments + .iter() + .map(|(name, ty)| "in ".to_owned() + ty + " " + name) + .collect::>() + .join(", ") + )); + + self.src.push_str(&format!( + "/// ditto\nalias {}_Impl = findWitExportFunc!(\"{}\", \"{}\", {0}_Sig, {});\n", + d_sig.name, + self.wasm_import_module.unwrap(), + func.name, + match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => "Impl", + _ => { + "witExportsIn!_Resource_Impl" + } + } + )); + + self.src.push_str("/// ditto\n"); + self.src.push_str(&format!( + "@wasmExport!(\"{}#{}\")\n", + self.wasm_import_module.unwrap(), + func.name + )); + + self.src.push_str(&format!( + "pragma(mangle, \"__wit_export_{}__{}\")\n", + self.wasm_import_module + .unwrap() + .replace("/", "__") + .replace("-", "_"), + func.name + .replace("-", "_") + .replace("[", ":") + .replace("]", ":") + )); + + if d_sig.implicit_self || d_sig.static_member { + self.src.push_str("static "); + } + self.src.push_str(&format!( + "private extern(C) {} __export_{}({}) {{\n", + match wasm_sig.results.len() { + 0 => "void", + 1 => wasm_type(wasm_sig.results[0]), + _ => unimplemented!("multi-value return not supported"), + }, + d_sig.name, + wasm_sig + .params + .iter() + .zip(¶ms) + .map(|(ty, name)| format!("{} {name}", wasm_type(*ty))) + .collect::>() + .join(", ") + )); + + let mut f = FunctionBindgen::new(self, ¶ms); + abi::call( + f.r#gen.resolve, + abi::AbiVariant::GuestExport, + abi::LiftLower::LiftArgsLowerResults, + func, + &mut f, + false, + ); + let ret_area_decl = f.emit_ret_area_if_needed(); + + let FunctionBindgen { src, .. } = f; + self.src.push_str(&ret_area_decl); + self.src.push_str(&src.to_string()); + + self.src.push_str("}\n"); + } } impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { @@ -1171,7 +1353,85 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { panic!("Resource definition without owner?"); } }, - Some(Direction::Export) => todo!("export of resource"), + Some(Direction::Export) => match ty.owner { + TypeOwner::Interface(owner_id) => { + if let Some(cur_interface) = self.interface + && cur_interface == owner_id + { + } else { + panic!("Emitting resource from `interface` outside that interface?"); + } + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + self.src.push_str(&format!( + "struct {escaped_name} {{ + package(wit) uint __handle = 0; + + package(wit) this(uint handle) {{ + __handle = handle; + }} + + @disable this(); + + // TODO: make RAII? disable copy for the own + + " + )); + + /*for (_, func) in &self.resolve.interfaces[owner_id].functions { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(_) => false, + FunctionKind::Static(mid) => *mid == id, + FunctionKind::Constructor(mid) => *mid == id, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => false, + FunctionKind::AsyncStatic(_) => todo!(), + } { + self.import_func(func); + } + }*/ + + self.src.push_str(&format!( + "struct Borrow {{ + package(wit) void* __ptr = null; + + package(wit) this(void* ptr) {{ + __ptr = ptr; + }} + + @disable this(); + + " + )); + + /*for (_, func) in &self.resolve.interfaces[owner_id].functions { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(mid) => *mid == id, + FunctionKind::Static(_) => false, + FunctionKind::Constructor(_) => false, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => todo!(), + FunctionKind::AsyncStatic(_) => false, + } { + self.import_func(func); + } + }*/ + + self.src.push_str("}\n"); + + self.src.push_str("}\n"); + } + TypeOwner::World(_) => todo!("resources in worlds"), + TypeOwner::None => { + panic!("Resource definition without owner?"); + } + }, } //todo!("def of `resource`") } @@ -2060,7 +2320,55 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .join(", ") )); } - abi::Instruction::Return { amt, func } => match amt { + abi::Instruction::CallInterface { func, async_ } => { + if *async_ { + todo!("CallInterface async"); + } + + if func.result.is_some() { + self.src.push_str("auto _ret = "); + results.push("_ret".to_string()); + } + + let split_name = match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &func.name, + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => func.name.split(".").skip(1).next().unwrap(), + }; + + let lower_name = split_name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + let implicit_self = match &func.kind { + FunctionKind::Freestanding + | FunctionKind::AsyncFreestanding + | FunctionKind::Static(_) + | FunctionKind::AsyncStatic(_) + | FunctionKind::Constructor(_) => { + self.src.push_str(&format!("{escaped_name}_Impl(")); + false + } + FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => { + self.src.push_str(&format!( + "__traits(child, cast(_Resource_Impl*)self, {escaped_name})(" + )); + true + } + }; + self.src.push_str( + &operands + .iter() + .skip(if implicit_self { 1 } else { 0 }) + .map(|op| op.clone()) + .collect::>() + .join(", "), + ); + self.src.push_str(");\n"); + } + abi::Instruction::Return { amt, .. } => match amt { 0 => {} _ => { assert!(*amt == operands.len()); diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index cc8ea75d8..d9b0cdb6a 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -3,26 +3,15 @@ module wit.common; import core.attribute : mustuse; import ldc.attributes : llvmAttr; -package(wit) extern(C) { -void* malloc(size_t size); -void free(void* ptr); -} - -package(wit) auto mallocSlice(T)(size_t count) { - auto ptr = malloc(count*T.sizeof); - if (ptr is null) return null; - - return (cast(T*)ptr)[0..count]; -} - -// from std.meta -package(wit) alias AliasSeq(T...) = T; - alias wasmImport(string mod, string name) = AliasSeq!( llvmAttr("wasm-import-module", mod), llvmAttr("wasm-import-name", name) ); +enum wasmExport(string name) = llvmAttr("wasm-export-name", name); + +struct witExport { string mod; string name; } + /// Thin CABI compliant wrapper over `T[]` struct WitList(T) { @safe @nogc pure nothrow: @@ -239,3 +228,59 @@ public: in (isErr) do { return _storage.error; } } } + + +package(wit): +extern(C) { +void* malloc(size_t size); +void free(void* ptr); +} + +auto mallocSlice(T)(size_t count) { + auto ptr = malloc(count*T.sizeof); + if (ptr is null) return null; + + return (cast(T*)ptr)[0..count]; +} + +// from std.meta +alias AliasSeq(T...) = T; + + +template findWitExportFunc(string mod, string name, Sig, Impl...) { + static foreach(Func; Impl) { + static foreach(uda; __traits(getAttributes, Func)) { + static if (!is(uda) && is(typeof(uda) == witExport) && uda == witExport(mod, name)) { + static assert( + !is(Func) && + (is(typeof(Func) == function) || is(typeof(Func) == delegate)), + "The implementation of '", mod, "#", name, "' ", + "`", __traits(fullyQualifiedName, findWitExportFunc), "` ", + "must be a function or method." + ); + + static assert( + !is(typeof(findWitExportFunc) == void) || __traits(isSame, findWitExportFunc, Func), + "There must be only one implementation of '", mod, "#", name, "'. ", + "Found at least `", __traits(fullyQualifiedName, findWitExportFunc), + "` and `", __traits(fullyQualifiedName, Func), "`." + ); + alias findWitExportFunc = Func; + } + } + } + + static assert( + !is(typeof(findWitExportFunc) == void), + "Could not find implementation for '", mod, "#", name, "'" + ); + + static assert( + is(typeof(&findWitExportFunc) : Sig), + "The implementation of '", mod, "#", name, "' ", + "`", __traits(fullyQualifiedName, findWitExportFunc), "` ", + "must conform to the necessary signature. ", + "Found `", typeof(&findWitExportFunc), "`", + ", but expected `", Sig, "`" + ); +} From 5234a97ceb60c2b7e80b5745b98ecb8c32f4d95b Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 25 Mar 2026 00:53:31 -0700 Subject: [PATCH 08/55] Begin support for `cargo run test` [skip ci] --- crates/d/src/lib.rs | 208 +++++++++++++++++++++++++++++++---------- crates/test/src/lib.rs | 4 + 2 files changed, 161 insertions(+), 51 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 749de5562..22a83917c 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -191,6 +191,13 @@ fn escape_d_identifier(name: &str) -> &str { "while" => "while_", "with" => "with_", + // Common DRuntime & Phobos symbols + "Object" => "Object_", + "Error" => "Error_", + "Throwable" => "Throwable_", + "Exception" => "Exception_", + "TypeInfo" => "TypeInfo_", + // Symbols we define as part of the bindings we want to avoid creating conflicts with "WitList" => "WitList_", "WitString" => "WitString_", @@ -236,7 +243,7 @@ fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { .replace('.', "_") .replace('-', "_") .replace('+', "_"); - format!(".{version}") + format!("_{version}") } else { String::default() } @@ -1219,6 +1226,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { let mut is_first = true; for field in &record.fields { + let lower_name = field.name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + if is_first { is_first = false; } else { @@ -1230,9 +1240,8 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { field.docs.contents.as_deref().unwrap_or_default() )); self.src.push_str(&format!( - "{} {};\n", - self.type_name(&field.ty, &owner_fqn), - field.name.to_lower_camel_case() + "{} {escaped_name};\n", + self.type_name(&field.ty, &owner_fqn) )); } @@ -1730,14 +1739,27 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } } +struct Block { + body: String, + results: Vec, + element: String, + base: String, +} + +struct BlockStorage { + body: Source, + element: String, + base: String, +} + struct FunctionBindgen<'a, 'b> { r#gen: &'b mut DInterfaceGenerator<'a>, params: &'b [&'b str], tmp: usize, src: Source, - block_storage: Vec, + block_storage: Vec, /// intermediate calculations for contained objects - blocks: Vec<(String, Vec)>, + blocks: Vec, payloads: Vec, return_pointer_area_size: ArchitectureSize, return_pointer_area_align: Alignment, @@ -1857,8 +1879,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::I32Const { val } => results.push(val.to_string()), abi::Instruction::ConstZero { tys } => { - for _ in tys.iter() { - results.push("0".to_string()); + for ty in tys.iter() { + results.push( + match ty { + WasmType::Pointer => "null", + _ => "0", + } + .to_string(), + ); } } abi::Instruction::ListCanonLower { .. } | abi::Instruction::StringLower { .. } => { @@ -1883,10 +1911,19 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(format!("{}({ptr}[0..{len}])", list_name)); } - abi::Instruction::IterElem { .. } => results.push("_elem".into()), - abi::Instruction::IterBasePointer => results.push("_base".into()), + abi::Instruction::IterElem { .. } => { + results.push(self.block_storage.last().unwrap().element.clone()) + } + abi::Instruction::IterBasePointer => { + results.push(self.block_storage.last().unwrap().base.clone()) + } abi::Instruction::ListLower { element, .. } => { - let body = self.blocks.pop().unwrap(); + let Block { + body, + element: block_element, + base, + .. + } = self.blocks.pop().unwrap(); let tmp = self.tmp(); let size = self.r#gen.sizes.size(element); @@ -1902,9 +1939,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { operands[0] )); - self.push_str(&format!("foreach (i, const ref _elem; {list_src}) {{\n")); - self.push_str(&format!("auto _base = {list} + i * {size_str};\n")); - self.push_str(&body.0); + self.push_str(&format!( + "foreach ({block_element}_idx, const ref {block_element}; {list_src}) {{\n" + )); + self.push_str(&format!( + "auto {base} = {list} + {block_element}_idx * {size_str};\n" + )); + self.push_str(&body); //self.push_str(&format!("_targetElem = {};", body.1[0])); self.push_str("\n}\n"); @@ -1912,7 +1953,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(format!("{}.length", operands[0])); } abi::Instruction::ListLift { ty, element, .. } => { - let body = self.blocks.pop().unwrap(); + let Block { + body, + results: block_results, + element: block_element, + base, + } = self.blocks.pop().unwrap(); let tmp = self.tmp(); let size = self.r#gen.sizes.size(element); let size_str = size.format("size_t.sizeof"); @@ -1927,12 +1973,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { "auto {list} = wit.common.mallocSlice!({elem_type_name})({list_len});\n", )); - self.push_str(&format!("foreach (i, ref _elem; {list}) {{\n",)); self.push_str(&format!( - "const auto _base = {list_src} + i * {size_str};\n" + "foreach ({block_element}_idx, ref {block_element}; {list}) {{\n", + )); + self.push_str(&format!( + "const auto {base} = {list_src} + {block_element}_idx * {size_str};\n" )); - self.push_str(&body.0); - self.push_str(&format!("_elem = {};", body.1[0])); + self.push_str(&body); + self.push_str(&format!("{block_element} = {};", block_results[0])); self.push_str("\n}\n"); let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); @@ -2032,12 +2080,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let ty_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - self.push_str(&format!( - "final switch ({}.tag) with ({ty_name}.Tag) {{\n", - operands[0] - )); + let tag_type = tempname("_Tag", self.tmp()); + + self.push_str(&format!("alias {tag_type} = {ty_name}.Tag;\n")); + self.push_str(&format!("final switch ({}.tag) {{\n", operands[0])); - for (i, ((case, (block, block_results)), payload)) in + for (i, ((case, block), payload)) in variant.cases.iter().zip(blocks).zip(payloads).enumerate() { let lower_name = case.name.to_lower_camel_case(); @@ -2046,7 +2094,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let uppper_name = case.name.to_upper_camel_case(); let upper_escaped_name = escape_d_identifier(&uppper_name); - self.push_str(&format!("case {lower_escaped_name}: {{\n")); + self.push_str(&format!("case {tag_type}.{lower_escaped_name}: {{\n")); if let Some(ty) = case.ty.as_ref() { let ty_name = self.r#gen.type_name(ty, self.r#gen.fqn); self.push_str(&format!( @@ -2054,9 +2102,9 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { operands[0], )); } - self.src.push_str(&block); + self.src.push_str(&block.body); - for (name, result) in variant_results.iter().zip(&block_results) { + for (name, result) in variant_results.iter().zip(&block.results) { self.push_str(&format!("{name} = {result};\n")); } self.src.push_str("break;\n}\n"); @@ -2075,26 +2123,25 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let result = tempname("_variant", tmp); let tag = tempname("_tag", tmp); + let tag_type = tempname("_Tag", tmp); self.push_str(&format!("{ty} {result} = void;\n")); self.push_str(&format!("auto {tag} = {};\n", operands[0])); - self.push_str(&format!( - "final switch (cast({ty}.Tag){tag}) with ({ty}.Tag) {{\n" - )); - for (i, (case, (block, block_results))) in - variant.cases.iter().zip(blocks).enumerate() - { + + self.push_str(&format!("alias {tag_type} = {ty}.Tag;\n")); + self.push_str(&format!("final switch (cast({ty}.Tag){tag}) {{\n")); + for (i, (case, block)) in variant.cases.iter().zip(blocks).enumerate() { let lower_name = case.name.to_lower_camel_case(); let escaped_name = escape_d_identifier(&lower_name); let payload = tempname("_payload", self.tmp()); - self.push_str(&format!("case {escaped_name}: {{\n")); - self.src.push_str(&block); - assert!(block_results.len() == (case.ty.is_some() as usize)); + self.push_str(&format!("case {tag_type}.{escaped_name}: {{\n")); + self.src.push_str(&block.body); + assert!(block.results.len() == (case.ty.is_some() as usize)); let val = if let Some(_) = case.ty.as_ref() { - self.push_str(&format!("auto {payload} = {};\n", block_results[0])); + self.push_str(&format!("auto {payload} = {};\n", block.results[0])); &payload } else { "" @@ -2109,8 +2156,16 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results: result_types, .. } => { - let (mut some, some_results) = self.blocks.pop().unwrap(); - let (mut none, none_results) = self.blocks.pop().unwrap(); + let Block { + body: mut some, + results: some_results, + .. + } = self.blocks.pop().unwrap(); + let Block { + body: mut none, + results: none_results, + .. + } = self.blocks.pop().unwrap(); let some_payload = self.payloads.pop().unwrap(); let _none_payload = self.payloads.pop().unwrap(); @@ -2137,8 +2192,16 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { )); } abi::Instruction::OptionLift { ty, .. } => { - let (some, some_results) = self.blocks.pop().unwrap(); - let (_none, none_results) = self.blocks.pop().unwrap(); + let Block { + body: mut some, + results: some_results, + .. + } = self.blocks.pop().unwrap(); + let Block { + body: mut none, + results: none_results, + .. + } = self.blocks.pop().unwrap(); assert!(none_results.is_empty()); assert!(some_results.len() == 1); @@ -2168,8 +2231,16 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { result, .. } => { - let (mut err, err_results) = self.blocks.pop().unwrap(); - let (mut ok, ok_results) = self.blocks.pop().unwrap(); + let Block { + body: mut err, + results: err_results, + .. + } = self.blocks.pop().unwrap(); + let Block { + body: mut ok, + results: ok_results, + .. + } = self.blocks.pop().unwrap(); let err_payload = self.payloads.pop().unwrap(); let ok_payload = self.payloads.pop().unwrap(); @@ -2211,9 +2282,17 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::ResultLift { result, ty, .. } => { - let (err, err_results) = self.blocks.pop().unwrap(); + let Block { + body: mut err, + results: err_results, + .. + } = self.blocks.pop().unwrap(); assert!(err_results.len() == (result.err.is_some() as usize)); - let (ok, ok_results) = self.blocks.pop().unwrap(); + let Block { + body: mut ok, + results: ok_results, + .. + } = self.blocks.pop().unwrap(); assert!(ok_results.len() == (result.ok.is_some() as usize)); let full_type = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); @@ -2274,7 +2353,19 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); match flags.repr() { - FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => { + FlagsRepr::U8 => { + results.push(format!( + "{type_name}(cast(ubyte)({}))", + operands.pop().unwrap() + )); + } + FlagsRepr::U16 => { + results.push(format!( + "{type_name}(cast(ushort)({}))", + operands.pop().unwrap() + )); + } + FlagsRepr::U32(1) => { results.push(format!("{type_name}({})", operands.pop().unwrap())); } FlagsRepr::U32(2) => { @@ -2456,14 +2547,29 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } fn push_block(&mut self) { - let prev = take(&mut self.src); - self.block_storage.push(prev); + let tmp = self.tmp(); + + self.block_storage.push(BlockStorage { + body: take(&mut self.src), + element: tempname("_elem", tmp), + base: tempname("_base", tmp), + }); } fn finish_block(&mut self, operands: &mut Vec) { - let to_restore = self.block_storage.pop().unwrap(); - let src = replace(&mut self.src, to_restore); - self.blocks.push((src.into(), take(operands))); + let BlockStorage { + body, + element, + base, + } = self.block_storage.pop().unwrap(); + + let src = replace(&mut self.src, body); + self.blocks.push(Block { + body: src.into(), + results: take(operands), + element, + base, + }); } fn sizes(&self) -> &SizeAlign { diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index 7a54f4612..b7e89553e 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -17,6 +17,7 @@ mod config; mod cpp; mod csharp; mod custom; +mod d; mod go; mod moonbit; mod runner; @@ -229,6 +230,7 @@ enum Language { Csharp, MoonBit, Go, + D, Custom(custom::Language), } @@ -1322,6 +1324,7 @@ impl Language { Language::Csharp, Language::MoonBit, Language::Go, + Language::D, ]; fn obj(&self) -> &dyn LanguageMethods { @@ -1333,6 +1336,7 @@ impl Language { Language::Csharp => &csharp::Csharp, Language::MoonBit => &moonbit::MoonBit, Language::Go => &go::Go, + Language::D => &d::D, Language::Custom(custom) => custom, } } From 13662bfc45a742019fe152c696d81d5b2ef30986 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 30 Mar 2026 23:13:33 -0700 Subject: [PATCH 09/55] Finish world-level imports & exports. [skip ci] --- crates/d/src/lib.rs | 258 +++++++++++++++++++++++++------------------- 1 file changed, 146 insertions(+), 112 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 22a83917c..29a69ec0c 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -444,7 +444,6 @@ impl WorldGenerator for D { r#gen.interface = Some(id); r#gen.prologue(); - r#gen.src.push_str("// Types"); if let WorldKey::Name(_) = name { // We have an inline interface imported in a world. // Emit the "common" types as well @@ -456,7 +455,6 @@ impl WorldGenerator for D { r#gen.types(id); - r#gen.src.push_str("\n// Functions\n"); for (_name, func) in &resolve.interfaces[id].functions { match func.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { @@ -491,24 +489,22 @@ impl WorldGenerator for D { r#gen.define_type(name, *id); } - let src = take(&mut r#gen.src); - self.type_imports_src.append_src(&src); + self.type_imports_src = take(&mut r#gen.src); } fn import_funcs( &mut self, - _resolve: &Resolve, + resolve: &Resolve, _world: WorldId, funcs: &[(&str, &Function)], _files: &mut Files, ) { - let _name = WorldKey::Name("$root".to_string()); - //let wasm_import_module = resolve.name_world_key(&name); - - for (name, _func) in funcs { - self.function_imports_src - .push_str(&format!("// Import function - {name}\n")); + let mut r#gen = self.interface(resolve, Some(Direction::Import), None, Some("$root")); + for (_name, func) in funcs { + r#gen.import_func(func); } + + self.function_imports_src = take(&mut r#gen.src); } fn export_interface( @@ -615,15 +611,17 @@ impl WorldGenerator for D { fn export_funcs( &mut self, - _resolve: &Resolve, + resolve: &Resolve, _world: WorldId, funcs: &[(&str, &Function)], _files: &mut Files, ) -> Result<()> { - for (name, _func) in funcs { - self.function_exports_src - .push_str(&format!("// Export function: {name}\n")); + let mut r#gen = self.interface(resolve, Some(Direction::Export), None, Some("$root")); + for (_name, func) in funcs { + r#gen.export_func(func); } + + self.function_exports_src = take(&mut r#gen.src); Ok(()) } @@ -657,7 +655,6 @@ impl WorldGenerator for D { world_src.push_str(&format!("module {};\n\n", self.world_fqn)); world_src.push_str("import wit.common;\n\n"); - world_src.push_str("// Interface imports\n"); world_src.push_str( &self .interface_imports @@ -667,13 +664,8 @@ impl WorldGenerator for D { .join("\n"), ); - world_src.push_str("\n\n// Type imports\n"); - world_src.append_src(&self.type_imports_src); - - world_src.push_str("\n// Function imports\n"); - world_src.append_src(&self.function_imports_src); + world_src.push_str("\n"); - world_src.push_str("\n// Interface exports\n"); world_src.push_str( &self .interface_exports @@ -683,10 +675,14 @@ impl WorldGenerator for D { .join("\n"), ); + world_src.push_str("\n"); + + world_src.append_src(&self.type_imports_src); + + world_src.append_src(&self.function_imports_src); + world_src.push_str("\n\nprivate alias AliasSeq(T...) = T;\n"); world_src.push_str("template Exports(Impl...) {\n"); - world_src.push_str("// Interface exports\n"); - world_src.push_str("alias InterfaceExports = AliasSeq!(\n"); world_src.indent(1); world_src.push_str( @@ -700,8 +696,7 @@ impl WorldGenerator for D { world_src.deindent(1); world_src.push_str("\n);\n"); - world_src.push_str("\n// Function exports\n"); - world_src.append_src(&self.function_exports_src); + world_src.push_str(&self.function_exports_src.as_str()); world_src.push_str("}\n"); let mut world_filepath = PathBuf::from_iter(get_world_fqn(world_id, resolve).split(".")); @@ -1256,112 +1251,151 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { match self.direction { None => panic!("Resources can only be generated for imports, or exports. Not common."), - Some(Direction::Import) => match ty.owner { - TypeOwner::Interface(owner_id) => { - if let Some(cur_interface) = self.interface - && cur_interface == owner_id - { - } else { - panic!("Emitting resource from `interface` outside that interface?"); - } - - self.src.push_str(&format!( - "\n/++\n{}\n+/\n", - docs.contents.as_deref().unwrap_or_default() - )); + Some(Direction::Import) => { + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); - self.src.push_str(&format!( - "struct {escaped_name} {{ - package(wit) uint __handle = 0; + self.src.push_str(&format!( + "struct {escaped_name} {{ + package(wit) uint __handle = 0; - package(wit) this(uint handle) {{ - __handle = handle; - }} + package(wit) this(uint handle) {{ + __handle = handle; + }} - @disable this(); + @disable this(); - // TODO: make RAII? disable copy for the own + // TODO: make RAII? disable copy for the own - auto borrow() => Borrow(__handle); - alias borrow this; + auto borrow() => Borrow(__handle); + alias borrow this; - " - )); + " + )); - for (_, func) in &self.resolve.interfaces[owner_id].functions { - if match &func.kind { - FunctionKind::Freestanding => false, - FunctionKind::Method(_) => false, - FunctionKind::Static(mid) => *mid == id, - FunctionKind::Constructor(mid) => *mid == id, - FunctionKind::AsyncFreestanding => false, - FunctionKind::AsyncMethod(_) => false, - FunctionKind::AsyncStatic(_) => todo!(), - } { - self.import_func(func); + match ty.owner { + TypeOwner::Interface(owner_id) => { + for (_, func) in &self.resolve.interfaces[owner_id].functions { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(_) => false, + FunctionKind::Static(mid) => *mid == id, + FunctionKind::Constructor(mid) => *mid == id, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => false, + FunctionKind::AsyncStatic(_) => todo!(), + } { + self.import_func(func); + } } } + TypeOwner::World(owner_id) => { + for (_, import) in &self.resolve.worlds[owner_id].imports { + match &import { + WorldItem::Function(func) => { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(_) => false, + FunctionKind::Static(mid) => *mid == id, + FunctionKind::Constructor(mid) => *mid == id, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => false, + FunctionKind::AsyncStatic(_) => todo!(), + } { + self.import_func(func); + } + } + _ => {} + } + } + } + TypeOwner::None => { + panic!("Resource definition without owner?"); + } + } - self.src - .push_str("void drop() {\n__import__drop(__handle);\n}\n"); + self.src + .push_str("void drop() {\n__import__drop(__handle);\n}\n"); - self.src.push_str(&format!( - "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", - self.wasm_import_module.unwrap(), - name - )); + self.src.push_str(&format!( + "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", + self.wasm_import_module.unwrap(), + name + )); - // The mangle is not important, as long as it won't conflict with other symbols - // WebAssembly symbol identifiers are much more permissive than C (can be any UTF-8). - // Yet, LDC before 1.42 doesn't allow full use of this fact. We make some substitutions. - self.src.push_str(&format!( - "pragma(mangle, \"__wit_import_{}__:resource_drop:{}\")\n", - self.wasm_import_module - .unwrap() - .replace("/", "__") - .replace("-", "_"), - name.replace("-", "_") - )); - self.src - .push_str("static private extern(C) void __import__drop(uint);\n\n"); + // The mangle is not important, as long as it won't conflict with other symbols + // WebAssembly symbol identifiers are much more permissive than C (can be any UTF-8). + // Yet, LDC before 1.42 doesn't allow full use of this fact. We make some substitutions. + self.src.push_str(&format!( + "pragma(mangle, \"__wit_import_{}__:resource_drop:{}\")\n", + self.wasm_import_module + .unwrap() + .replace("/", "__") + .replace("-", "_"), + name.replace("-", "_") + )); + self.src + .push_str("static private extern(C) void __import__drop(uint);\n\n"); - self.src.push_str(&format!( - "struct Borrow {{ - package(wit) uint __handle = 0; + self.src.push_str(&format!( + "struct Borrow {{ + package(wit) uint __handle = 0; - package(wit) this(uint handle) {{ - __handle = handle; - }} + package(wit) this(uint handle) {{ + __handle = handle; + }} - @disable this(); + @disable this(); - " - )); + " + )); - for (_, func) in &self.resolve.interfaces[owner_id].functions { - if match &func.kind { - FunctionKind::Freestanding => false, - FunctionKind::Method(mid) => *mid == id, - FunctionKind::Static(_) => false, - FunctionKind::Constructor(_) => false, - FunctionKind::AsyncFreestanding => false, - FunctionKind::AsyncMethod(_) => todo!(), - FunctionKind::AsyncStatic(_) => false, - } { - self.import_func(func); + match ty.owner { + TypeOwner::Interface(owner_id) => { + for (_, func) in &self.resolve.interfaces[owner_id].functions { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(mid) => *mid == id, + FunctionKind::Static(_) => false, + FunctionKind::Constructor(_) => false, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => todo!(), + FunctionKind::AsyncStatic(_) => false, + } { + self.import_func(func); + } } } - - self.src.push_str("}\n"); - - self.src.push_str("}\n"); - } - TypeOwner::World(_) => todo!("resources in worlds"), - TypeOwner::None => { - panic!("Resource definition without owner?"); + TypeOwner::World(owner_id) => { + for (_, import) in &self.resolve.worlds[owner_id].imports { + match &import { + WorldItem::Function(func) => { + if match &func.kind { + FunctionKind::Freestanding => false, + FunctionKind::Method(mid) => *mid == id, + FunctionKind::Static(_) => false, + FunctionKind::Constructor(_) => false, + FunctionKind::AsyncFreestanding => false, + FunctionKind::AsyncMethod(_) => todo!(), + FunctionKind::AsyncStatic(_) => false, + } { + self.import_func(func); + } + } + _ => {} + } + } + } + TypeOwner::None => { + panic!("Resource definition without owner?"); + } } - }, + self.src.push_str("}\n"); + self.src.push_str("}\n"); + } Some(Direction::Export) => match ty.owner { TypeOwner::Interface(owner_id) => { if let Some(cur_interface) = self.interface @@ -1436,7 +1470,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str("}\n"); } - TypeOwner::World(_) => todo!("resources in worlds"), + TypeOwner::World(_) => unimplemented!("resource exports in worlds"), TypeOwner::None => { panic!("Resource definition without owner?"); } From b426696b5457c327ff00e047e4ef64a8a812cbe0 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 2 Apr 2026 15:32:38 -0700 Subject: [PATCH 10/55] Fix resource lookups --- crates/d/src/lib.rs | 59 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 29a69ec0c..3a79d88b4 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -726,7 +726,9 @@ impl<'a> DInterfaceGenerator<'a> { fn scoped_type_name(&self, id: TypeId, from_module_fqn: &str) -> String { let ty = &self.resolve.types[id]; - let owner_fqn = self.type_owner_fqn(&ty.owner).unwrap(); + let owner_fqn = self + .type_owner_fqn(&ty.owner, self.r#gen.types.get(id).has_resource) + .unwrap(); let upper_name = ty.name.as_ref().unwrap().to_upper_camel_case(); let escaped_name = escape_d_identifier(&upper_name); @@ -820,14 +822,27 @@ impl<'a> DInterfaceGenerator<'a> { } } - fn type_owner_fqn(&self, owner: &TypeOwner) -> Option<&str> { + fn type_owner_fqn(&self, owner: &TypeOwner, imports_instead_of_common: bool) -> Option<&str> { match &owner { TypeOwner::None => None, TypeOwner::Interface(interface_id) => match self.direction { Some(_) => self .r#gen .lookup_interface_fqn(*interface_id, self.direction) - .or_else(|| self.r#gen.lookup_interface_fqn(*interface_id, None)), + .or_else(|| { + if !imports_instead_of_common || self.direction != Some(Direction::Import) { + self.r#gen.lookup_interface_fqn( + *interface_id, + if imports_instead_of_common { + Some(Direction::Import) + } else { + None + }, + ) + } else { + None + } + }), None => self.r#gen.lookup_interface_fqn(*interface_id, None), }, TypeOwner::World(world_id) => { @@ -882,6 +897,17 @@ impl<'a> DInterfaceGenerator<'a> { None => common_fqn, } )); + + if self.direction == Some(Direction::Export) { + if let Some(import_fqn) = self + .r#gen + .lookup_interface_fqn(dep_id, Some(Direction::Import)) + { + self.src.push_str("static import "); + self.src.push_str(import_fqn); + self.src.push_str(";\n"); + } + } } else { self.src .push_str(&format!("static import {};\n", common_fqn)); @@ -1209,7 +1235,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { let escaped_name = escape_d_identifier(&upper_name); let owner_fqn = self - .type_owner_fqn(&self.resolve.types[id].owner) + .type_owner_fqn(&self.resolve.types[id].owner, false) .unwrap() .to_string(); @@ -1488,7 +1514,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { docs.contents.as_deref().unwrap_or_default() )); - let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner, false) + .unwrap(); self.src.push_str(&format!( "alias {escaped_name} = Tuple!({});", tuple @@ -1549,7 +1577,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { }; let owner_fqn = self - .type_owner_fqn(&self.resolve.types[id].owner) + .type_owner_fqn(&self.resolve.types[id].owner, false) .unwrap() .to_string(); @@ -1641,7 +1669,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { docs.contents.as_deref().unwrap_or_default() )); - let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner, false) + .unwrap(); self.src.push_str(&format!( "alias {escaped_name} = Option!({});", self.type_name(payload, owner_fqn) @@ -1657,7 +1687,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { docs.contents.as_deref().unwrap_or_default() )); - let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner, false) + .unwrap(); self.src.push_str(&format!( "alias {escaped_name} = Result!({}, {});", self.optional_type_name(result.ok.as_ref(), owner_fqn), @@ -1714,7 +1746,8 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { let typename = self.type_name( alias_ty, - self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(), + self.type_owner_fqn(&self.resolve.types[id].owner, false) + .unwrap(), ); self.src @@ -1730,7 +1763,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { docs.contents.as_deref().unwrap_or_default() )); - let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner, false) + .unwrap(); self.src.push_str(&format!( "alias {escaped_name} = WitList!({});", self.type_name(ty, owner_fqn) @@ -1753,7 +1788,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { docs.contents.as_deref().unwrap_or_default() )); - let owner_fqn = self.type_owner_fqn(&self.resolve.types[id].owner).unwrap(); + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner, false) + .unwrap(); self.src.push_str(&format!( "alias {escaped_name} = {}[{size}];", self.type_name(ty, owner_fqn) From 3981268bb76b63b0b91f72cd3ede3228b917da7f Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 2 Apr 2026 23:56:56 -0700 Subject: [PATCH 11/55] Reorder emit instructions --- crates/d/src/lib.rs | 327 +++++++++++++++++++++++++------------------- 1 file changed, 184 insertions(+), 143 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 3a79d88b4..6dd0a867e 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1948,6 +1948,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(self.params[*nth].into()); } } + abi::Instruction::I32Const { val } => results.push(val.to_string()), abi::Instruction::ConstZero { tys } => { for ty in tys.iter() { @@ -1960,33 +1961,70 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ); } } - abi::Instruction::ListCanonLower { .. } | abi::Instruction::StringLower { .. } => { - results.push(format!("cast(void*)({}.ptr)", operands[0])); - results.push(format!("{}.length", operands[0])); + + abi::Instruction::I32Load { offset } => self.load("uint", *offset, operands, results), + abi::Instruction::I32Load8U { offset } => { + self.load_ext("ubyte", *offset, operands, results) } - abi::Instruction::ListCanonLift { element, ty, .. } => { - let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - let elem_name = self.r#gen.type_name(element, self.r#gen.fqn); - let tmp = self.tmp(); + abi::Instruction::I32Load8S { offset } => { + self.load_ext("byte", *offset, operands, results) + } + abi::Instruction::I32Load16U { offset } => { + self.load_ext("ushort", *offset, operands, results) + } + abi::Instruction::I32Load16S { offset } => { + self.load_ext("short", *offset, operands, results) + } + abi::Instruction::I64Load { offset } => self.load("ulong", *offset, operands, results), + abi::Instruction::F32Load { offset } => self.load("float", *offset, operands, results), + abi::Instruction::F64Load { offset } => self.load("double", *offset, operands, results), - let ptr = tempname("_ptr", tmp); - let len = tempname("_len", tmp); + abi::Instruction::PointerLoad { offset } => { + self.load("void*", *offset, operands, results) + } + abi::Instruction::LengthLoad { offset } => { + self.load("size_t", *offset, operands, results) + } - self.push_str(&format!( - "auto {ptr} = cast({elem_name}*)({}); - auto {len} = {}; - ", - operands[0], operands[1] - )); + abi::Instruction::I32Store { offset } => self.store("uint", *offset, operands), + abi::Instruction::I32Store8 { offset } => self.store("ubyte", *offset, operands), + abi::Instruction::I32Store16 { offset } => self.store("ushort", *offset, operands), - results.push(format!("{}({ptr}[0..{len}])", list_name)); - } + abi::Instruction::I64Store { offset } => self.store("ulong", *offset, operands), + abi::Instruction::F32Store { offset } => self.store("float", *offset, operands), + abi::Instruction::F64Store { offset } => self.store("double", *offset, operands), - abi::Instruction::IterElem { .. } => { - results.push(self.block_storage.last().unwrap().element.clone()) - } - abi::Instruction::IterBasePointer => { - results.push(self.block_storage.last().unwrap().base.clone()) + abi::Instruction::PointerStore { offset } => self.store("void*", *offset, operands), + abi::Instruction::LengthStore { offset } => self.store("size_t", *offset, operands), + + abi::Instruction::I32FromChar + | abi::Instruction::I32FromBool + | abi::Instruction::I32FromU8 + | abi::Instruction::I32FromS8 + | abi::Instruction::I32FromU16 + | abi::Instruction::I32FromS16 + | abi::Instruction::I32FromU32 + | abi::Instruction::I32FromS32 => top_as("uint"), + abi::Instruction::I64FromU64 | abi::Instruction::I64FromS64 => top_as("ulong"), + abi::Instruction::CoreF32FromF32 => top_as("float"), + abi::Instruction::CoreF64FromF64 => top_as("double"), + + abi::Instruction::S8FromI32 => top_as("byte"), + abi::Instruction::U8FromI32 => top_as("ubyte"), + abi::Instruction::S16FromI32 => top_as("short"), + abi::Instruction::U16FromI32 => top_as("ushort"), + abi::Instruction::S32FromI32 => top_as("int"), + abi::Instruction::U32FromI32 => top_as("uint"), + abi::Instruction::S64FromI64 => top_as("long"), + abi::Instruction::U64FromI64 => top_as("ulong"), + abi::Instruction::CharFromI32 => top_as("dchar"), + abi::Instruction::F32FromCoreF32 => top_as("float"), + abi::Instruction::F64FromCoreF64 => top_as("double"), + abi::Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])), + + abi::Instruction::ListCanonLower { .. } | abi::Instruction::StringLower { .. } => { + results.push(format!("cast(void*)({}.ptr)", operands[0])); + results.push(format!("{}.length", operands[0])); } abi::Instruction::ListLower { element, .. } => { let Block { @@ -2023,6 +2061,39 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(format!("{list}")); results.push(format!("{}.length", operands[0])); } + + abi::Instruction::ListCanonLift { element, ty, .. } => { + let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + let elem_name = self.r#gen.type_name(element, self.r#gen.fqn); + let tmp = self.tmp(); + + let ptr = tempname("_ptr", tmp); + let len = tempname("_len", tmp); + + self.push_str(&format!( + "auto {ptr} = cast({elem_name}*)({}); + auto {len} = {}; + ", + operands[0], operands[1] + )); + + results.push(format!("{}({ptr}[0..{len}])", list_name)); + } + abi::Instruction::StringLift => { + let tmp = self.tmp(); + + let ptr = tempname("_ptr", tmp); + let len = tempname("_len", tmp); + + self.push_str(&format!( + "auto {ptr} = cast(char*)({}); + auto {len} = {}; + ", + operands[0], operands[1] + )); + + results.push(format!("WitString({ptr}[0..{len}])")); + } abi::Instruction::ListLift { ty, element, .. } => { let Block { body, @@ -2057,6 +2128,27 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); results.push(format!("{list_name}({list})")); } + + abi::Instruction::FixedLengthListLift { .. } => { + todo!("instr: FixedLengthListLower"); + } + abi::Instruction::FixedLengthListLower { .. } => { + todo!("instr: FixedLengthListLower"); + } + abi::Instruction::FixedLengthListLowerToMemory { .. } => { + todo!("instr: FixedLengthListLowerToMemory"); + } + abi::Instruction::FixedLengthListLiftFromMemory { .. } => { + todo!("instr: FixedLengthListLiftFromMemory"); + } + + abi::Instruction::IterElem { .. } => { + results.push(self.block_storage.last().unwrap().element.clone()) + } + abi::Instruction::IterBasePointer => { + results.push(self.block_storage.last().unwrap().base.clone()) + } + abi::Instruction::RecordLower { record, .. } => { for field in record.fields.iter() { let lower_name = field.name.to_lower_camel_case(); @@ -2082,12 +2174,20 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(tmpvar); } + abi::Instruction::HandleLower { .. } => { + let op = &operands[0]; + results.push(format!("{op}.__handle")) + } + abi::Instruction::HandleLift { ty, .. } => { + let name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + results.push(format!("{name}({})", operands[0])); + } + abi::Instruction::TupleLower { tuple, .. } => { for i in 0..tuple.types.len() { results.push(format!("{}[{i}]", &operands[0])); } } - abi::Instruction::TupleLift { tuple, ty, .. } => { let name = tempname("_tuple", self.tmp()); self.push_str(&format!( @@ -2104,20 +2204,46 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(name); } - abi::Instruction::StringLift { .. } => { - let tmp = self.tmp(); - - let ptr = tempname("_ptr", tmp); - let len = tempname("_len", tmp); + abi::Instruction::FlagsLower { flags, .. } => match flags.repr() { + FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => { + results.push(format!("cast(uint)({}.bits)", operands.pop().unwrap())); + } + FlagsRepr::U32(2) => { + let tempname = tempname("_flags", self.tmp()); - self.push_str(&format!( - "auto {ptr} = cast(char*)({}); - auto {len} = {}; - ", - operands[0], operands[1] - )); + self.push_str(&format!("auto {tempname} = {};", operands[0])); + results.push(format!("cast(uint)({tempname}.bits & 0xffffffff)")); + results.push(format!("cast(uint)(({tempname}.bits >> 32) & 0xffffffff)")); + } + _ => todo!(), + }, + abi::Instruction::FlagsLift { flags, ty, .. } => { + let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - results.push(format!("WitString({ptr}[0..{len}])")); + match flags.repr() { + FlagsRepr::U8 => { + results.push(format!( + "{type_name}(cast(ubyte)({}))", + operands.pop().unwrap() + )); + } + FlagsRepr::U16 => { + results.push(format!( + "{type_name}(cast(ushort)({}))", + operands.pop().unwrap() + )); + } + FlagsRepr::U32(1) => { + results.push(format!("{type_name}({})", operands.pop().unwrap())); + } + FlagsRepr::U32(2) => { + results.push(format!( + "({type_name}({}) | {type_name}({} << 32))", + operands[0], operands[1] + )); + } + _ => todo!(), + } } abi::Instruction::VariantPayloadName => { @@ -2223,6 +2349,15 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.src.push_str("}\n"); results.push(result); } + + abi::Instruction::EnumLower { .. } => { + results.push(format!("cast(uint)({})", operands[0])) + } + abi::Instruction::EnumLift { ty, .. } => { + let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); + results.push(format!("cast({})({})", type_name, operands.pop().unwrap())) + } + abi::Instruction::OptionLower { results: result_types, .. @@ -2351,7 +2486,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { " )); } - abi::Instruction::ResultLift { result, ty, .. } => { let Block { body: mut err, @@ -2399,66 +2533,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(resultname); } - abi::Instruction::EnumLower { .. } => { - results.push(format!("cast(uint)({})", operands[0])) - } - abi::Instruction::EnumLift { ty, .. } => { - let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - results.push(format!("cast({})({})", type_name, operands.pop().unwrap())) - } - - abi::Instruction::FlagsLower { flags, .. } => match flags.repr() { - FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => { - results.push(format!("cast(uint)({}.bits)", operands.pop().unwrap())); - } - FlagsRepr::U32(2) => { - let tempname = tempname("_flags", self.tmp()); - - self.push_str(&format!("auto {tempname} = {};", operands[0])); - results.push(format!("cast(uint)({tempname}.bits & 0xffffffff)")); - results.push(format!("cast(uint)(({tempname}.bits >> 32) & 0xffffffff)")); - } - _ => todo!(), - }, - abi::Instruction::FlagsLift { flags, ty, .. } => { - let type_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - - match flags.repr() { - FlagsRepr::U8 => { - results.push(format!( - "{type_name}(cast(ubyte)({}))", - operands.pop().unwrap() - )); - } - FlagsRepr::U16 => { - results.push(format!( - "{type_name}(cast(ushort)({}))", - operands.pop().unwrap() - )); - } - FlagsRepr::U32(1) => { - results.push(format!("{type_name}({})", operands.pop().unwrap())); - } - FlagsRepr::U32(2) => { - results.push(format!( - "({type_name}({}) | {type_name}({} << 32))", - operands[0], operands[1] - )); - } - _ => todo!(), - } - } - - abi::Instruction::HandleLower { .. } => { - let op = &operands[0]; - results.push(format!("{op}.__handle")) - } - - abi::Instruction::HandleLift { ty, .. } => { - let name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - results.push(format!("{name}({})", operands[0])); - } - abi::Instruction::CallWasm { name, sig } => { let split_name = if name.contains('.') { name.split(".").skip(1).next().unwrap() @@ -2544,59 +2618,25 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } } }, - abi::Instruction::I32Load { offset } => self.load("uint", *offset, operands, results), - abi::Instruction::I64Load { offset } => self.load("ulong", *offset, operands, results), - abi::Instruction::F32Load { offset } => self.load("float", *offset, operands, results), - abi::Instruction::F64Load { offset } => self.load("double", *offset, operands, results), - abi::Instruction::PointerLoad { offset } => { - self.load("void*", *offset, operands, results) + + abi::Instruction::Malloc { .. } => { + todo!("instr: Malloc") } - abi::Instruction::LengthLoad { offset } => { - self.load("size_t", *offset, operands, results) + abi::Instruction::GuestDeallocate { .. } => { + todo!("instr: GuestDeallocate") } - abi::Instruction::I32Store { offset } => self.store("uint", *offset, operands), - abi::Instruction::I64Store { offset } => self.store("ulong", *offset, operands), - abi::Instruction::F32Store { offset } => self.store("float", *offset, operands), - abi::Instruction::F64Store { offset } => self.store("double", *offset, operands), - abi::Instruction::I32Store8 { offset } => self.store("ubyte", *offset, operands), - abi::Instruction::I32Store16 { offset } => self.store("ushort", *offset, operands), - abi::Instruction::PointerStore { offset } => self.store("void*", *offset, operands), - abi::Instruction::LengthStore { offset } => self.store("size_t", *offset, operands), - abi::Instruction::I32Load8U { offset } => { - self.load_ext("ubyte", *offset, operands, results) + abi::Instruction::GuestDeallocateString { .. } => { + todo!("instr: GuestDeallocateString") } - abi::Instruction::I32Load8S { offset } => { - self.load_ext("byte", *offset, operands, results) + abi::Instruction::GuestDeallocateList { .. } => { + todo!("instr: GuestDeallocateList") } - abi::Instruction::I32Load16U { offset } => { - self.load_ext("ushort", *offset, operands, results) + abi::Instruction::GuestDeallocateVariant { .. } => { + todo!("instr: GuestDeallocateVariant") } - abi::Instruction::I32Load16S { offset } => { - self.load_ext("short", *offset, operands, results) + abi::Instruction::DropHandle { .. } => { + todo!("instr: DropHandle") } - abi::Instruction::I32FromChar - | abi::Instruction::I32FromBool - | abi::Instruction::I32FromU8 - | abi::Instruction::I32FromS8 - | abi::Instruction::I32FromU16 - | abi::Instruction::I32FromS16 - | abi::Instruction::I32FromU32 - | abi::Instruction::I32FromS32 => top_as("uint"), - abi::Instruction::I64FromU64 | abi::Instruction::I64FromS64 => top_as("ulong"), - abi::Instruction::F32FromCoreF32 => top_as("float"), - abi::Instruction::F64FromCoreF64 => top_as("double"), - abi::Instruction::S8FromI32 => top_as("byte"), - abi::Instruction::U8FromI32 => top_as("ubyte"), - abi::Instruction::S16FromI32 => top_as("short"), - abi::Instruction::U16FromI32 => top_as("ushort"), - abi::Instruction::S32FromI32 => top_as("int"), - abi::Instruction::U32FromI32 => top_as("uint"), - abi::Instruction::S64FromI64 => top_as("long"), - abi::Instruction::U64FromI64 => top_as("ulong"), - abi::Instruction::CharFromI32 => top_as("dchar"), - abi::Instruction::CoreF32FromF32 => top_as("float"), - abi::Instruction::CoreF64FromF64 => top_as("double"), - abi::Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])), abi::Instruction::Flush { amt } => { for op in operands.iter().take(*amt) { @@ -2605,6 +2645,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(result); } } + unk => todo!("emit instruction: {unk:?}"), } } From cf6bcdd44339dcb97d0b123140b15049bfa470d3 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 3 Apr 2026 11:31:37 -0700 Subject: [PATCH 12/55] Fixed length lists --- crates/d/src/lib.rs | 92 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 17 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 6dd0a867e..868e800cb 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1916,7 +1916,12 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { fn emit_ret_area_if_needed(&self) -> String { if !self.return_pointer_area_size.is_empty() { format!( - "align({}) void[{}] _retArea = void;\n", + "{}align({}) void[{}] _retArea = void;\n", + if self.r#gen.direction == Some(Direction::Export) { + "static " + } else { + "" + }, self.return_pointer_area_align.format("size_t.sizeof"), self.return_pointer_area_size.format("size_t.sizeof") ) @@ -2003,23 +2008,25 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { | abi::Instruction::I32FromS8 | abi::Instruction::I32FromU16 | abi::Instruction::I32FromS16 - | abi::Instruction::I32FromU32 | abi::Instruction::I32FromS32 => top_as("uint"), - abi::Instruction::I64FromU64 | abi::Instruction::I64FromS64 => top_as("ulong"), - abi::Instruction::CoreF32FromF32 => top_as("float"), - abi::Instruction::CoreF64FromF64 => top_as("double"), + abi::Instruction::I32FromU32 => results.push(operands.pop().unwrap()), + + abi::Instruction::I64FromU64 => results.push(operands.pop().unwrap()), + abi::Instruction::I64FromS64 => top_as("ulong"), + abi::Instruction::CoreF32FromF32 => results.push(operands.pop().unwrap()), + abi::Instruction::CoreF64FromF64 => results.push(operands.pop().unwrap()), abi::Instruction::S8FromI32 => top_as("byte"), abi::Instruction::U8FromI32 => top_as("ubyte"), abi::Instruction::S16FromI32 => top_as("short"), abi::Instruction::U16FromI32 => top_as("ushort"), abi::Instruction::S32FromI32 => top_as("int"), - abi::Instruction::U32FromI32 => top_as("uint"), + abi::Instruction::U32FromI32 => results.push(operands.pop().unwrap()), abi::Instruction::S64FromI64 => top_as("long"), - abi::Instruction::U64FromI64 => top_as("ulong"), + abi::Instruction::U64FromI64 => results.push(operands.pop().unwrap()), abi::Instruction::CharFromI32 => top_as("dchar"), - abi::Instruction::F32FromCoreF32 => top_as("float"), - abi::Instruction::F64FromCoreF64 => top_as("double"), + abi::Instruction::F32FromCoreF32 => results.push(operands.pop().unwrap()), + abi::Instruction::F64FromCoreF64 => results.push(operands.pop().unwrap()), abi::Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])), abi::Instruction::ListCanonLower { .. } | abi::Instruction::StringLower { .. } => { @@ -2129,17 +2136,68 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(format!("{list_name}({list})")); } - abi::Instruction::FixedLengthListLift { .. } => { - todo!("instr: FixedLengthListLower"); + abi::Instruction::FixedLengthListLift { size, id, .. } => { + let result = tempname("_arr", self.tmp()); + let type_name = self.r#gen.type_name(&Type::Id(*id), self.r#gen.fqn); + self.push_str(&format!("{type_name} {result} = [\n",)); + self.src.indent(1); + for op in operands.drain(0..(*size as usize)) { + self.push_str(&op); + self.push_str(", \n"); + } + self.src.deindent(1); + self.push_str("];\n"); + results.push(result); } - abi::Instruction::FixedLengthListLower { .. } => { - todo!("instr: FixedLengthListLower"); + abi::Instruction::FixedLengthListLower { size, .. } => { + for i in 0..(*size as usize) { + results.push(format!("{}[{i}]", operands[0])); + } } - abi::Instruction::FixedLengthListLowerToMemory { .. } => { - todo!("instr: FixedLengthListLowerToMemory"); + abi::Instruction::FixedLengthListLowerToMemory { element, .. } => { + let Block { + body, + results: _, + element: block_element, + base, + } = self.blocks.pop().unwrap(); + let arr_src = &operands[0]; + let size_str = self.r#gen.sizes.size(element).format("size_t.sizeof"); + + self.push_str(&format!( + "foreach ({block_element}_idx, const ref {block_element}; {arr_src}) {{\n" + )); + self.push_str(&format!( + "const auto {base} = {arr_src} + {block_element}_idx * {size_str};\n" + )); + self.push_str(&body); + self.push_str("\n}\n"); } - abi::Instruction::FixedLengthListLiftFromMemory { .. } => { - todo!("instr: FixedLengthListLiftFromMemory"); + abi::Instruction::FixedLengthListLiftFromMemory { id, element, .. } => { + let Block { + body, + results: block_results, + element: block_element, + base, + } = self.blocks.pop().unwrap(); + let arr_src = &operands[0]; + let type_name = self.r#gen.type_name(&Type::Id(*id), self.r#gen.fqn); + let size_str = self.r#gen.sizes.size(element).format("size_t.sizeof"); + + let result = tempname("_arr", self.tmp()); + self.push_str(&format!("{type_name} {result} = void;\n")); + + self.push_str(&format!( + "foreach ({block_element}_idx, ref {block_element}; {result}) {{\n" + )); + self.push_str(&format!( + "const auto {base} = {arr_src} + {block_element}_idx * {size_str};\n" + )); + self.push_str(&body); + self.push_str(&format!("{block_element} = {};", block_results[0])); + self.push_str("\n}\n"); + + results.push(result); } abi::Instruction::IterElem { .. } => { From 2de8895b03f2457947507242fb7e7fad170c9cea Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 3 Apr 2026 15:35:13 -0700 Subject: [PATCH 13/55] Bitcasts, fixed export _retArea, make sure async fails. --- crates/d/src/lib.rs | 134 +++++++++++++++++++++++++++++++------- crates/d/src/wit_common.d | 8 +++ 2 files changed, 120 insertions(+), 22 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 868e800cb..4eba9bbd0 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -6,7 +6,7 @@ use std::mem::{replace, take}; use std::path::PathBuf; use wit_bindgen_core::{ Direction, Files, InterfaceGenerator, Source, Types, WorldGenerator, - abi::{self, Bindgen, WasmType}, + abi::{self, Bindgen, Bitcast, WasmType}, wit_parser::*, }; @@ -322,6 +322,9 @@ impl D { direction, wasm_import_module, + + return_pointer_area_size: Default::default(), + return_pointer_area_align: Default::default(), } } @@ -598,6 +601,9 @@ impl WorldGenerator for D { } } + let ret_area_decl = r#gen.emit_ret_area_if_needed(); + + r#gen.src.push_str(&ret_area_decl); r#gen.src.push_str("}\n"); let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); @@ -618,10 +624,19 @@ impl WorldGenerator for D { ) -> Result<()> { let mut r#gen = self.interface(resolve, Some(Direction::Export), None, Some("$root")); for (_name, func) in funcs { - r#gen.export_func(func); + match func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { + r#gen.export_func(func); + } + _ => {} + } } + let ret_area_decl = r#gen.emit_ret_area_if_needed(); + self.function_exports_src = take(&mut r#gen.src); + self.function_exports_src.push_str(&ret_area_decl); + Ok(()) } @@ -720,6 +735,9 @@ struct DInterfaceGenerator<'a> { fqn: &'a str, sizes: SizeAlign, + + return_pointer_area_size: ArchitectureSize, + return_pointer_area_align: Alignment, } impl<'a> DInterfaceGenerator<'a> { @@ -1095,11 +1113,15 @@ impl<'a> DInterfaceGenerator<'a> { fn export_func(&mut self, func: &Function) { match &func.kind { FunctionKind::Freestanding => {} + FunctionKind::Constructor(_) => { + todo!("Export FunctionKind::Constructor - {}\n", func.name); + } FunctionKind::Method(_) => {} + FunctionKind::Static(_) => { + todo!("Export FunctionKind::Static - {}\n", func.name); + } kind => { - self.src - .push_str(&format!("// TODO: Export {kind:?} - {}\n", func.name)); - return; + todo!("Export {kind:?} - {}\n", func.name); } } @@ -1205,14 +1227,37 @@ impl<'a> DInterfaceGenerator<'a> { &mut f, false, ); + let ret_area_decl = f.emit_ret_area_if_needed(); - let FunctionBindgen { src, .. } = f; + let FunctionBindgen { + src, + return_pointer_area_size, + return_pointer_area_align, + .. + } = f; + self.return_pointer_area_size = self.return_pointer_area_size.max(return_pointer_area_size); + self.return_pointer_area_align = self + .return_pointer_area_align + .max(return_pointer_area_align); + self.src.push_str(&ret_area_decl); self.src.push_str(&src.to_string()); self.src.push_str("}\n"); } + + fn emit_ret_area_if_needed(&self) -> String { + if !self.return_pointer_area_size.is_empty() { + format!( + "\nalign({}) private void[{}] _exportsRetArea;\n", + self.return_pointer_area_align.format("size_t.sizeof"), + self.return_pointer_area_size.format("size_t.sizeof") + ) + } else { + String::new() + } + } } impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { @@ -1894,7 +1939,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { fn store(&mut self, ty: &str, offset: ArchitectureSize, operands: &[String]) { self.push_str(&format!( - "*(cast({ty}*)({} + {})) = cast({ty})({});\n", + "*cast({ty}*)({} + {}) = cast({ty})({});\n", operands[1], offset.format("size_t.sizeof"), operands[0] @@ -1915,22 +1960,58 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { /// The scratch space is reused but never accessed simultaneously. fn emit_ret_area_if_needed(&self) -> String { if !self.return_pointer_area_size.is_empty() { - format!( - "{}align({}) void[{}] _retArea = void;\n", - if self.r#gen.direction == Some(Direction::Export) { - "static " - } else { - "" - }, - self.return_pointer_area_align.format("size_t.sizeof"), - self.return_pointer_area_size.format("size_t.sizeof") - ) + match self.r#gen.direction { + Some(Direction::Import) => format!( + "align({}) void[{}] _retArea = void;\n", + self.return_pointer_area_align.format("size_t.sizeof"), + self.return_pointer_area_size.format("size_t.sizeof") + ), + Some(Direction::Export) => "alias _retArea = _exportsRetArea;\n".to_string(), + None => { + unreachable!(); + } + } } else { String::new() } } } +fn perform_cast(op: &str, cast: &Bitcast) -> String { + match cast { + Bitcast::I32ToF32 | Bitcast::I64ToF32 => { + format!("cast(uint)({op}).reinterpretCast!float") + } + Bitcast::F32ToI32 | Bitcast::F32ToI64 => { + format!("({op}).reinterpretCast!uint") + } + Bitcast::I64ToF64 => { + format!("({op}).reinterpretCast!double") + } + Bitcast::F64ToI64 => { + format!("({op}).reinterpretCast!ulong") + } + Bitcast::I32ToI64 | Bitcast::LToI64 | Bitcast::PToP64 => { + format!("cast(ulong)({op})") + } + Bitcast::I64ToI32 | Bitcast::PToI32 | Bitcast::LToI32 => { + format!("cast(uint)({op})") + } + Bitcast::P64ToI64 | Bitcast::None | Bitcast::I64ToP64 => op.to_string(), + Bitcast::P64ToP | Bitcast::I32ToP | Bitcast::LToP => { + format!("cast(void*)({op})") + } + Bitcast::PToL | Bitcast::I32ToL | Bitcast::I64ToL => { + format!("cast(size_t)({op})") + } + Bitcast::Sequence(sequence) => { + let [first, second] = &**sequence; + let inner = perform_cast(op, first); + perform_cast(&inner, second) + } + } +} + impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { type Operand = String; @@ -1942,7 +2023,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results: &mut Vec, ) { let mut top_as = |cvt: &str| { - results.push(format!("(cast({cvt})({}))", operands.pop().unwrap())); + results.push(format!("cast({cvt})({})", operands.pop().unwrap())); }; match inst { @@ -1955,6 +2036,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::I32Const { val } => results.push(val.to_string()), + abi::Instruction::Bitcasts { casts } => { + for (cast, op) in casts.iter().zip(operands) { + let op = perform_cast(op, cast); + results.push(op); + } + } abi::Instruction::ConstZero { tys } => { for ty in tys.iter() { results.push( @@ -2027,7 +2114,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::CharFromI32 => top_as("dchar"), abi::Instruction::F32FromCoreF32 => results.push(operands.pop().unwrap()), abi::Instruction::F64FromCoreF64 => results.push(operands.pop().unwrap()), - abi::Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])), + abi::Instruction::BoolFromI32 => results.push(format!("({}) != 0", operands[0])), abi::Instruction::ListCanonLower { .. } | abi::Instruction::StringLower { .. } => { results.push(format!("cast(void*)({}.ptr)", operands[0])); @@ -2059,7 +2146,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { "foreach ({block_element}_idx, const ref {block_element}; {list_src}) {{\n" )); self.push_str(&format!( - "auto {base} = {list} + {block_element}_idx * {size_str};\n" + "auto {base} = {list} + {block_element}_idx * ({size_str});\n" )); self.push_str(&body); //self.push_str(&format!("_targetElem = {};", body.1[0])); @@ -2681,10 +2768,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { todo!("instr: Malloc") } abi::Instruction::GuestDeallocate { .. } => { - todo!("instr: GuestDeallocate") + self.push_str(&format!("free({});", operands[0])); } abi::Instruction::GuestDeallocateString { .. } => { - todo!("instr: GuestDeallocateString") + todo!("instr: GuestDeallocateString"); + //self.push_str(&format!("if (({}) > 0) {{\n", operands[1])); + //self.push_str(&format!("free({});", operands[0])); + //self.push_str("}\n"); } abi::Instruction::GuestDeallocateList { .. } => { todo!("instr: GuestDeallocateList") diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index d9b0cdb6a..155e8c408 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -231,11 +231,19 @@ public: package(wit): + extern(C) { void* malloc(size_t size); void free(void* ptr); } +// from numem.casting +pragma(inline, true) +auto ref T reinterpretCast(T, U)(auto ref U from) @trusted if (T.sizeof == U.sizeof) { + union tmp { U from; T to; } + return tmp(from).to; +} + auto mallocSlice(T)(size_t count) { auto ptr = malloc(count*T.sizeof); if (ptr is null) return null; From fb8528534a3779fc0f2742c815595a13130ad623 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 8 Apr 2026 00:43:29 -0700 Subject: [PATCH 14/55] Import and export static methods --- crates/d/src/lib.rs | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 4eba9bbd0..aa22f7f57 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -946,10 +946,9 @@ impl<'a> DInterfaceGenerator<'a> { fn get_d_signature(&mut self, func: &Function) -> DSig { match &func.kind { - FunctionKind::Freestanding | FunctionKind::Method(_) => {} + FunctionKind::Freestanding | FunctionKind::Method(_) | FunctionKind::Static(_) => {} FunctionKind::AsyncFreestanding - | FunctionKind::Static(_) | FunctionKind::Constructor(_) | FunctionKind::AsyncMethod(_) | FunctionKind::AsyncStatic(_) => { @@ -972,6 +971,10 @@ impl<'a> DInterfaceGenerator<'a> { let escaped_name = escape_d_identifier(&lower_name); res.name = escaped_name.into(); + res.static_member = match &func.kind { + FunctionKind::Static(_) => true, + _ => false, + }; res.result .push_str(&(self.optional_type_name(func.result.as_ref(), self.fqn))); @@ -1008,11 +1011,17 @@ impl<'a> DInterfaceGenerator<'a> { fn import_func(&mut self, func: &Function) { match &func.kind { FunctionKind::Freestanding => {} + FunctionKind::Constructor(_) => { + self.src.push_str(&format!( + "// TODO: Import FunctionKind::Constructor - {}\n", + func.name + )); + return; + } FunctionKind::Method(_) => {} + FunctionKind::Static(_) => {} kind => { - self.src - .push_str(&format!("// TODO: Import {kind:?} - {}\n", func.name)); - return; + todo!("Import {kind:?} - {}\n", func.name); } } @@ -1114,12 +1123,14 @@ impl<'a> DInterfaceGenerator<'a> { match &func.kind { FunctionKind::Freestanding => {} FunctionKind::Constructor(_) => { - todo!("Export FunctionKind::Constructor - {}\n", func.name); + self.src.push_str(&format!( + "// TODO: Export FunctionKind::Constructor - {}\n", + func.name + )); + return; } FunctionKind::Method(_) => {} - FunctionKind::Static(_) => { - todo!("Export FunctionKind::Static - {}\n", func.name); - } + FunctionKind::Static(_) => {} kind => { todo!("Export {kind:?} - {}\n", func.name); } @@ -1389,7 +1400,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } self.src - .push_str("void drop() {\n__import__drop(__handle);\n}\n"); + .push_str("\nvoid drop() {\n__import__drop(__handle);\n}\n"); self.src.push_str(&format!( "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", From 3bff02103b7aad27bba6c6007aee611a3ecbb2f3 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 8 Apr 2026 15:22:14 -0700 Subject: [PATCH 15/55] Add export stub generation --- crates/d/src/lib.rs | 94 +++++++++++++++++++++++++++++-- crates/test/src/d.rs | 131 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 crates/test/src/d.rs diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index aa22f7f57..0452a5a36 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -26,12 +26,14 @@ struct DSig { #[derive(Default)] struct D { used_interfaces: HashSet<(WorldKey, InterfaceId)>, + export_stubs: Vec, interface_imports: Vec, interface_exports: Vec, type_imports_src: Source, function_imports_src: Source, function_exports_src: Source, + export_stubs_src: Source, opts: Opts, @@ -57,6 +59,11 @@ pub struct Opts { /// Where to place output files #[cfg_attr(feature = "clap", arg(skip))] out_dir: Option, + + #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] + /// Whether stubs/declarations for exports should be emitted + /// Only for testing purposes. + emit_export_stubs: bool, } impl Opts { @@ -313,6 +320,8 @@ impl D { DInterfaceGenerator { src: Source::default(), + stub_src: Source::default(), + stubs: Vec::default(), fqn: "", r#gen: self, resolve, @@ -487,7 +496,10 @@ impl WorldGenerator for D { types: &[(&str, TypeId)], _files: &mut Files, ) { + let fqn = self.world_fqn.clone(); let mut r#gen = self.interface(resolve, Some(Direction::Import), None, Some("$root")); + r#gen.fqn = &fqn; + for (name, id) in types.iter() { r#gen.define_type(name, *id); } @@ -502,7 +514,10 @@ impl WorldGenerator for D { funcs: &[(&str, &Function)], _files: &mut Files, ) { + let fqn = self.world_fqn.clone(); let mut r#gen = self.interface(resolve, Some(Direction::Import), None, Some("$root")); + r#gen.fqn = &fqn; + for (_name, func) in funcs { r#gen.import_func(func); } @@ -604,12 +619,31 @@ impl WorldGenerator for D { let ret_area_decl = r#gen.emit_ret_area_if_needed(); r#gen.src.push_str(&ret_area_decl); - r#gen.src.push_str("}\n"); + r#gen.src.push_str("}\n\n"); + + let DInterfaceGenerator { + mut src, + stub_src, + stubs, + .. + } = r#gen; + + if self.opts.emit_export_stubs { + src.append_src(&stub_src); + + src.push_str("alias STUBS = AliasSeq!(\n"); + src.indent(1); + src.push_str(&stubs.join(",\n")); + src.deindent(1); + src.push_str("\n);\n"); + + self.export_stubs.push(format!("{fqn}.STUBS")); + } let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); interface_filepath.add_extension("d"); - files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); + files.push(interface_filepath.to_str().unwrap(), src.as_bytes()); self.cur_interface = None; Ok(()) @@ -622,7 +656,10 @@ impl WorldGenerator for D { funcs: &[(&str, &Function)], _files: &mut Files, ) -> Result<()> { + let fqn = self.world_fqn.clone(); let mut r#gen = self.interface(resolve, Some(Direction::Export), None, Some("$root")); + r#gen.fqn = &fqn; + for (_name, func) in funcs { match func.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { @@ -634,9 +671,21 @@ impl WorldGenerator for D { let ret_area_decl = r#gen.emit_ret_area_if_needed(); - self.function_exports_src = take(&mut r#gen.src); + let DInterfaceGenerator { + src, + stub_src, + mut stubs, + .. + } = r#gen; + + self.function_exports_src = src; self.function_exports_src.push_str(&ret_area_decl); + if self.opts.emit_export_stubs { + self.export_stubs_src.append_src(&stub_src); + self.export_stubs.append(&mut stubs); + } + Ok(()) } @@ -714,6 +763,20 @@ impl WorldGenerator for D { world_src.push_str(&self.function_exports_src.as_str()); world_src.push_str("}\n"); + if self.opts.emit_export_stubs { + self.export_stubs_src.push_str("alias STUBS = AliasSeq!(\n"); + self.export_stubs_src.indent(1); + self.export_stubs_src + .push_str(&self.export_stubs.join(",\n")); + self.export_stubs_src.deindent(1); + self.export_stubs_src.push_str("\n);\n"); + + self.export_stubs_src + .push_str("alias Exports_STUB_INVOKE = Exports!(STUBS);\n"); + + world_src.append_src(&self.export_stubs_src); + } + let mut world_filepath = PathBuf::from_iter(get_world_fqn(world_id, resolve).split(".")); world_filepath.push("package.d"); @@ -726,6 +789,8 @@ impl WorldGenerator for D { struct DInterfaceGenerator<'a> { src: Source, + stub_src: Source, + stubs: Vec, direction: Option, r#gen: &'a mut D, resolve: &'a Resolve, @@ -1190,6 +1255,24 @@ impl<'a> DInterfaceGenerator<'a> { } )); + if self.r#gen.opts.emit_export_stubs { + self.stub_src.push_str(&format!( + "@witExport(\"{}\", \"{}\")\n{} {}_STUB({});\n", + self.wasm_import_module.unwrap(), + func.name, + d_sig.result, + d_sig.name, + d_sig + .arguments + .iter() + .map(|(name, ty)| "in ".to_owned() + ty + " " + name) + .collect::>() + .join(", ") + )); + + self.stubs.push(d_sig.name.clone() + "_STUB"); + } + self.src.push_str("/// ditto\n"); self.src.push_str(&format!( "@wasmExport!(\"{}#{}\")\n", @@ -1991,7 +2074,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { fn perform_cast(op: &str, cast: &Bitcast) -> String { match cast { Bitcast::I32ToF32 | Bitcast::I64ToF32 => { - format!("cast(uint)({op}).reinterpretCast!float") + format!("(cast(uint){op}).reinterpretCast!float") } Bitcast::F32ToI32 | Bitcast::F32ToI64 => { format!("({op}).reinterpretCast!uint") @@ -2260,13 +2343,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { base, } = self.blocks.pop().unwrap(); let arr_src = &operands[0]; + let arr_dst = &operands[1]; let size_str = self.r#gen.sizes.size(element).format("size_t.sizeof"); self.push_str(&format!( "foreach ({block_element}_idx, const ref {block_element}; {arr_src}) {{\n" )); self.push_str(&format!( - "const auto {base} = {arr_src} + {block_element}_idx * {size_str};\n" + "const auto {base} = {arr_dst} + {block_element}_idx * {size_str};\n" )); self.push_str(&body); self.push_str("\n}\n"); diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs new file mode 100644 index 000000000..333875d4c --- /dev/null +++ b/crates/test/src/d.rs @@ -0,0 +1,131 @@ +use crate::config::StringList; +use crate::{Compile, LanguageMethods, Runner, Verify}; +use anyhow::{Context, Result}; +use clap::Parser; +use heck::ToSnakeCase; +use serde::Deserialize; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +#[derive(Default, Debug, Clone, Parser)] +pub struct DOpts {} + +pub struct D; + +fn ldc2(runner: &Runner) -> PathBuf { + format!("ldc2").into() +} + +impl LanguageMethods for D { + fn display(&self) -> &str { + "d" + } + + fn comment_prefix_for_test_config(&self) -> Option<&str> { + Some("//@") + } + + fn should_fail_verify( + &self, + name: &str, + config: &crate::config::WitConfig, + _args: &[String], + ) -> bool { + config.async_ || config.error_context + } + + fn default_bindgen_args_for_codegen(&self) -> &[&str] { + &["--emit-export-stubs"] + } + + fn prepare(&self, runner: &mut Runner) -> Result<()> { + prepare(runner, ldc2(runner)) + } + + fn compile(&self, runner: &Runner, c: &Compile<'_>) -> Result<()> { + compile(runner, c, ldc2(runner)) + } + + fn verify(&self, runner: &Runner, v: &Verify<'_>) -> Result<()> { + verify(runner, v, ldc2(runner)) + } +} + +fn prepare(runner: &mut Runner, compiler: PathBuf) -> Result<()> { + let cwd = env::current_dir()?; + let dir = cwd.join(&runner.opts.artifacts).join("d"); + + super::write_if_different(&dir.join("test.d"), "extern(C) void _start() {}")?; + + println!("Testing if `{}` works...", compiler.display()); + runner + .run_command( + Command::new(&compiler) + .current_dir(&dir) + .arg("-mtriple=wasm32-unknown-unknown") + .arg("-betterC") + .arg("test.d"), + ) + .inspect_err(|_| { + eprintln!("Error: failed to find `{}`.", compiler.display()); + })?; + + Ok(()) +} + +fn search_for_world_package(bindings_root: &Path) -> Option { + // Look for a package.d generated from a world nested at wit/*/*/*/package.d + + // TODO: If we had access to the full package+version of the world being + // generated, we wouldn't need to search. + + // ./wit/* + fs::read_dir(bindings_root.join("wit")) + .ok()? + .flatten() + .map(|e| e.path()) + .filter(|p| p.is_dir()) + // ./wit/*/* + .filter_map(|p| fs::read_dir(p).ok()) + .flatten() + .flatten() + .map(|e| e.path()) + .filter(|p| p.is_dir()) + // ./wit/*/*/* + .filter_map(|p| fs::read_dir(p).ok()) + .flatten() + .flatten() + .map(|e| e.path()) + .filter(|p| p.is_dir()) + // ./wit/*/*/*/package.d + .filter_map(|p| fs::read_dir(p).ok()) + .flatten() + .flatten() + .map(|e| e.path()) + .find(|p| p.is_file() && p.file_name().unwrap() == "package.d") +} + +fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result<()> { + todo!(); +} + +fn verify(runner: &Runner, verify: &Verify<'_>, compiler: PathBuf) -> Result<()> { + let mut cmd = Command::new(compiler); + + let world_path = search_for_world_package(verify.bindings_dir).unwrap(); + + cmd.arg(world_path) + .arg("-betterC") + .arg("-mtriple=wasm32-unknown-unknown") + .arg("-I") + .arg(&verify.bindings_dir) + .arg("-i") // compile included dependencies + .arg("-c") // compile only + .arg("--de") // deperecations are errors + .arg("-w") // warnigns are errors + .arg("-of") + .arg(verify.artifacts_dir.join("tmp.o")); + runner.run_command(&mut cmd) +} From 91681441fe8a7a5de9f29273abd7f899cb4b8b7a Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 8 Apr 2026 22:36:36 -0700 Subject: [PATCH 16/55] Implement resource exports + stubs --- crates/d/src/lib.rs | 79 ++++++++++++++++++--------------------- crates/d/src/wit_common.d | 49 ++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 45 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 0452a5a36..64201229b 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -541,12 +541,15 @@ impl WorldGenerator for D { self.interface_exports.push(fqn.clone()); let wasm_import_module = resolve.name_world_key(name); + let emit_exports_stubs = self.opts.emit_export_stubs; + let mut r#gen = self.interface( resolve, Some(Direction::Export), Some(name), Some(&wasm_import_module), ); + r#gen.fqn = &fqn; r#gen.interface = Some(id); r#gen.prologue(); @@ -596,6 +599,16 @@ impl WorldGenerator for D { wasm_import_module, type_name )); + if emit_exports_stubs { + r#gen.stub_src.push_str(&format!( + "@witExport(\"{}\", \"{}\")\nstruct {escaped_name}_STUB {{\n", + wasm_import_module, + ty.name.as_ref().unwrap() + )); + + r#gen.stubs.push(escaped_name.to_owned() + "_STUB"); + } + for (_, func) in &resolve.interfaces[id].functions { match func.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {} @@ -611,6 +624,10 @@ impl WorldGenerator for D { } } r#gen.src.push_str("}\n"); + + if emit_exports_stubs { + r#gen.stub_src.push_str("}\n"); + } } _ => {} } @@ -697,6 +714,7 @@ impl WorldGenerator for D { let wasm_import_module = resolve.name_world_key(&name); let mut r#gen = self.interface(resolve, None, Some(&name), Some(&wasm_import_module)); + r#gen.fqn = &fqn; r#gen.interface = Some(id); r#gen.prologue(); r#gen.types(id); @@ -941,8 +959,6 @@ impl<'a> DInterfaceGenerator<'a> { fn prologue(&mut self) { let id = self.interface.unwrap(); - let fqn = self.r#gen.lookup_interface_fqn(id, self.direction).unwrap(); - let interface = &self.resolve.interfaces[self.interface.unwrap()]; self.src.push_str(&format!( @@ -950,7 +966,7 @@ impl<'a> DInterfaceGenerator<'a> { interface.docs.contents.as_deref().unwrap_or_default() )); - self.src.push_str(&format!("module {};\n\n", fqn)); + self.src.push_str(&format!("module {};\n\n", self.fqn)); self.src.push_str("import wit.common;\n\n"); if self.direction.is_some() @@ -1226,14 +1242,9 @@ impl<'a> DInterfaceGenerator<'a> { )); self.src.push_str(&format!( - "alias {}_Sig = {} {}({});\n", + "alias {}_Sig = {} function({});\n", d_sig.name, d_sig.result, - if d_sig.implicit_self { - "delegate" - } else { - "function" - }, d_sig .arguments .iter() @@ -1243,10 +1254,11 @@ impl<'a> DInterfaceGenerator<'a> { )); self.src.push_str(&format!( - "/// ditto\nalias {}_Impl = findWitExportFunc!(\"{}\", \"{}\", {0}_Sig, {});\n", + "/// ditto\nalias {}_Impl = findWitExportFunc!(\"{}\", \"{}\", {0}_Sig, {}, {});\n", d_sig.name, self.wasm_import_module.unwrap(), func.name, + d_sig.implicit_self, match &func.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => "Impl", _ => { @@ -1257,9 +1269,15 @@ impl<'a> DInterfaceGenerator<'a> { if self.r#gen.opts.emit_export_stubs { self.stub_src.push_str(&format!( - "@witExport(\"{}\", \"{}\")\n{} {}_STUB({});\n", + "@witExport(\"{}\", \"{}\")\n", self.wasm_import_module.unwrap(), - func.name, + func.name + )); + if d_sig.static_member { + self.stub_src.push_str("static "); + } + self.stub_src.push_str(&format!( + "{} {}_STUB({});\n", d_sig.result, d_sig.name, d_sig @@ -1270,7 +1288,12 @@ impl<'a> DInterfaceGenerator<'a> { .join(", ") )); - self.stubs.push(d_sig.name.clone() + "_STUB"); + match func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { + self.stubs.push(d_sig.name.clone() + "_STUB"); + } + _ => {} + } } self.src.push_str("/// ditto\n"); @@ -1590,20 +1613,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { " )); - /*for (_, func) in &self.resolve.interfaces[owner_id].functions { - if match &func.kind { - FunctionKind::Freestanding => false, - FunctionKind::Method(_) => false, - FunctionKind::Static(mid) => *mid == id, - FunctionKind::Constructor(mid) => *mid == id, - FunctionKind::AsyncFreestanding => false, - FunctionKind::AsyncMethod(_) => false, - FunctionKind::AsyncStatic(_) => todo!(), - } { - self.import_func(func); - } - }*/ - self.src.push_str(&format!( "struct Borrow {{ package(wit) void* __ptr = null; @@ -1617,20 +1626,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { " )); - /*for (_, func) in &self.resolve.interfaces[owner_id].functions { - if match &func.kind { - FunctionKind::Freestanding => false, - FunctionKind::Method(mid) => *mid == id, - FunctionKind::Static(_) => false, - FunctionKind::Constructor(_) => false, - FunctionKind::AsyncFreestanding => false, - FunctionKind::AsyncMethod(_) => todo!(), - FunctionKind::AsyncStatic(_) => false, - } { - self.import_func(func); - } - }*/ - self.src.push_str("}\n"); self.src.push_str("}\n"); @@ -2829,7 +2824,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } FunctionKind::Method(_) | FunctionKind::AsyncMethod(_) => { self.src.push_str(&format!( - "__traits(child, cast(_Resource_Impl*)self, {escaped_name})(" + "__traits(child, cast(_Resource_Impl*)self, {escaped_name}_Impl)(" )); true } diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 155e8c408..8e39147b3 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -255,13 +255,13 @@ auto mallocSlice(T)(size_t count) { alias AliasSeq(T...) = T; -template findWitExportFunc(string mod, string name, Sig, Impl...) { +template findWitExportFunc(string mod, string name, Sig, bool implicitSelf, Impl...) { static foreach(Func; Impl) { static foreach(uda; __traits(getAttributes, Func)) { static if (!is(uda) && is(typeof(uda) == witExport) && uda == witExport(mod, name)) { static assert( !is(Func) && - (is(typeof(Func) == function) || is(typeof(Func) == delegate)), + (is(typeof(Func) == function)), "The implementation of '", mod, "#", name, "' ", "`", __traits(fullyQualifiedName, findWitExportFunc), "` ", "must be a function or method." @@ -284,7 +284,7 @@ template findWitExportFunc(string mod, string name, Sig, Impl...) { ); static assert( - is(typeof(&findWitExportFunc) : Sig), + is(typeof(&findWitExportFunc) : Sig) && __traits(isStaticFunction, findWitExportFunc) != implicitSelf, "The implementation of '", mod, "#", name, "' ", "`", __traits(fullyQualifiedName, findWitExportFunc), "` ", "must conform to the necessary signature. ", @@ -292,3 +292,46 @@ template findWitExportFunc(string mod, string name, Sig, Impl...) { ", but expected `", Sig, "`" ); } + +template findWitExportResource(string mod, string name, Impl...) { + static foreach(Resource; Impl) { + static foreach(uda; __traits(getAttributes, Resource)) { + static if (!is(uda) && is(typeof(uda) == witExport) && uda == witExport(mod, name)) { + static assert( + is(Resource == struct), + "The implementation of '", mod, "#", name, "' ", + "`", __traits(fullyQualifiedName, findWitExportResource), "` ", + "must be a struct." + ); + + static assert( + !is(typeof(findWitExportResource) == void) || __traits(isSame, findWitExportResource, Resource), + "There must be only one implementation of '", mod, "#", name, "'. ", + "Found at least `", __traits(fullyQualifiedName, findWitExportResource), + "` and `", __traits(fullyQualifiedName, Resource), "`." + ); + alias findWitExportResource = Resource; + } + } + } + + static assert( + !is(typeof(findWitExportResource) == void), + "Could not find implementation for '", mod, "#", name, "'" + ); +} + + +template witExportsIn(T) { + alias witExportsIn = AliasSeq!(); + + static foreach(M; __traits(allMembers, T)) { + static foreach(Export; __traits(getOverloads, T, M)) { + static foreach(uda; __traits(getAttributes, Export)) { + static if (!is(uda) && is(typeof(uda) == witExport)) { + witExportsIn = AliasSeq!(witExportsIn, Export); + } + } + } + } +} From 69cb904db8decfa7651688e988c69ccf260039f3 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 9 Apr 2026 00:48:09 -0700 Subject: [PATCH 17/55] cabi_post_* and cabi_realloc --- crates/d/src/lib.rs | 146 ++++++++++++++++++++++++++++++++------ crates/d/src/wit_common.d | 10 +++ 2 files changed, 135 insertions(+), 21 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 64201229b..b639e2f57 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1057,6 +1057,9 @@ impl<'a> DInterfaceGenerator<'a> { _ => false, }; + res.post_return = self.direction == Some(Direction::Export) + && abi::guest_export_needs_post_return(self.resolve, func); + res.result .push_str(&(self.optional_type_name(func.result.as_ref(), self.fqn))); @@ -1296,23 +1299,21 @@ impl<'a> DInterfaceGenerator<'a> { } } + let core_module_name = self.name.map(|s| self.resolve.name_world_key(s)); + let export_name = func.legacy_core_export_name(core_module_name.as_deref()); + self.src.push_str("/// ditto\n"); - self.src.push_str(&format!( - "@wasmExport!(\"{}#{}\")\n", - self.wasm_import_module.unwrap(), - func.name - )); + self.src + .push_str(&format!("@wasmExport!(\"{export_name}\")\n")); self.src.push_str(&format!( - "pragma(mangle, \"__wit_export_{}__{}\")\n", - self.wasm_import_module - .unwrap() + "pragma(mangle, \"__wit_export_{}\")\n", + export_name .replace("/", "__") - .replace("-", "_"), - func.name .replace("-", "_") .replace("[", ":") .replace("]", ":") + .replace("#", "::") )); if d_sig.implicit_self || d_sig.static_member { @@ -1362,6 +1363,68 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str(&src.to_string()); self.src.push_str("}\n"); + + if abi::guest_export_needs_post_return(self.resolve, func) { + let mut param_data = Vec::new(); + let mut params = Vec::<&str>::new(); + + for (arg, _ty) in wasm_sig.results.iter().enumerate() { + param_data.push(format!("arg{arg}")); + } + for param in ¶m_data { + params.push(¶m); + } + + self.src + .push_str(&format!("@wasmExport!(\"cabi_post_{export_name}\")\n")); + + self.src.push_str(&format!( + "pragma(mangle, \"__wit_cabi_post_{}\")\n", + export_name + .replace("/", "__") + .replace("-", "_") + .replace("[", ":") + .replace("]", ":") + .replace("#", "::") + )); + + if d_sig.implicit_self || d_sig.static_member { + self.src.push_str("static "); + } + self.src.push_str(&format!( + "private extern(C) void __cabi_post_{}({}) {{\n", + d_sig.name, + wasm_sig + .results + .iter() + .zip(¶ms) + .map(|(ty, name)| format!("{} {name}", wasm_type(*ty))) + .collect::>() + .join(", ") + )); + + let mut f = FunctionBindgen::new(self, ¶ms); + abi::post_return(f.r#gen.resolve, func, &mut f); + + let ret_area_decl = f.emit_ret_area_if_needed(); + + let FunctionBindgen { + src, + return_pointer_area_size, + return_pointer_area_align, + .. + } = f; + self.return_pointer_area_size = + self.return_pointer_area_size.max(return_pointer_area_size); + self.return_pointer_area_align = self + .return_pointer_area_align + .max(return_pointer_area_align); + + self.src.push_str(&ret_area_decl); + self.src.push_str(&src.to_string()); + + self.src.push_str("}\n"); + } } fn emit_ret_area_if_needed(&self) -> String { @@ -1615,10 +1678,10 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "struct Borrow {{ - package(wit) void* __ptr = null; + package(wit) uint __handle = 0; - package(wit) this(void* ptr) {{ - __ptr = ptr; + package(wit) this(uint handle) {{ + __handle = handle; }} @disable this(); @@ -2861,16 +2924,57 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("free({});", operands[0])); } abi::Instruction::GuestDeallocateString { .. } => { - todo!("instr: GuestDeallocateString"); - //self.push_str(&format!("if (({}) > 0) {{\n", operands[1])); - //self.push_str(&format!("free({});", operands[0])); - //self.push_str("}\n"); + self.push_str(&format!("if ({} > 0) {{\n", operands[1])); + self.push_str(&format!("free({});\n", operands[0])); + self.push_str("}\n"); } - abi::Instruction::GuestDeallocateList { .. } => { - todo!("instr: GuestDeallocateList") + abi::Instruction::GuestDeallocateList { element } => { + let Block { + body, + results: block_results, + element: block_element, + base, + } = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.r#gen.sizes.size(element); + let size_str = size.format("size_t.sizeof"); + + let list = tempname("_list", tmp); + let list_len = tempname("_listLen", tmp); + let list_src = tempname("_listSrcPtr", tmp); + self.push_str(&format!("auto {list_src} = {};\n", operands[0])); + self.push_str(&format!("auto {list_len} = {};\n", operands[1])); + + self.push_str(&format!( + "foreach ({block_element}_idx; 0..{list_len}) {{\n", + )); + self.push_str(&format!( + "const auto {base} = {list_src} + {block_element}_idx * {size_str};\n" + )); + self.push_str(&body); + self.push_str("\n}\n"); + + self.push_str(&format!("if ({} > 0) {{\n", operands[1])); + self.push_str(&format!("free({});\n", operands[0])); + self.push_str("}\n"); } - abi::Instruction::GuestDeallocateVariant { .. } => { - todo!("instr: GuestDeallocateVariant") + abi::Instruction::GuestDeallocateVariant { + blocks: block_count, + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - block_count..) + .collect::>(); + + self.push_str(&format!("switch ({}) {{\n", operands[0])); + for (i, block) in blocks.into_iter().enumerate() { + assert!(results.is_empty()); + + self.push_str(&format!("case {}: {{\n", i)); + self.src.push_str(&block.body); + self.src.push_str("break;\n}\n"); + } + self.src.push_str("default: break;\n}\n"); } abi::Instruction::DropHandle { .. } => { todo!("instr: DropHandle") diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 8e39147b3..0d43bc81b 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -234,7 +234,9 @@ package(wit): extern(C) { void* malloc(size_t size); +void* realloc(void* ptr, size_t newSIzew); void free(void* ptr); +noreturn abort(); } // from numem.casting @@ -335,3 +337,11 @@ template witExportsIn(T) { } } } + +@wasmExport!("cabi_realloc") +void* cabi_realloc(void *ptr, size_t oldSize, size_t alignment, size_t newSize) { + if (newSize == 0) return cast(void*)alignment; + void *ret = realloc(ptr, newSize); + if (!ret) abort(); + return ret; +} From e63e17a73f80cce55c88553dfff8d927093c2619 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 13 Apr 2026 16:22:01 -0700 Subject: [PATCH 18/55] Resource constructors [skip ci] --- crates/d/src/lib.rs | 79 +++++++++++++++++++++++++------------------- crates/test/src/d.rs | 2 +- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index b639e2f57..4a05f5629 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -211,9 +211,11 @@ fn escape_d_identifier(name: &str) -> &str { "WitFlags" => "WitFlags_", "Option" => "Option_", "Result" => "Result_", - "bits" => "bits_", // part of WitFlags - "borrow" => "borrow_", // part of the expansion of `resource` - "drop" => "drop_", // part of the expansion of `resource` + "bits" => "bits_", // part of WitFlags + "borrow" => "borrow_", // part of the expansion of `resource` + "drop" => "drop_", // part of the expansion of `resource` + "makeNew" => "makeNew_", // part of the expansion of `resource` + "constructor" => "constructor_", // part of the expansion of `resource` s => s, } @@ -893,15 +895,15 @@ impl<'a> DInterfaceGenerator<'a> { self.type_name(&ty, from_module_fqn) )), TypeDefKind::Future(_) => { - Cow::Borrowed("/* todo - type_name of `future` */") + todo!("type_name of `future`") } TypeDefKind::Stream(_) => { - Cow::Borrowed("/* todo - type_name of `stream` */") + todo!("type_name of `stream`") } TypeDefKind::FixedLengthList(ty, size) => { Cow::Owned(format!("{}[{size}]", self.type_name(ty, from_module_fqn))) } - TypeDefKind::Map(_, _) => todo!(), + TypeDefKind::Map(_, _) => todo!("type_name of `map`"), TypeDefKind::Unknown => unimplemented!(), unhandled => { panic!( @@ -1027,10 +1029,12 @@ impl<'a> DInterfaceGenerator<'a> { fn get_d_signature(&mut self, func: &Function) -> DSig { match &func.kind { - FunctionKind::Freestanding | FunctionKind::Method(_) | FunctionKind::Static(_) => {} + FunctionKind::Freestanding + | FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => {} FunctionKind::AsyncFreestanding - | FunctionKind::Constructor(_) | FunctionKind::AsyncMethod(_) | FunctionKind::AsyncStatic(_) => { todo!() @@ -1041,19 +1045,27 @@ impl<'a> DInterfaceGenerator<'a> { let split_name = match &func.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &func.name, + FunctionKind::Constructor(_) => "", FunctionKind::Method(_) | FunctionKind::Static(_) - | FunctionKind::Constructor(_) | FunctionKind::AsyncMethod(_) | FunctionKind::AsyncStatic(_) => func.name.split(".").skip(1).next().unwrap(), }; let lower_name = split_name.to_lower_camel_case(); - let escaped_name = escape_d_identifier(&lower_name); + let escaped_name = if let FunctionKind::Constructor(_) = &func.kind { + match self.direction { + Some(Direction::Import) => "makeNew", + _ => "constructor", + } + } else { + escape_d_identifier(&lower_name) + }; res.name = escaped_name.into(); res.static_member = match &func.kind { FunctionKind::Static(_) => true, + FunctionKind::Constructor(_) => true, _ => false, }; @@ -1094,16 +1106,10 @@ impl<'a> DInterfaceGenerator<'a> { fn import_func(&mut self, func: &Function) { match &func.kind { - FunctionKind::Freestanding => {} - FunctionKind::Constructor(_) => { - self.src.push_str(&format!( - "// TODO: Import FunctionKind::Constructor - {}\n", - func.name - )); - return; - } - FunctionKind::Method(_) => {} - FunctionKind::Static(_) => {} + FunctionKind::Freestanding + | FunctionKind::Constructor(_) + | FunctionKind::Method(_) + | FunctionKind::Static(_) => {} kind => { todo!("Import {kind:?} - {}\n", func.name); } @@ -1205,16 +1211,10 @@ impl<'a> DInterfaceGenerator<'a> { fn export_func(&mut self, func: &Function) { match &func.kind { - FunctionKind::Freestanding => {} - FunctionKind::Constructor(_) => { - self.src.push_str(&format!( - "// TODO: Export FunctionKind::Constructor - {}\n", - func.name - )); - return; - } - FunctionKind::Method(_) => {} - FunctionKind::Static(_) => {} + FunctionKind::Freestanding + | FunctionKind::Constructor(_) + | FunctionKind::Method(_) + | FunctionKind::Static(_) => {} kind => { todo!("Export {kind:?} - {}\n", func.name); } @@ -1699,7 +1699,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } }, } - //todo!("def of `resource`") } fn type_tuple(&mut self, id: TypeId, name: &str, tuple: &Tuple, docs: &Docs) { @@ -1994,6 +1993,10 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { )); } + fn type_map(&mut self, _id: TypeId, name: &str, _key: &Type, _value: &Type, _docs: &Docs) { + todo!("def of `map` - {name}"); + } + fn type_future(&mut self, _id: TypeId, name: &str, _ty: &Option, _docs: &Docs) { todo!("def of `future` - {name}"); } @@ -2839,7 +2842,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { }; let lower_name = split_name.to_lower_camel_case(); - let escaped_name = escape_d_identifier(&lower_name); + let escaped_name = if name.starts_with("[constructor]") { + "makeNew" + } else { + escape_d_identifier(&lower_name) + }; if !sig.results.is_empty() { self.src.push_str("auto _ret = "); @@ -2866,15 +2873,19 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let split_name = match &func.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &func.name, + FunctionKind::Constructor(_) => "", FunctionKind::Method(_) | FunctionKind::Static(_) - | FunctionKind::Constructor(_) | FunctionKind::AsyncMethod(_) | FunctionKind::AsyncStatic(_) => func.name.split(".").skip(1).next().unwrap(), }; let lower_name = split_name.to_lower_camel_case(); - let escaped_name = escape_d_identifier(&lower_name); + let escaped_name = if let FunctionKind::Constructor(_) = &func.kind { + "constructor" + } else { + escape_d_identifier(&lower_name) + }; let implicit_self = match &func.kind { FunctionKind::Freestanding diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index 333875d4c..dfde08313 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -33,7 +33,7 @@ impl LanguageMethods for D { config: &crate::config::WitConfig, _args: &[String], ) -> bool { - config.async_ || config.error_context + config.async_ || config.error_context || name == "map.wit" } fn default_bindgen_args_for_codegen(&self) -> &[&str] { From 77efc5c6e703c14827fcc963668c89457bc43b19 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 13 Apr 2026 19:41:42 -0700 Subject: [PATCH 19/55] Resource alloc/dealloc [skip ci] --- crates/d/src/lib.rs | 113 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 4a05f5629..a5579b65c 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -214,6 +214,7 @@ fn escape_d_identifier(name: &str) -> &str { "bits" => "bits_", // part of WitFlags "borrow" => "borrow_", // part of the expansion of `resource` "drop" => "drop_", // part of the expansion of `resource` + "rep" => "rep_", // part of the expansion of `resource` "makeNew" => "makeNew_", // part of the expansion of `resource` "constructor" => "constructor_", // part of the expansion of `resource` @@ -625,6 +626,25 @@ impl WorldGenerator for D { } } } + + r#gen.src.push_str(&format!( + "\n@wasmExport!(\"{}#[dtor]{}\")\n", + wasm_import_module, + ty.name.as_ref().unwrap() + )); + r#gen.src.push_str(&format!( + "pragma(mangle, \"__wit_export_{}__:dtor:{}\")\n", + wasm_import_module.replace("/", "__").replace("-", "_"), + ty.name.as_ref().unwrap().replace("-", "_") + )); + r#gen.src.push_str( + "static private extern(C) void __export_dtor(void* ptr) { + (*cast(_Resource_Impl*)ptr).destroy!false; + free(ptr); + } + ", + ); + r#gen.src.push_str("}\n"); if emit_exports_stubs { @@ -1518,12 +1538,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { @disable this(); - // TODO: make RAII? disable copy for the own - - - auto borrow() => Borrow(__handle); - alias borrow this; - " )); @@ -1569,17 +1583,12 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } self.src - .push_str("\nvoid drop() {\n__import__drop(__handle);\n}\n"); - + .push_str("\nvoid drop() {\n__import_drop(__handle);\n}\n"); self.src.push_str(&format!( "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", self.wasm_import_module.unwrap(), name )); - - // The mangle is not important, as long as it won't conflict with other symbols - // WebAssembly symbol identifiers are much more permissive than C (can be any UTF-8). - // Yet, LDC before 1.42 doesn't allow full use of this fact. We make some substitutions. self.src.push_str(&format!( "pragma(mangle, \"__wit_import_{}__:resource_drop:{}\")\n", self.wasm_import_module @@ -1589,10 +1598,15 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { name.replace("-", "_") )); self.src - .push_str("static private extern(C) void __import__drop(uint);\n\n"); + .push_str("static private extern(C) void __import_drop(uint);\n\n"); self.src.push_str(&format!( - "struct Borrow {{ + "// TODO: make RAII? disable copy for the own + + auto borrow() => Borrow(__handle); + alias borrow this; + + struct Borrow {{ package(wit) uint __handle = 0; package(wit) this(uint handle) {{ @@ -1600,7 +1614,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { }} @disable this(); - " )); @@ -1670,14 +1683,78 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { }} @disable this(); +" + )); - // TODO: make RAII? disable copy for the own + self.src.push_str(&format!( + " + static {escaped_name} makeNew(T)(scope void delegate(out T) dg) if (is(T == struct)) {{ + auto ptr = cast(T*)malloc(T.sizeof); + if (ptr is null) return {escaped_name}.init; + + dg(*ptr); + return {escaped_name}(__import_makeNew(ptr)); + }} + ", + )); + self.src.push_str(&format!( + "@wasmImport!(\"{}\", \"[resource-new]{}\")\n", + self.wasm_import_module.unwrap(), + name + )); + self.src.push_str(&format!( + "pragma(mangle, \"__wit_import_{}__:resource_new:{}\")\n", + self.wasm_import_module + .unwrap() + .replace("/", "__") + .replace("-", "_"), + name.replace("-", "_") + )); + self.src + .push_str("static private extern(C) uint __import_makeNew(void*);\n\n"); - " + self.src + .push_str("T* rep(T)() if (is(T == struct)) {\nreturn cast(T*)__import_rep(__handle);\n}\n"); + self.src.push_str(&format!( + "@wasmImport!(\"{}\", \"[resource-rep]{}\")\n", + self.wasm_import_module.unwrap(), + name )); + self.src.push_str(&format!( + "pragma(mangle, \"__wit_import_{}__:resource_rep:{}\")\n", + self.wasm_import_module + .unwrap() + .replace("/", "__") + .replace("-", "_"), + name.replace("-", "_") + )); + self.src + .push_str("static private extern(C) void __import_rep(uint);\n\n"); + + self.src + .push_str("void drop() {\n__import_drop(__handle);\n}\n"); + self.src.push_str(&format!( + "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", + self.wasm_import_module.unwrap(), + name + )); + self.src.push_str(&format!( + "pragma(mangle, \"__wit_import_{}__:resource_drop:{}\")\n", + self.wasm_import_module + .unwrap() + .replace("/", "__") + .replace("-", "_"), + name.replace("-", "_") + )); + self.src + .push_str("static private extern(C) void __import_drop(uint);\n\n"); self.src.push_str(&format!( - "struct Borrow {{ + "// TODO: make RAII? disable copy for the own + auto borrow() => Borrow(__handle); + alias borrow this; + + struct Borrow {{ package(wit) uint __handle = 0; package(wit) this(uint handle) {{ From 8eb52fc95ef4c435a45d21ff80bf6f54ade5ecdc Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 13 Apr 2026 21:41:30 -0700 Subject: [PATCH 20/55] GitHub CI integration --- .github/workflows/main.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3a86dcb49..d3cccc822 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -79,7 +79,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] # moonbit removed from language matrix for now - causing CI failures - lang: [c, rust, csharp, cpp, go] + lang: [c, rust, csharp, cpp, go, d] exclude: # For now csharp doesn't work on macos, so exclude it from testing. - os: macos-latest @@ -121,6 +121,12 @@ jobs: go-version: 1.25.4 if: matrix.lang == 'go' && matrix.os != 'ubuntu-latest' + - name: Setup D + uses: dlang-community/setup-dlang@v2 + with: + compiler: ldc2-1.41 + if: matrix.lang == 'd' + # Hacky work-around for https://github.com/dotnet/runtime/issues/80619 - run: dotnet new console -o /tmp/foo if: matrix.os != 'windows-latest' && matrix.lang == 'csharp' From 2788e0e0b32c382d2aecd0d2f15cfd115b3e8a39 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 13 Apr 2026 21:43:27 -0700 Subject: [PATCH 21/55] `ci/publish.rs` crate whitelist --- ci/publish.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/publish.rs b/ci/publish.rs index b5fec6431..4f2dc76e3 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -25,6 +25,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-bindgen-markdown", "wit-bindgen-moonbit", "wit-bindgen-go", + "wit-bindgen-d", "wit-bindgen-rust-macro", "wit-bindgen-rt", "wit-bindgen", From ba26baf5bab675306c247c1f42e2dddbb5da426e Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 13 Apr 2026 21:51:01 -0700 Subject: [PATCH 22/55] Fix warnings, fix CI integration --- .github/workflows/main.yml | 2 +- crates/d/src/lib.rs | 56 +++++++------------------------------- crates/test/src/d.rs | 9 ++---- 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d3cccc822..55356a56c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -124,7 +124,7 @@ jobs: - name: Setup D uses: dlang-community/setup-dlang@v2 with: - compiler: ldc2-1.41 + compiler: ldc-1.41 if: matrix.lang == 'd' # Hacky work-around for https://github.com/dotnet/runtime/issues/80619 diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index a5579b65c..9e9a94fdb 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -13,12 +13,10 @@ use wit_bindgen_core::{ type DType = String; #[derive(Default, Debug)] struct DSig { - const_member: bool, static_member: bool, result: DType, arguments: Vec<(String, DType)>, name: String, - //namespace: Vec, implicit_self: bool, post_return: bool, } @@ -1166,7 +1164,7 @@ impl<'a> DInterfaceGenerator<'a> { if d_sig.implicit_self { params.push("this"); } - for (arg, ty) in &d_sig.arguments { + for (arg, _ty) in &d_sig.arguments { params.push(arg); } @@ -1924,7 +1922,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { "/// ditto\nbool is{escaped_upper_case_name}() const => _tag == Tag.{escaped_lower_case_name};\n", )); - if let Some(ty) = &case.ty { + if case.ty.is_some() { self.src.push_str(&format!( "///ditto\nalias get{escaped_upper_case_name} = _get!(Tag.{escaped_lower_case_name});\n", )); @@ -2538,7 +2536,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::RecordLift { ty, record, .. } => { let name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - let mut tmpvar = tempname("_record", self.tmp()); + let tmpvar = tempname("_record", self.tmp()); self.push_str(&format!("{name} {tmpvar} = {{\n")); for (field, op) in record.fields.iter().zip(operands.iter()) { @@ -2566,7 +2564,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { results.push(format!("{}[{i}]", &operands[0])); } } - abi::Instruction::TupleLift { tuple, ty, .. } => { + abi::Instruction::TupleLift { ty, .. } => { let name = tempname("_tuple", self.tmp()); self.push_str(&format!( "auto {name} = {}(\n", @@ -2660,9 +2658,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("alias {tag_type} = {ty_name}.Tag;\n")); self.push_str(&format!("final switch ({}.tag) {{\n", operands[0])); - for (i, ((case, block), payload)) in - variant.cases.iter().zip(blocks).zip(payloads).enumerate() - { + for ((case, block), payload) in variant.cases.iter().zip(blocks).zip(payloads) { let lower_name = case.name.to_lower_camel_case(); let lower_escaped_name = escape_d_identifier(&lower_name); @@ -2705,7 +2701,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("alias {tag_type} = {ty}.Tag;\n")); self.push_str(&format!("final switch (cast({ty}.Tag){tag}) {{\n")); - for (i, (case, block)) in variant.cases.iter().zip(blocks).enumerate() { + for (case, block) in variant.cases.iter().zip(blocks) { let lower_name = case.name.to_lower_camel_case(); let escaped_name = escape_d_identifier(&lower_name); @@ -2777,12 +2773,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::OptionLift { ty, .. } => { let Block { - body: mut some, + body: some, results: some_results, .. } = self.blocks.pop().unwrap(); let Block { - body: mut none, results: none_results, .. } = self.blocks.pop().unwrap(); @@ -2866,13 +2861,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::ResultLift { result, ty, .. } => { let Block { - body: mut err, + body: err, results: err_results, .. } = self.blocks.pop().unwrap(); assert!(err_results.len() == (result.err.is_some() as usize)); let Block { - body: mut ok, + body: ok, results: ok_results, .. } = self.blocks.pop().unwrap(); @@ -3019,7 +3014,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::GuestDeallocateList { element } => { let Block { body, - results: block_results, + results: _, element: block_element, base, } = self.blocks.pop().unwrap(); @@ -3027,7 +3022,6 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let size = self.r#gen.sizes.size(element); let size_str = size.format("size_t.sizeof"); - let list = tempname("_list", tmp); let list_len = tempname("_listLen", tmp); let list_src = tempname("_listSrcPtr", tmp); self.push_str(&format!("auto {list_src} = {};\n", operands[0])); @@ -3122,33 +3116,3 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.r#gen.resolve.all_bits_valid(ty) } } - -/// This describes the common ABI function referenced or implemented, the C++ side might correspond to a different type -enum SpecialMethod { - None, - ResourceDrop, // ([export]) [resource-drop] - ResourceNew, // [export][resource-new] - ResourceRep, // [export][resource-rep] - Dtor, // [dtor] (guest export only) - Allocate, // internal: allocate new object (called from generated code) -} - -fn is_special_method(func: &Function) -> SpecialMethod { - if matches!(func.kind, FunctionKind::Static(_)) { - if func.name.starts_with("[resource-drop]") { - SpecialMethod::ResourceDrop - } else if func.name.starts_with("[resource-new]") { - SpecialMethod::ResourceNew - } else if func.name.starts_with("[resource-rep]") { - SpecialMethod::ResourceRep - } else if func.name.starts_with("[dtor]") { - SpecialMethod::Dtor - } else if func.name == "$alloc" { - SpecialMethod::Allocate - } else { - SpecialMethod::None - } - } else { - SpecialMethod::None - } -} diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index dfde08313..da52dac40 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -1,9 +1,6 @@ -use crate::config::StringList; use crate::{Compile, LanguageMethods, Runner, Verify}; -use anyhow::{Context, Result}; +use anyhow::Result; use clap::Parser; -use heck::ToSnakeCase; -use serde::Deserialize; use std::env; use std::fs; use std::path::{Path, PathBuf}; @@ -14,7 +11,7 @@ pub struct DOpts {} pub struct D; -fn ldc2(runner: &Runner) -> PathBuf { +fn ldc2(_runner: &Runner) -> PathBuf { format!("ldc2").into() } @@ -107,7 +104,7 @@ fn search_for_world_package(bindings_root: &Path) -> Option { .find(|p| p.is_file() && p.file_name().unwrap() == "package.d") } -fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result<()> { +fn compile(_runner: &Runner, _compile: &Compile<'_>, _compiler: PathBuf) -> Result<()> { todo!(); } From 28418f6a0a2f360fd38273e2560122e45042587b Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 13 Apr 2026 21:56:28 -0700 Subject: [PATCH 23/55] Replace `add_extension` with `set_extension` --- crates/d/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 9e9a94fdb..3feb87c2a 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -478,7 +478,7 @@ impl WorldGenerator for D { } let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); - interface_filepath.add_extension("d"); + interface_filepath.set_extension("d"); files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); @@ -678,7 +678,7 @@ impl WorldGenerator for D { } let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); - interface_filepath.add_extension("d"); + interface_filepath.set_extension("d"); files.push(interface_filepath.to_str().unwrap(), src.as_bytes()); @@ -740,7 +740,7 @@ impl WorldGenerator for D { r#gen.types(id); let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); - interface_filepath.add_extension("d"); + interface_filepath.set_extension("d"); files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); } From 3040ce78967c8f7a795f028d48d17c10273d2600 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 13 Apr 2026 23:16:44 -0700 Subject: [PATCH 24/55] Fix clippy warnings --- crates/d/src/lib.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 3feb87c2a..17fb176fc 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -596,8 +596,7 @@ impl WorldGenerator for D { .push_str(&format!("/// ditto\nstruct {escaped_name}_Wrappers {{\n")); r#gen.src.push_str(&format!( - "alias _Resource_Impl = findWitExportResource!(\"{}\", \"{}\", Impl);\n", - wasm_import_module, type_name + "alias _Resource_Impl = findWitExportResource!(\"{wasm_import_module}\", \"{type_name}\", Impl);\n" )); if emit_exports_stubs { @@ -1028,11 +1027,10 @@ impl<'a> DInterfaceGenerator<'a> { } } } else { - self.src - .push_str(&format!("static import {};\n", common_fqn)); + self.src.push_str(&format!("static import {common_fqn};\n")); if let Some(fqn) = directional_fqn { - self.src.push_str(&format!("static import {};\n", fqn)); + self.src.push_str(&format!("static import {fqn};\n")); }; } } @@ -1181,7 +1179,7 @@ impl<'a> DInterfaceGenerator<'a> { let FunctionBindgen { src, .. } = f; self.src.push_str(&ret_area_decl); - self.src.push_str(&src.to_string()); + self.src.push_str(&src); self.src.push_str("}\n"); @@ -1378,7 +1376,7 @@ impl<'a> DInterfaceGenerator<'a> { .max(return_pointer_area_align); self.src.push_str(&ret_area_decl); - self.src.push_str(&src.to_string()); + self.src.push_str(&src); self.src.push_str("}\n"); @@ -1439,7 +1437,7 @@ impl<'a> DInterfaceGenerator<'a> { .max(return_pointer_area_align); self.src.push_str(&ret_area_decl); - self.src.push_str(&src.to_string()); + self.src.push_str(&src); self.src.push_str("}\n"); } @@ -2401,7 +2399,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { operands[0], operands[1] )); - results.push(format!("{}({ptr}[0..{len}])", list_name)); + results.push(format!("{list_name}({ptr}[0..{len}])")); } abi::Instruction::StringLift => { let tmp = self.tmp(); @@ -2926,11 +2924,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } self.push_str(&format!( "__import_{escaped_name}({});\n", - operands - .iter() - .map(|op| { op.clone() }) - .collect::>() - .join(", ") + operands.iter().cloned().collect::>().join(", ") )); } abi::Instruction::CallInterface { func, async_ } => { @@ -2979,7 +2973,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { &operands .iter() .skip(if implicit_self { 1 } else { 0 }) - .map(|op| op.clone()) + .cloned() .collect::>() .join(", "), ); @@ -3052,7 +3046,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { for (i, block) in blocks.into_iter().enumerate() { assert!(results.is_empty()); - self.push_str(&format!("case {}: {{\n", i)); + self.push_str(&format!("case {i}: {{\n")); self.src.push_str(&block.body); self.src.push_str("break;\n}\n"); } @@ -3065,7 +3059,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { abi::Instruction::Flush { amt } => { for op in operands.iter().take(*amt) { let result = tempname("_flush", self.tmp()); - self.push_str(&format!("auto {result} = {};\n", op)); + self.push_str(&format!("auto {result} = {op};\n")); results.push(result); } } From c61ff229dd22b1ac493fdc69c7db1ef7a8de5c84 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 14 Apr 2026 12:23:57 -0700 Subject: [PATCH 25/55] Don't use `in` on simple parameters --- crates/d/src/lib.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 17fb176fc..09a22e0b5 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1111,9 +1111,18 @@ impl<'a> DInterfaceGenerator<'a> { let lower_param_name = name.to_lower_camel_case(); let escaped_param_name = escape_d_identifier(&lower_param_name); + let needs_in_qualifier = match param { + Type::ErrorContext | Type::String | Type::Id(_) => true, + _ => false, + }; + res.arguments.push(( escaped_param_name.into(), - self.type_name(¶m, self.fqn).into(), + if needs_in_qualifier { + "in ".to_owned() + } else { + "".to_owned() + } + &self.type_name(¶m, self.fqn), )); } @@ -1152,7 +1161,7 @@ impl<'a> DInterfaceGenerator<'a> { d_sig .arguments .iter() - .map(|(name, ty)| "in ".to_owned() + ty + " " + name) + .map(|(name, ty)| ty.to_owned() + " " + name) .collect::>() .join(", ") )); @@ -1267,7 +1276,7 @@ impl<'a> DInterfaceGenerator<'a> { d_sig .arguments .iter() - .map(|(name, ty)| "in ".to_owned() + ty + " " + name) + .map(|(name, ty)| ty.to_owned() + " " + name) .collect::>() .join(", ") )); @@ -1302,7 +1311,7 @@ impl<'a> DInterfaceGenerator<'a> { d_sig .arguments .iter() - .map(|(name, ty)| "in ".to_owned() + ty + " " + name) + .map(|(name, ty)| ty.to_owned() + " " + name) .collect::>() .join(", ") )); From 76ff89edff2feeb025897970e9dc936283d5f7f9 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 14 Apr 2026 12:30:44 -0700 Subject: [PATCH 26/55] Begin work on runtime tests --- crates/d/src/wit_common.d | 53 ++++++++++++++++++++++++++++++--- crates/test/src/d.rs | 30 +++++++++++++++++-- crates/test/src/lib.rs | 1 + tests/runtime/versions/runner.d | 18 +++++++++++ tests/runtime/versions/test.d | 21 +++++++++++++ 5 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 tests/runtime/versions/runner.d create mode 100644 tests/runtime/versions/test.d diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 0d43bc81b..cb631d58f 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -233,10 +233,55 @@ public: package(wit): extern(C) { -void* malloc(size_t size); -void* realloc(void* ptr, size_t newSIzew); -void free(void* ptr); -noreturn abort(); +version (WitBindings_DummyLibc) { + extern __gshared ubyte __heap_base; + private __gshared void* heapTail = &__heap_base; + + // basic bump allocator + // based on `malloc0` sans the ability to free + void* malloc(size_t size) { + import ldc.intrinsics : llvm_wasm_memory_grow, llvm_wasm_memory_size; + size = (size + 7) & ~7; // align up to 8 bytes + + void* allocStart = heapTail; + void* allocEnd = allocStart+size; + + // Pages in Wasm are 64KiB (65536) + size_t memSizePages = llvm_wasm_memory_size(0); + size_t memSizeBytes = memSizePages << 16; + + if (cast(size_t)allocEnd > memSizeBytes) { + if (llvm_wasm_memory_grow(0, (cast(size_t)allocEnd >> 16)-memSizePages + 1) == -1) abort(); + } + + void* ret = allocStart; + heapTail = allocEnd; + return ret; + } + + void* realloc(void* ptr, size_t newSize) { + // can't actual realloc; only handles use as conditional malloc/free + if (ptr) abort(); + + if (newSize == 0) return null; + + return malloc(newSize); + } + + // no ability to free + void free(void* ptr) {} + + noreturn abort() { + import ldc.intrinsics : llvm_trap; + llvm_trap(); + while(true) {} + } +} else { + void* malloc(size_t size); + void* realloc(void* ptr, size_t newSize); + void free(void* ptr); + noreturn abort(); +} } // from numem.casting diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index da52dac40..31166b4b5 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -1,5 +1,5 @@ use crate::{Compile, LanguageMethods, Runner, Verify}; -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Parser; use std::env; use std::fs; @@ -104,8 +104,32 @@ fn search_for_world_package(bindings_root: &Path) -> Option { .find(|p| p.is_file() && p.file_name().unwrap() == "package.d") } -fn compile(_runner: &Runner, _compile: &Compile<'_>, _compiler: PathBuf) -> Result<()> { - todo!(); +fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result<()> { + let mut cmd = Command::new(compiler); + + let output = compile.output.with_extension("core.wasm"); + cmd.arg(&compile.component.path) + .arg("-betterC") + .arg("-mtriple=wasm32-unknown-unknown") + .arg("-I") + .arg(&compile.bindings_dir) + .arg("-i") // compile included dependencies + .arg("--de") // deperecations are errors + .arg("-w") // warnings are errors + .arg("-L--no-entry") + .arg("-L--no-export-dynamic") + .arg("--d-version=WitBindings_DummyLibc") // to provide bump allocator and `abort` + .arg("--checkaction=halt") // to trap instead of using libc __assert + .arg("-of") + .arg(&output); + + runner.run_command(&mut cmd)?; + + runner + .convert_p1_to_component(&output, compile) + .with_context(|| format!("failed to convert {output:?}"))?; + + Ok(()) } fn verify(runner: &Runner, verify: &Verify<'_>, compiler: PathBuf) -> Result<()> { diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index b7e89553e..de496275b 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -453,6 +453,7 @@ impl Runner { "cs" => Language::Csharp, "mbt" => Language::MoonBit, "go" => Language::Go, + "d" => Language::D, other => Language::Custom(custom::Language::lookup(self, other)?), }; diff --git a/tests/runtime/versions/runner.d b/tests/runtime/versions/runner.d new file mode 100644 index 000000000..7b62cf5ee --- /dev/null +++ b/tests/runtime/versions/runner.d @@ -0,0 +1,18 @@ +import wit.test.versions.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + import v1 = wit.test.dep_0_1_0.test.imports; + + assert(v1.x() == 1.0); + assert(v1.y(1.0) == 2.0); + + import v2 = wit.test.dep_0_2_0.test.imports; + assert(v2.x() == 2.0); + assert(v2.z(1.0, 1.0) == 4.0); +} + +alias Exports = wit.test.versions.runner.Exports!( + run +); diff --git a/tests/runtime/versions/test.d b/tests/runtime/versions/test.d new file mode 100644 index 000000000..19879bd68 --- /dev/null +++ b/tests/runtime/versions/test.d @@ -0,0 +1,21 @@ +import wit.test.versions.test; +import wit.common; + +@witExport("test:dep/test@0.1.0", "x") +float x_v1() => 1.0; + +@witExport("test:dep/test@0.1.0", "y") +float y(float a) => 1.0 + a; + +@witExport("test:dep/test@0.2.0", "x") +float x_v2() => 2.0; + +@witExport("test:dep/test@0.2.0", "z") +float z(float a, float b) => 2.0 + a + b; + +alias Exports = wit.test.versions.test.Exports!( + x_v1, + y, + x_v2, + z +); From 3ed314bf6404b4fc84e36c80290b7175fb9cc038 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 14 May 2026 01:03:04 -0700 Subject: [PATCH 27/55] Add free and clone utilities for WIT types. Add some runtime tests. --- crates/d/src/lib.rs | 98 ++++++++++++++++++++ crates/d/src/wit_common.d | 104 ++++++++++++++++++++++ tests/runtime/common-types/leaf.d | 24 +++++ tests/runtime/common-types/middle.d | 19 ++++ tests/runtime/common-types/runner.d | 21 +++++ tests/runtime/demo/runner.d | 11 +++ tests/runtime/demo/test.d | 10 +++ tests/runtime/fixed-length-lists/runner.d | 68 ++++++++++++++ tests/runtime/fixed-length-lists/test.d | 63 +++++++++++++ tests/runtime/flavorful/runner.d | 86 ++++++++++++++++++ 10 files changed, 504 insertions(+) create mode 100644 tests/runtime/common-types/leaf.d create mode 100644 tests/runtime/common-types/middle.d create mode 100644 tests/runtime/common-types/runner.d create mode 100644 tests/runtime/demo/runner.d create mode 100644 tests/runtime/demo/test.d create mode 100644 tests/runtime/fixed-length-lists/runner.d create mode 100644 tests/runtime/fixed-length-lists/test.d create mode 100644 tests/runtime/flavorful/runner.d diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 09a22e0b5..ce6004706 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1463,6 +1463,27 @@ impl<'a> DInterfaceGenerator<'a> { String::new() } } + + fn needs_wit_free(&self, ty: Type) -> bool { + match ty { + Type::String => true, + Type::Id(id) => { + let typeinfo = &self.r#gen.types.get(id); + typeinfo.has_list || typeinfo.has_resource + } + _ => false, + } + } + + fn can_have_wit_clone(&self, ty: Type) -> bool { + match ty { + Type::Id(id) => { + let typeinfo = &self.r#gen.types.get(id); + !typeinfo.has_own_handle + } + _ => true, + } + } } impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { @@ -1516,6 +1537,34 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { )); } + self.src.push_str("\nvoid witFree() {\n"); + for field in &record.fields { + let lower_name = field.name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + if self.needs_wit_free(field.ty) { + self.src.push_str(&format!("{escaped_name}.witFree;\n")); + } + } + self.src.push_str("}\n"); + + if self.can_have_wit_clone(Type::Id(id)) { + self.src + .push_str(&format!("\n{escaped_name} witClone() const {{\n")); + self.src + .push_str(&format!("{escaped_name} clone = void;\n")); + for field in &record.fields { + let lower_name = field.name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + self.src.push_str(&format!( + "clone.{escaped_name} = this.{escaped_name}.witClone;\n" + )); + } + self.src.push_str("return clone;\n"); + self.src.push_str("}\n"); + } + self.src.push_str("}\n"); } @@ -1604,6 +1653,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { )); self.src .push_str("static private extern(C) void __import_drop(uint);\n\n"); + self.src.push_str("alias witFree = drop;\n"); self.src.push_str(&format!( "// TODO: make RAII? disable copy for the own @@ -1619,6 +1669,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { }} @disable this(); + + void witFree() {{}} + Borrow witClone() const {{ return Borrow(__handle); }} " )); @@ -1753,6 +1806,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { )); self.src .push_str("static private extern(C) void __import_drop(uint);\n\n"); + self.src.push_str("alias witFree = drop;\n"); self.src.push_str(&format!( "// TODO: make RAII? disable copy for the own @@ -1768,6 +1822,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { @disable this(); + void witFree() {{}} + Borrow witClone() const {{ return Borrow(__handle); }} + " )); @@ -1935,6 +1992,47 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { )); } } + + self.src.push_str("\nvoid witFree() {\n"); + if self.needs_wit_free(Type::Id(id)) { + self.src.push_str("switch (_tag) with (Tag) {\n"); + for case in &variant.cases { + let lower_case_name = case.name.to_lower_camel_case(); + let escaped_lower_case_name = escape_d_identifier(&lower_case_name); + + if case.ty.is_some() && self.needs_wit_free(case.ty.unwrap()) { + self.src.push_str(&format!( + "case {escaped_lower_case_name}: _get!(Tag.{escaped_lower_case_name}).witFree; break;\n", + )); + } + } + self.src.push_str("default: break;\n"); + self.src.push_str("}\n"); + } + self.src.push_str("}\n"); + + if self.can_have_wit_clone(Type::Id(id)) { + self.src + .push_str(&format!("\n{escaped_name} witClone() const {{\n")); + self.src.push_str("final switch (_tag) {\n"); + for case in &variant.cases { + let lower_case_name = case.name.to_lower_camel_case(); + let escaped_lower_case_name = escape_d_identifier(&lower_case_name); + + if case.ty.is_some() { + self.src.push_str(&format!( + "case Tag.{escaped_lower_case_name}: return _create!(Tag.{escaped_lower_case_name})(this._get!(Tag.{escaped_lower_case_name}).witClone); break;\n", + )); + } else { + self.src.push_str(&format!( + "case Tag.{escaped_lower_case_name}: return _create!(Tag.{escaped_lower_case_name}); break;\n", + )); + } + } + self.src.push_str("}\n"); + self.src.push_str("}\n"); + } + self.src.push_str("}\n"); } diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index cb631d58f..0d4e69e3d 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -31,6 +31,9 @@ struct WitList(T) { inout(T)[] asSlice() @trusted inout { return (ptr && length) ? ptr[0..length] : null; } + + bool opEquals(in T[] other) const => this[] == other; + size_t toHash() const => this[].hashOf; } auto witList(T : U[], U)(T slice) => WitList!U(slice); @@ -75,6 +78,8 @@ mixin template WitFlags(T) if (__traits(isUnsigned, T)) { result.opOpAssign!op(flags); return result; } + + typeof(this) witClone() const { return this; } } @@ -163,6 +168,14 @@ public: { return _present ? _value : fallback(); } } +auto some(T)(T value) @safe @nogc nothrow { + return Option!T.some(value); +} + +auto none(T)(T value) @safe @nogc nothrow { + return Option!T.some(value); +} + /// Based on Rust's Result @mustuse struct Result(T, E) { @@ -230,6 +243,83 @@ public: } +void witFree(T)(scope ref T val) if (__traits(isArithmetic, T)) { + // no-op +} +T witClone(T)(in T val) if (__traits(isArithmetic, T)) { + return val; +} + +void witFree(T : Option!U, U)(scope ref T val) { + static if (!is(U == void)) if (val.isSome) val.unwrap.witFree; +} +T witClone(T : Option!U, U)(in T val) { + if (val.isSome) { + static if (!is(U == void)) { + return T.some(val.unwrap.witClone); + } else { + return T.some; + } + } else { + return T.none; + } +} + +void witFree(T : Result!(U, V), U, V)(scope ref T val) { + if (val.isErr) { + static if (!is(V == void)) val.unwrapErr.witFree; + } else { + static if (!is(U == void)) val.unwrap.witFree; + } +} +T witClone(T : Result!(U, V), U, V)(in T val) { + if (val.isErr) { + static if (!is(V == void)) { + return T.err(val.unwrapErr.witClone); + } else { + return T.err; + } + } else { + static if (!is(U == void)) { + return T.ok(val.unwrap.witClone); + } else { + return T.ok; + } + } +} + +void witFree(T : WitList!U, U)(scope ref T val) { + foreach (ref e; val) { + e.witFree; + } + if (val.ptr && val.length) free(val.ptr); + val = null; +} +T witClone(T : WitList!U, U)(in T val) { + if (val.ptr == null || val.length == 0) return T(null); + + auto clone = mallocSlice!U(val.length); + + foreach (i, ref e; clone) { + e = val[i].witClone; + } + + return clone.witList; +} + +void witFree(T : Tuple!U, U...)(scope ref T val) { + static foreach (F; T.tupleof) { + __traits(child, val, F).witFree; + } +} +T witClone(T : Tuple!U, U...)(in T val) { + T clone; + static foreach (F; T.tupleof) { + __traits(child, clone, F) = __traits(child, val, F).witClone; + } + return clone; +} + package(wit): extern(C) { @@ -276,6 +366,20 @@ version (WitBindings_DummyLibc) { llvm_trap(); while(true) {} } + + private int memcmp(const void* ptr1, const void* ptr2, size_t size) + { + auto data1 = cast(const(ubyte)*)ptr1; + auto data2 = cast(const(ubyte)*)ptr2; + + foreach (i; 0..size) { + auto b1 = data1[i]; + auto b2 = data2[i]; + if (b1 != b2) return b1-b2; + } + + return 0; + } } else { void* malloc(size_t size); void* realloc(void* ptr, size_t newSize); diff --git a/tests/runtime/common-types/leaf.d b/tests/runtime/common-types/leaf.d new file mode 100644 index 000000000..2e387263c --- /dev/null +++ b/tests/runtime/common-types/leaf.d @@ -0,0 +1,24 @@ +import wit.test.common.leaf; +import wit.common; + +@witExport("test:common/to-test", "wrap") +R1 wrap(in F1 flag) { + switch (flag.bits) with (F1) { + case a.bits: + return R1(1, flag); + case b.bits: + return R1(2, flag); + default: + assert(0); + } +} + +@witExport("test:common/to-test", "var-f") +V1 varF() { + return V1.b(42); +} + +alias Exports = wit.test.common.leaf.Exports!( + wrap, + varF +); diff --git a/tests/runtime/common-types/middle.d b/tests/runtime/common-types/middle.d new file mode 100644 index 000000000..5d000f754 --- /dev/null +++ b/tests/runtime/common-types/middle.d @@ -0,0 +1,19 @@ +import wit.test.common.middle; +import wit.common; + +import imps = wit.test.common.to_test.imports; + +@witExport("test:common/to-test", "wrap") +R1 wrap(in F1 flag) { + return imps.wrap(flag); +} + +@witExport("test:common/to-test", "var-f") +V1 varF() { + return imps.varF; +} + +alias Exports = wit.test.common.middle.Exports!( + wrap, + varF +); diff --git a/tests/runtime/common-types/runner.d b/tests/runtime/common-types/runner.d new file mode 100644 index 000000000..b94bb5f66 --- /dev/null +++ b/tests/runtime/common-types/runner.d @@ -0,0 +1,21 @@ +import wit.test.common.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + R1 res = wrap(F1.a); + assert(res.b == F1.a); + assert(res.a == 1); + + R1 res2 = wrap(F1.b); + assert(res2.b == F1.b); + assert(res2.a == 2); + + V1 res3 = varF(); + assert(res3.isB); + assert(res3.getB == 42); +} + +alias Exports = wit.test.common.runner.Exports!( + run +); diff --git a/tests/runtime/demo/runner.d b/tests/runtime/demo/runner.d new file mode 100644 index 000000000..848742806 --- /dev/null +++ b/tests/runtime/demo/runner.d @@ -0,0 +1,11 @@ +import wit.a.b.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + x(); +} + +alias Exports = wit.a.b.runner.Exports!( + run +); diff --git a/tests/runtime/demo/test.d b/tests/runtime/demo/test.d new file mode 100644 index 000000000..647d2e3fa --- /dev/null +++ b/tests/runtime/demo/test.d @@ -0,0 +1,10 @@ +import wit.a.b.test; +import wit.common; + +@witExport("a:b/the-test", "x") +void x() { +} + +alias Exports = wit.a.b.test.Exports!( + x +); diff --git a/tests/runtime/fixed-length-lists/runner.d b/tests/runtime/fixed-length-lists/runner.d new file mode 100644 index 000000000..0b9ee34de --- /dev/null +++ b/tests/runtime/fixed-length-lists/runner.d @@ -0,0 +1,68 @@ +//@ wasmtime-flags = '-Wcomponent-model-fixed-length-lists' + +import wit.test.fixed_length_lists.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + listParam([1, 2, 3, 4]); + listParam2([[1, 2], [3, 4]]); + listParam3([ + -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16, -17, 18, -19, 20, + ]); + { + auto result = listResult(); + assert(result == ['0', '1', 'A', 'B', 'a', 'b', 128, 255]); + } + { + auto result = listMinmax16([0, 1024, 32768, 65535], [1, 2048, -32767, -2]); + assert(result == Tuple!(ushort[4], short[4])([0, 1024, 32768, 65535], [1, 2048, -32767, -2])); + } + { + auto result = listMinmaxFloat([2.0, -42.0], [0.25, -0.125]); + assert(result == Tuple!(float[2], float[2])([2.0, -42.0], [0.25, -0.125])); + } + { + auto result = listRoundtrip(['a', 'b', 'c', 'd', 0, 1, 2, 3, 'A', 'B', 'Y', 'Z']); + assert(result == ['a', 'b', 'c', 'd', 0, 1, 2, 3, 'A', 'B', 'Y', 'Z']); + } + { + auto result = nestedRoundtrip([[1, 5], [42, 1_000_000]], [[-1, 3], [-2_000_000, 4711]]); + assert( + result == + Tuple!(uint[2][2], int[2][2])([[1, 5], [42, 1_000_000]], [[-1, 3], [-2_000_000, 4711]]) + ); + } + { + auto result = largeRoundtrip( + [[1, 5], [42, 1_000_000]], + [ + [-1, 3, -2, 4], + [-2_000_000, 4711, 99_999, -5], + [-6, 7, 8, -9], + [50, -5, 500, -5000], + ], + ); + assert( + result == + Tuple!(uint[2][2], int[4][4])( + [[1, 5], [42, 1_000_000]], + [ + [-1, 3, -2, 4], + [-2_000_000, 4711, 99_999, -5], + [-6, 7, 8, -9], + [50, -5, 500, -5000] + ] + ) + ); + } + { + auto result = nightmareOnCpp([Nested(l: [1, -1]), Nested(l: [2, -2])]); + assert(result[0].l == [1, -1]); + assert(result[1].l == [2, -2]); + } +} + +alias Exports = wit.test.fixed_length_lists.runner.Exports!( + run +); diff --git a/tests/runtime/fixed-length-lists/test.d b/tests/runtime/fixed-length-lists/test.d new file mode 100644 index 000000000..253310987 --- /dev/null +++ b/tests/runtime/fixed-length-lists/test.d @@ -0,0 +1,63 @@ +import wit.test.fixed_length_lists.test; +import wit.common; + +@witExport("test:fixed-length-lists/to-test", "list-param") +void listParam(in uint[4] a) { + assert(a == [1, 2, 3, 4]); +} + +@witExport("test:fixed-length-lists/to-test", "list-param2") +void listParam2(in uint[2][2] a) { + enum uint[2][2] v = [[1, 2], [3, 4]]; + assert(a == v); +} + +@witExport("test:fixed-length-lists/to-test", "list-param3") +void listParam3(in int[20] a) { + assert(a == [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16, -17, 18, -19, 20]); +} + +@witExport("test:fixed-length-lists/to-test", "list-minmax16") +Tuple!(ushort[4], short[4]) listMinmax16(in ushort[4] a, in short[4] b) { + return Tuple!(ushort[4], short[4])(a, b); +} + + +@witExport("test:fixed-length-lists/to-test", "list-minmax-float") +Tuple!(float[2], double[2]) listMinmaxFloat(in float[2] a, in double[2] b) { + return Tuple!(float[2], double[2])(a, b); +} + +@witExport("test:fixed-length-lists/to-test", "list-roundtrip") +ubyte[12] listRoundtrip(in ubyte[12] a) => a; + +@witExport("test:fixed-length-lists/to-test", "list-result") +ubyte[8] listResult() => ['0', '1', 'A', 'B', 'a', 'b', 128, 255]; + +@witExport("test:fixed-length-lists/to-test", "nested-roundtrip") +Tuple!(uint[2][2], int[2][2]) nestedRoundtrip(in uint[2][2] a, in int[2][2] b) { + return Tuple!(uint[2][2], int[2][2])(a, b); +} + +@witExport("test:fixed-length-lists/to-test", "large-roundtrip") +Tuple!(uint[2][2], int[4][4]) largeRoundtrip(in uint[2][2] a, in int[4][4] b) { + return Tuple!(uint[2][2], int[4][4])(a, b); +} + +@witExport("test:fixed-length-lists/to-test", "nightmare-on-cpp") +Nested[2] nightmareOnCpp(in Nested[2] a) { + return a; +} + +alias Exports = wit.test.fixed_length_lists.test.Exports!( + listParam, + listParam2, + listParam3, + listMinmax16, + listMinmaxFloat, + listRoundtrip, + listResult, + nestedRoundtrip, + largeRoundtrip, + nightmareOnCpp +); diff --git a/tests/runtime/flavorful/runner.d b/tests/runtime/flavorful/runner.d new file mode 100644 index 000000000..87efed369 --- /dev/null +++ b/tests/runtime/flavorful/runner.d @@ -0,0 +1,86 @@ +import wit.test.flavorful.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + fListInRecord1(ListInRecord1(a: cast(WitString)"list_in_record1".witList)); + + { + auto result = fListInRecord2(); + scope(exit) result.witFree; + + assert(result.a == "list_in_record2"); + } + + { + auto result = fListInRecord3(ListInRecord3(a: cast(WitString)"list_in_record3 input".witList)); + scope(exit) result.witFree; + + assert( + result.a + == "list_in_record3 output" + ); + } + + { + auto result = fListInRecord4(ListInAlias(a: cast(WitString)"input4".witList)); + scope(exit) result.witFree; + + assert( + result.a + == "result4" + ); + } + + fListInVariant1(some(cast(WitString)"foo".witList), Result!(void, WitString).err(cast(WitString)"bar".witList)); + + { + auto result = fListInVariant2(); + scope(exit) result.witFree; + + + assert( + result + == some(cast(WitString)"list_in_variant2".witList) + ); + } + + { + auto result = fListInVariant3(some(cast(WitString)"input3".witList)); + scope(exit) result.witFree; + + assert( + result + == some(cast(WitString)"output3".witList) + ); + } + + { + auto errno = errnoResult(); + assert(errno.isErr && errno.unwrapErr == MyErrno.b); + } + assert(errnoResult().isOk); + + { + WitString[1] input = [cast(WitString)"typedef2".witList]; + auto result = listTypedefs(cast(WitString)"typedef1".witList, input[].witList); + scope(exit) result.witFree; + + assert(result[0] == (cast(ubyte[])"typedef3").witList); + assert(result[1].length == 1); + assert(result[1][0] == "typedef4"); + } + + { + bool[2] input1 = [true, false]; + Result!(void, void)[2] input2 = [Result!(void, void).ok(), Result!(void, void).err()]; + MyErrno[2] input3 = [MyErrno.success, MyErrno.a]; + + auto result = listOfVariants(input1[].witList, input2[].witList, input3[].witList); + scope(exit) result.witFree; + } +} + +alias Exports = wit.test.flavorful.runner.Exports!( + run +); From 8f26e2761f5aa349d2aedf0762f299d19cd0f5a0 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 3 Jun 2026 01:09:59 -0700 Subject: [PATCH 28/55] Component type custom section --- crates/d/src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++-- crates/d/src/wit_common.d | 2 +- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index ce6004706..94590f47c 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -62,6 +62,11 @@ pub struct Opts { /// Whether stubs/declarations for exports should be emitted /// Only for testing purposes. emit_export_stubs: bool, + + /// Add the specified suffix to the name of the custome section containing + /// the component type. + #[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))] + pub type_section_suffix: Option, } impl Opts { @@ -814,6 +819,55 @@ impl WorldGenerator for D { world_src.append_src(&self.export_stubs_src); } + // Linker `component-type` section + { + let opts_suffix = self.opts.type_section_suffix.as_deref().unwrap_or(""); + let world = &resolve.worlds[world_id]; + let world_name = &world.name; + let pkg = &resolve.packages[world.package.unwrap()].name; + let version = env!("CARGO_PKG_VERSION"); + world_src.push_str(&format!( + "\n@(imported!\"ldc.attributes\".section(\"component-type:wit-bindgen:{version}:\ + {pkg}:{world_name}:{opts_suffix}\"))\n" + )); + + let mut producers = wasm_metadata::Producers::empty(); + producers.add( + "processed-by", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + ); + + let component_type = wit_component::metadata::encode( + resolve, + world_id, + wit_component::StringEncoding::UTF8, + Some(&producers), + ) + .unwrap(); + + world_src.push_str(&format!( + " + void __wit_bindgen_component_types() {{ + imported!\"ldc.llvmasm\".__irEx!( + \"\", + \"\", + `!wasm.custom_sections = !{{!0}} + !0 = !{{!\"component-type:wit-bindgen:{version}:{pkg}:{world_name}:{opts_suffix}\", !\"{}\"}}`, + void + ); + }} + ", + &component_type + .iter() + .map(|b| format!("\\{b:02X}")) + .enumerate() + .fold(String::default(), |a, (i, b)| { + if (i % 24) == 0 { a + "`\n~`" + &b } else { a + &b } + }) + )); + } + let mut world_filepath = PathBuf::from_iter(get_world_fqn(world_id, resolve).split(".")); world_filepath.push("package.d"); @@ -1658,7 +1712,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "// TODO: make RAII? disable copy for the own - auto borrow() => Borrow(__handle); + Borrow borrow() => Borrow(__handle); alias borrow this; struct Borrow {{ @@ -1810,7 +1864,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "// TODO: make RAII? disable copy for the own - auto borrow() => Borrow(__handle); + Borrow borrow() => Borrow(__handle); alias borrow this; struct Borrow {{ diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 0d4e69e3d..93daccc95 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -178,7 +178,7 @@ auto none(T)(T value) @safe @nogc nothrow { /// Based on Rust's Result @mustuse -struct Result(T, E) { +struct Result(T = void, E = void) { private: bool _hasError; union Storage { From 826d01d1ffb02899a95264c8e89af4afbd7fe44c Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 3 Jun 2026 16:11:15 -0700 Subject: [PATCH 29/55] `flavorful` test and cast removals --- crates/d/src/lib.rs | 6 +- crates/d/src/wit_common.d | 50 +++++++++------ crates/test/src/d.rs | 2 +- tests/runtime/flavorful/runner.d | 29 +++++---- tests/runtime/flavorful/test.d | 103 +++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 tests/runtime/flavorful/test.d diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 94590f47c..9abd739a6 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -826,10 +826,6 @@ impl WorldGenerator for D { let world_name = &world.name; let pkg = &resolve.packages[world.package.unwrap()].name; let version = env!("CARGO_PKG_VERSION"); - world_src.push_str(&format!( - "\n@(imported!\"ldc.attributes\".section(\"component-type:wit-bindgen:{version}:\ - {pkg}:{world_name}:{opts_suffix}\"))\n" - )); let mut producers = wasm_metadata::Producers::empty(); producers.add( @@ -848,7 +844,7 @@ impl WorldGenerator for D { world_src.push_str(&format!( " - void __wit_bindgen_component_types() {{ + void __wit_bindgen_component_type() {{ imported!\"ldc.llvmasm\".__irEx!( \"\", \"\", diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 93daccc95..d39f24dcc 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -18,8 +18,9 @@ struct WitList(T) { T* ptr; size_t length; - this(T[] slice) @trusted { - this = slice; + this(inout T[] slice) inout @trusted { + ptr = slice.ptr; + length = slice.length; } void opAssign(T[] slice) @trusted { @@ -35,8 +36,7 @@ struct WitList(T) { bool opEquals(in T[] other) const => this[] == other; size_t toHash() const => this[].hashOf; } -auto witList(T : U[], U)(T slice) => WitList!U(slice); - +auto witList(T : U[], U)(inout T slice) => inout WitList!U(slice); // WIT ABI for string matches List, // except list in WIT is actually List!(dchar) @@ -140,17 +140,17 @@ private: bool _present = false; T _value; - this(bool present, T value = T.init) @safe @nogc nothrow { + this(bool present, inout T value) inout @safe @nogc nothrow { _present = present; _value = value; } public: - static Option some(T value) @safe @nogc nothrow { - return Option(true, value); + static inout(Option) some(inout T value) @safe @nogc nothrow { + return inout Option(true, value); } static Option none() @safe @nogc nothrow { - return Option(false); + return Option(false, T.init); } bool isSome() const @safe @nogc nothrow => _present; @@ -168,12 +168,12 @@ public: { return _present ? _value : fallback(); } } -auto some(T)(T value) @safe @nogc nothrow { +auto some(T)(inout T value) @safe @nogc nothrow { return Option!T.some(value); } -auto none(T)(T value) @safe @nogc nothrow { - return Option!T.some(value); +auto none(T)() @safe @nogc nothrow { + return Option!T.none; } /// Based on Rust's Result @@ -192,7 +192,7 @@ private: } Storage _storage; - this(bool hasError, Storage storage) @safe @nogc nothrow { + this(bool hasError, inout(Storage) storage) inout @safe @nogc nothrow { _hasError = hasError; _storage = storage; } @@ -201,22 +201,22 @@ public: static if (is(T == void)) { static Result ok() @safe @nogc nothrow => Result(false, Storage.init); } else { - static Result ok(T value) @trusted @nogc nothrow { + static Result ok(inout(T) value) @trusted @nogc nothrow { Storage newStorage = Storage.init; - newStorage.value = value; + newStorage.value = cast(T)value; - return Result(false, newStorage); + return Result(false, cast(inout Storage)newStorage); } } static if (is(E == void)) { static Result err() @safe @nogc nothrow => Result(true, Storage.init); } else { - static Result err(E error) @trusted @nogc nothrow { + static inout(Result) err(inout(E) error) @trusted @nogc nothrow { Storage newStorage = Storage.init; - newStorage.error = error; + newStorage.error = cast(E)error; - return Result(true, newStorage); + return inout Result(true, cast(inout Storage)newStorage); } } @@ -320,6 +320,20 @@ T witClone(T : Tuple!U, U...)(in T val) { return clone; } + +void witFree(T : U[L], U, size_t L)(scope ref T val) { + foreach (ref e; val) { + e.witFree; + } +} +T witClone(T : U[L], U, size_t L)(in T val) { + T clone; + foreach (i, ref e; clone) { + e = val[i].witClone; + } + return clone; +} + package(wit): extern(C) { diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index 31166b4b5..ffb719ab4 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -111,13 +111,13 @@ fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result< cmd.arg(&compile.component.path) .arg("-betterC") .arg("-mtriple=wasm32-unknown-unknown") + .arg("-fvisibility=hidden") // important to make sure unused symbols don't get linked .arg("-I") .arg(&compile.bindings_dir) .arg("-i") // compile included dependencies .arg("--de") // deperecations are errors .arg("-w") // warnings are errors .arg("-L--no-entry") - .arg("-L--no-export-dynamic") .arg("--d-version=WitBindings_DummyLibc") // to provide bump allocator and `abort` .arg("--checkaction=halt") // to trap instead of using libc __assert .arg("-of") diff --git a/tests/runtime/flavorful/runner.d b/tests/runtime/flavorful/runner.d index 87efed369..decba03b1 100644 --- a/tests/runtime/flavorful/runner.d +++ b/tests/runtime/flavorful/runner.d @@ -13,7 +13,7 @@ void run() { } { - auto result = fListInRecord3(ListInRecord3(a: cast(WitString)"list_in_record3 input".witList)); + auto result = fListInRecord3(const ListInRecord3("list_in_record3 input".witList)); scope(exit) result.witFree; assert( @@ -23,7 +23,7 @@ void run() { } { - auto result = fListInRecord4(ListInAlias(a: cast(WitString)"input4".witList)); + auto result = fListInRecord4(const ListInAlias("input4".witList)); scope(exit) result.witFree; assert( @@ -32,7 +32,7 @@ void run() { ); } - fListInVariant1(some(cast(WitString)"foo".witList), Result!(void, WitString).err(cast(WitString)"bar".witList)); + fListInVariant1(some("foo".witList), Result!(void, WitString).err("bar".witList)); { auto result = fListInVariant2(); @@ -41,17 +41,17 @@ void run() { assert( result - == some(cast(WitString)"list_in_variant2".witList) + == some("list_in_variant2".witList) ); } { - auto result = fListInVariant3(some(cast(WitString)"input3".witList)); + auto result = fListInVariant3(some("input3".witList)); scope(exit) result.witFree; assert( result - == some(cast(WitString)"output3".witList) + == some("output3".witList) ); } @@ -62,8 +62,8 @@ void run() { assert(errnoResult().isOk); { - WitString[1] input = [cast(WitString)"typedef2".witList]; - auto result = listTypedefs(cast(WitString)"typedef1".witList, input[].witList); + immutable WitString[1] input = ["typedef2".witList]; + auto result = listTypedefs("typedef1".witList, input[].witList); scope(exit) result.witFree; assert(result[0] == (cast(ubyte[])"typedef3").witList); @@ -72,12 +72,19 @@ void run() { } { - bool[2] input1 = [true, false]; - Result!(void, void)[2] input2 = [Result!(void, void).ok(), Result!(void, void).err()]; - MyErrno[2] input3 = [MyErrno.success, MyErrno.a]; + static immutable bool[] input1 = [true, false]; + static immutable Result!()[] input2 = [Result!().ok, Result!().err]; + static immutable MyErrno[] input3 = [MyErrno.success, MyErrno.a]; auto result = listOfVariants(input1[].witList, input2[].witList, input3[].witList); scope(exit) result.witFree; + + static immutable bool[] output1 = [false, true]; + static immutable Result!()[] output2 = [Result!().err, Result!().ok]; + static immutable MyErrno[] output3 = [MyErrno.a, MyErrno.b]; + assert(result[0] == output1); + assert(result[1] == output2); + assert(result[2] == output3); } } diff --git a/tests/runtime/flavorful/test.d b/tests/runtime/flavorful/test.d new file mode 100644 index 000000000..630cfa589 --- /dev/null +++ b/tests/runtime/flavorful/test.d @@ -0,0 +1,103 @@ +import wit.test.flavorful.test; +import wit.common; + +@witExport("test:flavorful/to-test", "f-list-in-record1") +void fListInRecord1(in ListInRecord1 a) { + assert(a.a == "list_in_record1"); +} + +@witExport("test:flavorful/to-test", "f-list-in-record2") +ListInRecord2 fListInRecord2() { + return (const ListInRecord2("list_in_record2".witList)).witClone; +} + +@witExport("test:flavorful/to-test", "f-list-in-record3") +ListInRecord3 fListInRecord3(in ListInRecord3 a) { + assert(a.a == "list_in_record3 input"); + return (const ListInRecord3("list_in_record3 output".witList)).witClone; +} + +@witExport("test:flavorful/to-test", "f-list-in-record4") +ListInAlias fListInRecord4(in ListInAlias a) { + assert(a.a == "input4"); + return (const ListInAlias("result4".witList)).witClone; +} + +@witExport("test:flavorful/to-test", "f-list-in-variant1") +void fListInVariant1(in ListInVariant1V1 a, in ListInVariant1V2 b) { + assert(a.unwrap() == "foo"); + assert(b.unwrapErr() == "bar"); +} + +@witExport("test:flavorful/to-test", "f-list-in-variant2") +Option!WitString fListInVariant2() { + return some("list_in_variant2".witList).witClone; +} + +@witExport("test:flavorful/to-test", "f-list-in-variant3") +Option!WitString fListInVariant3(in ListInVariant3 a) { + assert(a.unwrap() == "input3"); + return some("output3".witList).witClone; +} + +@witExport("test:flavorful/to-test", "errno-result") +Result!(void, MyErrno) errnoResult() { + static bool first = true; + + if (first) { + first = false; + return Result!(void, MyErrno).err(MyErrno.b); + } else { + return Result!(void, MyErrno).ok(); + } +} + + +@witExport("test:flavorful/to-test", "list-typedefs") +Tuple!(ListTypedef2, ListTypedef3) listTypedefs(in ListTypedef a, in ListTypedef3 b) { + assert(a == "typedef1"); + assert(b.length == 1); + assert(b[0] == "typedef2"); + + WitString[1] strings = [ + cast(WitString)"typedef4".witList + ]; + + return (const Tuple!(ListTypedef2, ListTypedef3)((cast(immutable ubyte[])"typedef3").witList, strings[].witList)).witClone; +} + + + +@witExport("test:flavorful/to-test", "list-of-variants") +Tuple!(WitList!bool, WitList!(Result!()), WitList!MyErrno) listOfVariants(in WitList!bool bools, in WitList!(Result!()) results, in WitList!MyErrno enums) { + static immutable bool[] boolsCmp = [true, false]; + assert(bools == boolsCmp[]); + + static immutable Result!()[] resultsCmp = [Result!().ok, Result!().err]; + assert(results == resultsCmp[]); + + static immutable MyErrno[] enumsCmp = [MyErrno.success, MyErrno.a]; + assert(enums == enumsCmp[]); + + static immutable bool[] boolsOut = [false, true]; + static immutable Result!(void)[] resultsOut = [Result!().err, Result!().ok]; + static immutable MyErrno[] enumsOut = [MyErrno.a, MyErrno.b]; + return (const Tuple!(WitList!bool, WitList!(Result!()), WitList!MyErrno)( + boolsOut.witList, + resultsOut.witList, + enumsOut.witList + )).witClone; +} + +alias Exports = wit.test.flavorful.test.Exports!( + fListInRecord1, + fListInRecord2, + fListInRecord3, + fListInRecord4, + fListInVariant1, + fListInVariant2, + fListInVariant3, + errnoResult, + listTypedefs, + listOfVariants +); From 988a65b8322f32dcd39923d15cdcccc2d4379944 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sun, 7 Jun 2026 22:13:10 -0700 Subject: [PATCH 30/55] Allow changing root package. --- crates/d/src/lib.rs | 138 +++++++++++++++++++++++++++----------- crates/d/src/wit_common.d | 18 ++--- 2 files changed, 109 insertions(+), 47 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 9abd739a6..665037fe4 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -23,6 +23,8 @@ struct DSig { #[derive(Default)] struct D { + root_pkg: String, + used_interfaces: HashSet<(WorldKey, InterfaceId)>, export_stubs: Vec, @@ -67,6 +69,11 @@ pub struct Opts { /// the component type. #[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))] pub type_section_suffix: Option, + + /// Add the specified suffix to the name of the custome section containing + /// the component type. + #[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))] + pub root_package: Option, } impl Opts { @@ -237,7 +244,7 @@ pub fn wasm_type(ty: WasmType) -> &'static str { } } -fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { +fn get_package_fqn(root_pkg: &str, id: PackageId, resolve: &Resolve) -> String { let pkg = &resolve.packages[id]; let pkg_has_multiple_versions = resolve.packages.iter().any(|(_, p)| { p.name.namespace == pkg.name.namespace @@ -246,7 +253,7 @@ fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { }); format!( - "wit.{}.{}{}", + "{root_pkg}.{}.{}{}", escape_d_identifier(&pkg.name.namespace.to_snake_case()), escape_d_identifier(&pkg.name.name.to_snake_case()), if pkg_has_multiple_versions { @@ -267,6 +274,7 @@ fn get_package_fqn(id: PackageId, resolve: &Resolve) -> String { } fn get_interface_fqn( + root_pkg: &str, interface_id: &WorldKey, world_fqn: &str, resolve: &Resolve, @@ -292,7 +300,7 @@ fn get_interface_fqn( format!( "{}.{}.{}", - get_package_fqn(iface.package.unwrap(), resolve), + get_package_fqn(root_pkg, iface.package.unwrap(), resolve), escape_d_identifier(&iface.name.as_ref().unwrap().to_snake_case()), match direction { None => "common", @@ -304,11 +312,11 @@ fn get_interface_fqn( } } -fn get_world_fqn(id: WorldId, resolve: &Resolve) -> String { +fn get_world_fqn(root_pkg: &str, id: WorldId, resolve: &Resolve) -> String { let world = &resolve.worlds[id]; format!( "{}.{}", - get_package_fqn(world.package.unwrap(), resolve), + get_package_fqn(root_pkg, world.package.unwrap(), resolve), escape_d_identifier(&world.name.to_snake_case()) ) } @@ -359,7 +367,9 @@ impl WorldGenerator for D { } fn preprocess(&mut self, resolve: &Resolve, world_id: WorldId) { - self.world_fqn = get_world_fqn(world_id, resolve); + self.root_pkg = self.opts.root_package.as_deref().unwrap_or("wit").into(); + + self.world_fqn = get_world_fqn(&self.root_pkg, world_id, resolve); self.world_id = Some(world_id); self.types.analyze(resolve); @@ -373,12 +383,18 @@ impl WorldGenerator for D { match name { WorldKey::Interface(_) => { - result.common = - Some(get_interface_fqn(&name, &self.world_fqn, resolve, None)); + result.common = Some(get_interface_fqn( + &self.root_pkg, + &name, + &self.world_fqn, + resolve, + None, + )); } WorldKey::Name(_) => { // For anonymous/inline imports, the common types are in the same file as the imports result.common = Some(get_interface_fqn( + &self.root_pkg, &name, &self.world_fqn, resolve, @@ -390,6 +406,7 @@ impl WorldGenerator for D { result }); (*fqns).import = Some(get_interface_fqn( + &self.root_pkg, &name, &self.world_fqn, resolve, @@ -408,12 +425,18 @@ impl WorldGenerator for D { match name { WorldKey::Interface(_) => { - result.common = - Some(get_interface_fqn(&name, &self.world_fqn, resolve, None)); + result.common = Some(get_interface_fqn( + &self.root_pkg, + &name, + &self.world_fqn, + resolve, + None, + )); } WorldKey::Name(_) => { // For anonymous/inline exports, the common types are in the same file as the exports result.common = Some(get_interface_fqn( + &self.root_pkg, &name, &self.world_fqn, resolve, @@ -425,6 +448,7 @@ impl WorldGenerator for D { result }); (*fqns).export = Some(get_interface_fqn( + &self.root_pkg, &name, &self.world_fqn, resolve, @@ -482,7 +506,11 @@ impl WorldGenerator for D { } } - let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + let mut interface_filepath = PathBuf::from_iter( + ["wit"] + .into_iter() + .chain(fqn.split(".").skip(r#gen.r#gen.root_pkg.split(".").count())), + ); interface_filepath.set_extension("d"); files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); @@ -570,9 +598,10 @@ impl WorldGenerator for D { r#gen.types(id); - r#gen - .src - .push_str("\npackage(wit) template Exports(Impl...) {\n"); + r#gen.src.push_str(&format!( + "\npackage({}) template Exports(Impl...) {{\n", + r#gen.r#gen.root_pkg + )); for (_name, func) in &resolve.interfaces[id].functions { match func.kind { @@ -681,7 +710,11 @@ impl WorldGenerator for D { self.export_stubs.push(format!("{fqn}.STUBS")); } - let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + let mut interface_filepath = PathBuf::from_iter( + ["wit"] + .into_iter() + .chain(fqn.split(".").skip(self.root_pkg.split(".").count())), + ); interface_filepath.set_extension("d"); files.push(interface_filepath.to_str().unwrap(), src.as_bytes()); @@ -743,7 +776,11 @@ impl WorldGenerator for D { r#gen.prologue(); r#gen.types(id); - let mut interface_filepath = PathBuf::from_iter(fqn.split(".")); + let mut interface_filepath = PathBuf::from_iter( + ["wit"] + .into_iter() + .chain(fqn.split(".").skip(r#gen.r#gen.root_pkg.split(".").count())), + ); interface_filepath.set_extension("d"); files.push(interface_filepath.to_str().unwrap(), r#gen.src.as_bytes()); @@ -760,7 +797,7 @@ impl WorldGenerator for D { )); world_src.push_str(&format!("module {};\n\n", self.world_fqn)); - world_src.push_str("import wit.common;\n\n"); + world_src.push_str(&format!("import {}.common;\n\n", self.root_pkg)); world_src.push_str( &self .interface_imports @@ -864,12 +901,20 @@ impl WorldGenerator for D { )); } - let mut world_filepath = PathBuf::from_iter(get_world_fqn(world_id, resolve).split(".")); + let mut world_filepath = PathBuf::from_iter( + ["wit"].into_iter().chain( + get_world_fqn(&self.root_pkg, world_id, resolve) + .split(".") + .skip(self.root_pkg.split(".").count()), + ), + ); world_filepath.push("package.d"); files.push(world_filepath.to_str().unwrap(), world_src.as_bytes()); - files.push("wit/common.d", include_bytes!("wit_common.d")); + let mut wit_common_file = format!("module {}.common;\n\n", self.root_pkg).into_bytes(); + wit_common_file.extend_from_slice(include_bytes!("wit_common.d").as_slice()); + files.push("wit/common.d", &wit_common_file); Ok(()) } } @@ -1037,7 +1082,8 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str(&format!("module {};\n\n", self.fqn)); - self.src.push_str("import wit.common;\n\n"); + self.src + .push_str(&format!("import {}.common;\n\n", self.r#gen.root_pkg)); if self.direction.is_some() && let Some(WorldKey::Interface(_)) = self.name { @@ -1205,7 +1251,7 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str("static "); } self.src.push_str(&format!( - "{} {}({}) {{\n", + "{} {}({}) @nogc nothrow {{\n", d_sig.result, d_sig.name, d_sig @@ -1268,7 +1314,7 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str("static "); } self.src.push_str(&format!( - "private extern(C) {} __import_{}({});\n", + "private extern(C) {} __import_{}({}) @nogc nothrow;\n", match wasm_sig.results.len() { 0 => "void", 1 => wasm_type(wasm_sig.results[0]), @@ -1634,15 +1680,18 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "struct {escaped_name} {{ - package(wit) uint __handle = 0; + @nogc nothrow: - package(wit) this(uint handle) {{ + package({}) uint __handle = 0; + + package({0}) this(uint handle) {{ __handle = handle; }} @disable this(); - " + ", + self.r#gen.root_pkg )); match ty.owner { @@ -1712,9 +1761,11 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { alias borrow this; struct Borrow {{ - package(wit) uint __handle = 0; + @nogc nothrow: + + package({}) uint __handle = 0; - package(wit) this(uint handle) {{ + package({0}) this(uint handle) {{ __handle = handle; }} @@ -1722,7 +1773,8 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { void witFree() {{}} Borrow witClone() const {{ return Borrow(__handle); }} - " + ", + self.r#gen.root_pkg )); match ty.owner { @@ -1784,14 +1836,17 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "struct {escaped_name} {{ - package(wit) uint __handle = 0; + @nogc nothrow: - package(wit) this(uint handle) {{ + package({}) uint __handle = 0; + + package({0}) this(uint handle) {{ __handle = handle; }} @disable this(); -" +", + self.r#gen.root_pkg )); self.src.push_str(&format!( @@ -1864,9 +1919,11 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { alias borrow this; struct Borrow {{ - package(wit) uint __handle = 0; + @nogc nothrow: + + package({}) uint __handle = 0; - package(wit) this(uint handle) {{ + package({0}) this(uint handle) {{ __handle = handle; }} @@ -1875,7 +1932,8 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { void witFree() {{}} Borrow witClone() const {{ return Borrow(__handle); }} - " + ", + self.r#gen.root_pkg )); self.src.push_str("}\n"); @@ -2016,7 +2074,8 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str("}\n"); - self.src.push_str("Tag tag() const => _tag;\n"); + self.src + .push_str("Tag tag() const @safe @nogc nothrow pure => _tag;\n"); for case in &variant.cases { self.src.push_str(&format!( @@ -2522,9 +2581,9 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!( "auto {list_src} = {}; - auto {list} = wit.common.malloc({list_src}.length * ({size_str})); - scope(exit) {{ wit.common.free({list}); }}\n", - operands[0] + auto {list} = {}.common.malloc({list_src}.length * ({size_str})); + scope(exit) {{ {1}.common.free({list}); }}\n", + operands[0], self.r#gen.r#gen.root_pkg )); self.push_str(&format!( @@ -2591,7 +2650,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("auto {list_src} = {};\n", operands[0])); self.push_str(&format!("auto {list_len} = {};\n", operands[1])); self.push_str(&format!( - "auto {list} = wit.common.mallocSlice!({elem_type_name})({list_len});\n", + "auto {list} = {}.common.mallocSlice!({elem_type_name})({list_len});\n", + self.r#gen.r#gen.root_pkg )); self.push_str(&format!( diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index d39f24dcc..6a07194a2 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -1,5 +1,3 @@ -module wit.common; - import core.attribute : mustuse; import ldc.attributes : llvmAttr; @@ -115,7 +113,7 @@ private: @disable this(); - this(Tag tag, Storage storage = Storage.init) { + this(Tag tag, inout Storage storage = Storage.init) inout @nogc nothrow @trusted { _tag = tag; _storage = storage; } @@ -124,10 +122,10 @@ private: static auto _create(Tag tag)() if (is(Types[tag] == void)) { return typeof(this)(tag); } - static auto _create(Tag tag)(Types[tag] val) if (!is(Types[tag] == void)) { + static auto _create(Tag tag)(inout Types[tag] val) if (!is(Types[tag] == void)) { Storage storage = Storage.init; - storage.tupleof[tag+1] = val; - return typeof(this)(tag, storage); + storage.tupleof[tag+1] = cast(Types[tag])val; + return inout typeof(this)(tag, cast(inout(Storage))storage); } ref auto _get(Tag tag)() inout return if (!is(Types[tag] == void)) @@ -334,10 +332,11 @@ T witClone(T : U[L], U, size_t L)(in T val) { return clone; } -package(wit): +package: extern(C) { version (WitBindings_DummyLibc) { +@nogc nothrow { extern __gshared ubyte __heap_base; private __gshared void* heapTail = &__heap_base; @@ -394,13 +393,16 @@ version (WitBindings_DummyLibc) { return 0; } +} } else { +@nogc nothrow { void* malloc(size_t size); void* realloc(void* ptr, size_t newSize); void free(void* ptr); noreturn abort(); } } +} // from numem.casting pragma(inline, true) @@ -409,7 +411,7 @@ auto ref T reinterpretCast(T, U)(auto ref U from) @trusted if (T.sizeof == U.siz return tmp(from).to; } -auto mallocSlice(T)(size_t count) { +auto mallocSlice(T)(size_t count) @nogc nothrow { auto ptr = malloc(count*T.sizeof); if (ptr is null) return null; From d0284096a3e33f400111154b0b362a017f8493e5 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 8 Jun 2026 01:00:25 -0700 Subject: [PATCH 31/55] Force component types to be linked --- crates/d/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 665037fe4..fef301ee2 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -881,7 +881,11 @@ impl WorldGenerator for D { world_src.push_str(&format!( " - void __wit_bindgen_component_type() {{ + pragma(inline, false) + package({}) void __wit_bindgen_component_type_force_link() pure @nogc nothrow {{}} + + + package({0}) void __wit_bindgen_component_type() {{ imported!\"ldc.llvmasm\".__irEx!( \"\", \"\", @@ -891,6 +895,7 @@ impl WorldGenerator for D { ); }} ", + self.root_pkg, &component_type .iter() .map(|b| format!("\\{b:02X}")) @@ -1131,6 +1136,7 @@ impl<'a> DInterfaceGenerator<'a> { } } self.src.push_str("\n"); + self.src.push_str(&format!("package ({}) void __wit_bindgen_component_type_force_link() pure @nogc nothrow => imported!\"{}\".__wit_bindgen_component_type_force_link();\n", self.r#gen.root_pkg, self.r#gen.world_fqn)); } fn type_is_direction_sensitive(&self, id: TypeId) -> bool { From 21dbcc8b6b145acbb313efb2a9e1a1b81ec6cf4f Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 00:34:45 -0700 Subject: [PATCH 32/55] Use `walloc` instead of bump allocator in tests. --- crates/d/src/wit_common.d | 71 +--- crates/test/d-test-support/libc.d | 21 ++ crates/test/d-test-support/walloc.d | 512 ++++++++++++++++++++++++++++ crates/test/src/d.rs | 15 +- 4 files changed, 552 insertions(+), 67 deletions(-) create mode 100644 crates/test/d-test-support/libc.d create mode 100644 crates/test/d-test-support/walloc.d diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 6a07194a2..f57306bb9 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -334,77 +334,18 @@ T witClone(T : U[L], U, size_t L)(in T val) { package: -extern(C) { -version (WitBindings_DummyLibc) { -@nogc nothrow { - extern __gshared ubyte __heap_base; - private __gshared void* heapTail = &__heap_base; - - // basic bump allocator - // based on `malloc0` sans the ability to free - void* malloc(size_t size) { - import ldc.intrinsics : llvm_wasm_memory_grow, llvm_wasm_memory_size; - size = (size + 7) & ~7; // align up to 8 bytes - - void* allocStart = heapTail; - void* allocEnd = allocStart+size; - - // Pages in Wasm are 64KiB (65536) - size_t memSizePages = llvm_wasm_memory_size(0); - size_t memSizeBytes = memSizePages << 16; - - if (cast(size_t)allocEnd > memSizeBytes) { - if (llvm_wasm_memory_grow(0, (cast(size_t)allocEnd >> 16)-memSizePages + 1) == -1) abort(); - } - - void* ret = allocStart; - heapTail = allocEnd; - return ret; - } - - void* realloc(void* ptr, size_t newSize) { - // can't actual realloc; only handles use as conditional malloc/free - if (ptr) abort(); - - if (newSize == 0) return null; - - return malloc(newSize); - } - - // no ability to free - void free(void* ptr) {} - - noreturn abort() { - import ldc.intrinsics : llvm_trap; - llvm_trap(); - while(true) {} - } - - private int memcmp(const void* ptr1, const void* ptr2, size_t size) - { - auto data1 = cast(const(ubyte)*)ptr1; - auto data2 = cast(const(ubyte)*)ptr2; - - foreach (i; 0..size) { - auto b1 = data1[i]; - auto b2 = data2[i]; - if (b1 != b2) return b1-b2; - } - - return 0; - } -} -} else { -@nogc nothrow { +extern(C) @nogc nothrow { void* malloc(size_t size); void* realloc(void* ptr, size_t newSize); void free(void* ptr); noreturn abort(); } -} -} -// from numem.casting +// from https://github.com/Inochi2D/numem/blob/main/source/numem/casting.d +// Copyright © 2023-2025, Kitsunebi Games +// Copyright © 2023-2025, Inochi2D Project +// License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) +// Authors: Luna Nielsen pragma(inline, true) auto ref T reinterpretCast(T, U)(auto ref U from) @trusted if (T.sizeof == U.sizeof) { union tmp { U from; T to; } diff --git a/crates/test/d-test-support/libc.d b/crates/test/d-test-support/libc.d new file mode 100644 index 000000000..e225ec9a7 --- /dev/null +++ b/crates/test/d-test-support/libc.d @@ -0,0 +1,21 @@ +extern(C) @nogc nothrow: + +noreturn abort() { + import ldc.intrinsics : llvm_trap; + llvm_trap(); + while(true) {} +} + +private int memcmp(const void* ptr1, const void* ptr2, size_t size) +{ + auto data1 = cast(const(ubyte)*)ptr1; + auto data2 = cast(const(ubyte)*)ptr2; + + foreach (i; 0..size) { + auto b1 = data1[i]; + auto b2 = data2[i]; + if (b1 != b2) return b1-b2; + } + + return 0; +} diff --git a/crates/test/d-test-support/walloc.d b/crates/test/d-test-support/walloc.d new file mode 100644 index 000000000..fca63c6a8 --- /dev/null +++ b/crates/test/d-test-support/walloc.d @@ -0,0 +1,512 @@ +// From https://github.com/Inochi2D/numem/blob/main/modules/hookset-wasm/source/walloc.d +/** + A small malloc implementation for use in WebAssembly targets + + Copyright (c) 2023-2025, Kitsunebi Games + Copyright (c) 2023-2025, Inochi2D Project + Copyright (c) 2020, Igalia, S.L. + + Distributed under an MIT-style License. + (See accompanying LICENSE file or copy at + https://github.com/wingo/walloc/blob/master/LICENSE.md) +*/ +module walloc; +import ldc.intrinsics : + llvm_wasm_memory_grow, + llvm_wasm_memory_size, + llvm_memmove; + +extern(C) @nogc nothrow: + +void* malloc(size_t size) @nogc nothrow @system { + if (size == 0) + return null; + + size_t granules = size_to_granules(size); + chunk_kind kind = granules_to_chunk_kind(granules); + return (kind == chunk_kind.LARGE_OBJECT) ? allocate_large(size) : allocate_small(kind); +} + +export +void free(void *ptr) @nogc nothrow @system { + if (!ptr) return; + + _page_t* page = get_page(ptr); + size_t chunk = get_chunk_index(ptr); + ubyte kind = page.header.chunk_kinds[chunk]; + if (kind == chunk_kind.LARGE_OBJECT) { + _large_object_t* obj = get_large_object(ptr); + obj.next = large_objects; + large_objects = obj; + allocate_chunk(page, chunk, chunk_kind.FREE_LARGE_OBJECT); + pending_large_object_compact = 1; + } else { + size_t granules = kind; + _freelist_t** loc = get_small_object_freelist(cast(chunk_kind)granules); + _freelist_t* obj = cast(_freelist_t*)ptr; + obj.next = *loc; + *loc = obj; + } +} + +export +void* realloc(void* ptr, size_t newSize) @nogc nothrow @system { + if (!ptr) + return malloc(newSize); + + size_t oldSize = get_alloc_size(ptr); + if (newSize <= oldSize) + return ptr; + + // Size is bigger, realloc just to be sure. + void* n_mem = malloc(newSize); + llvm_memmove(n_mem, ptr, oldSize, true); + free(ptr); + return n_mem; +} + +private: + +size_t get_alloc_size(void* ptr) { + _page_t* page = get_page(ptr); + size_t chunk = get_chunk_index(ptr); + chunk_kind kind = cast(chunk_kind)page.header.chunk_kinds[chunk]; + + if (kind == chunk_kind.LARGE_OBJECT) { + _large_object_t* obj = get_large_object(ptr); + return obj.size; + } + + if (kind < chunk_kind.SMALL_OBJECT_CHUNK_KINDS) { + ptrdiff_t granules = chunk_kind_to_granules(kind); + return granules * GRANULE_SIZE; + } + + return 0; +} + +extern __gshared void* __heap_base; +__gshared size_t walloc_heap_size; +__gshared _freelist_t*[chunk_kind.SMALL_OBJECT_CHUNK_KINDS] small_object_freelists; +__gshared _large_object_t* large_objects; + + +pragma(inline, true) +size_t _max(size_t a, size_t b) { return a < b ? b : a; } + +pragma(inline, true) +size_t _alignv(size_t val, size_t alignment) { return (val + alignment - 1) & ~(alignment - 1); } + +pragma(inline, true) +extern(D) +void __assert_aligned(T, Y)(T x, Y y) { + assert(cast(size_t)x == _alignv(cast(size_t)x, cast(size_t)y)); +} + +enum size_t CHUNK_SIZE = 256; +enum size_t CHUNK_SIZE_LOG_2 = 8; +enum size_t CHUNK_MASK = (CHUNK_SIZE - 1); +enum size_t PAGE_SIZE = 65536; +enum size_t PAGE_SIZE_LOG_2 = 16; +enum size_t PAGE_MASK = (PAGE_SIZE - 1); +enum size_t CHUNKS_PER_PAGE = 256; +enum size_t GRANULE_SIZE = 8; +enum size_t GRANULE_SIZE_LOG_2 = 3; +enum size_t LARGE_OBJECT_THRESHOLD = 256; +enum size_t LARGE_OBJECT_GRANULE_THRESHOLD = 32; +enum size_t FIRST_ALLOCATABLE_CHUNK = 1; +enum size_t PAGE_HEADER_SIZE = _page_header_t.sizeof; +enum size_t LARGE_OBJECT_HEADER_SIZE = _large_object_t.sizeof; + +static assert(PAGE_SIZE == CHUNK_SIZE * CHUNKS_PER_PAGE); +static assert(CHUNK_SIZE == 1 << CHUNK_SIZE_LOG_2); +static assert(PAGE_SIZE == 1 << PAGE_SIZE_LOG_2); +static assert(GRANULE_SIZE == 1 << GRANULE_SIZE_LOG_2); +static assert(LARGE_OBJECT_THRESHOLD == + LARGE_OBJECT_GRANULE_THRESHOLD * GRANULE_SIZE); + +struct _chunk_t { + void[CHUNK_SIZE] data; +} + +enum chunk_kind : ubyte { + GRANULES_1, + GRANULES_2, + GRANULES_3, + GRANULES_4, + GRANULES_5, + GRANULES_6, + GRANULES_8, + GRANULES_10, + GRANULES_16, + GRANULES_32, + + SMALL_OBJECT_CHUNK_KINDS, + FREE_LARGE_OBJECT = 254, + LARGE_OBJECT = 255 +} + +__gshared const ubyte[] small_object_granule_sizes = [ + 1, 2, 3, 4, 5, 6, 8, 10, 16, 32 +]; + +pragma(inline, true) +chunk_kind granules_to_chunk_kind(size_t granules) { + static foreach(gsize; small_object_granule_sizes) { + if (granules <= gsize) + return mixin(q{chunk_kind.GRANULES_}, cast(int)gsize); + } + return chunk_kind.LARGE_OBJECT; +} + +pragma(inline, true) +ubyte chunk_kind_to_granules(chunk_kind kind) { + static foreach(gsize; small_object_granule_sizes) { + if (kind == mixin(q{chunk_kind.GRANULES_}, cast(int)gsize)) + return gsize; + } + return cast(ubyte)-1; +} + +struct _page_header_t { + ubyte[CHUNKS_PER_PAGE] chunk_kinds; +} + +struct _page_t { + union { + _page_header_t header; + _chunk_t[CHUNKS_PER_PAGE] chunks; + } +} + +pragma(inline, true) +_page_t* get_page(void *ptr) { + return cast(_page_t*)cast(void*)((cast(size_t) ptr) & ~PAGE_MASK); +} + +pragma(inline, true) +static size_t get_chunk_index(void *ptr) { + return ((cast(size_t) ptr) & PAGE_MASK) / CHUNK_SIZE; +} + +struct _freelist_t { + _freelist_t *next; +} + +struct _large_object_t { + _large_object_t* next; + size_t size; +} + +pragma(inline, true) +void* get_large_object_payload(_large_object_t *obj) { + return (cast(void*)obj) + LARGE_OBJECT_HEADER_SIZE; +} + +pragma(inline, true) +_large_object_t* get_large_object(void *ptr) { + return cast(_large_object_t*)(ptr - LARGE_OBJECT_HEADER_SIZE); +} + +_page_t* allocate_pages(size_t payloadSize, size_t* allocated) { + size_t needed = payloadSize + PAGE_HEADER_SIZE; + size_t heap_size = llvm_wasm_memory_size(0) * PAGE_SIZE; + size_t base = heap_size; + size_t preallocated = 0, grow = 0; + + if (!walloc_heap_size) { + // We are allocating the initial pages, if any. We skip the first 64 kB, + // then take any additional space up to the memory size. + size_t heap_base = _alignv(cast(size_t)&__heap_base, PAGE_SIZE); + preallocated = heap_size - heap_base; // Preallocated pages. + walloc_heap_size = preallocated; + base -= preallocated; + } + + if (preallocated < needed) { + // Always grow the walloc heap at least by 50%. + grow = _alignv(_max(walloc_heap_size / 2, needed - preallocated), + PAGE_SIZE); + + assert(grow); + if (llvm_wasm_memory_grow(0, cast(int)(grow >> PAGE_SIZE_LOG_2)) == -1) { + return null; + } + + walloc_heap_size += grow; + } + + _page_t* ret = cast(_page_t*)base; + size_t size = grow + preallocated; + + assert(size); + assert(size == _alignv(size, PAGE_SIZE)); + *allocated = size / PAGE_SIZE; + return ret; +} + +void* allocate_chunk(_page_t* page, size_t idx, chunk_kind kind) { + page.header.chunk_kinds[idx] = kind; + return page.chunks[idx].data.ptr; +} + +// It's possible for splitting to produce a large object of size 248 (256 minus +// the header size) -- i.e. spanning a single chunk. In that case, push the +// chunk back on the GRANULES_32 small object freelist. +void maybe_repurpose_single_chunk_large_objects_head() { + if (large_objects.size < CHUNK_SIZE) { + size_t idx = get_chunk_index(large_objects); + void* ptr = allocate_chunk(get_page(large_objects), idx, chunk_kind.GRANULES_32); + large_objects = large_objects.next; + _freelist_t* head = cast(_freelist_t*)ptr; + head.next = small_object_freelists[chunk_kind.GRANULES_32]; + small_object_freelists[chunk_kind.GRANULES_32] = head; + } +} + +// If there have been any large-object frees since the last large object +// allocation, go through the freelist and merge any adjacent objects. +__gshared int pending_large_object_compact = 0; +_large_object_t** maybe_merge_free_large_object(_large_object_t** prev) { + _large_object_t* obj = *prev; + + while(true) { + void* end = get_large_object_payload(obj) + obj.size; + __assert_aligned(end, CHUNK_SIZE); + + size_t chunk = get_chunk_index(end); + if (chunk < FIRST_ALLOCATABLE_CHUNK) { + // Merging can't create a large object that newly spans the header chunk. + // This check also catches the end-of-heap case. + return prev; + } + _page_t* page = get_page(end); + if (page.header.chunk_kinds[chunk] != chunk_kind.FREE_LARGE_OBJECT) { + return prev; + } + _large_object_t* next = cast(_large_object_t*)end; + + _large_object_t** prev_prev = &large_objects; + _large_object_t* walk = large_objects; + while(true) { + assert(walk); + if (walk == next) { + obj.size += LARGE_OBJECT_HEADER_SIZE + walk.size; + *prev_prev = walk.next; + if (prev == &walk.next) { + prev = prev_prev; + } + break; + } + prev_prev = &walk.next; + walk = walk.next; + } + } +} + +void maybe_compact_free_large_objects() { + if (pending_large_object_compact) { + pending_large_object_compact = 0; + _large_object_t** prev = &large_objects; + while (*prev) { + prev = &(*maybe_merge_free_large_object(prev)).next; + } + } +} + +// Allocate a large object with enough space for SIZE payload bytes. Returns a +// large object with a header, aligned on a chunk boundary, whose payload size +// may be larger than SIZE, and whose total size (header included) is +// chunk-aligned. Either a suitable allocation is found in the large object +// freelist, or we ask the OS for some more pages and treat those pages as a +// large object. If the allocation fits in that large object and there's more +// than an aligned chunk's worth of data free at the end, the large object is +// split. +// +// The return value's corresponding chunk in the page as starting a large +// object. +_large_object_t* allocate_large_object(size_t size) { + maybe_compact_free_large_objects(); + + _large_object_t* best = null; + _large_object_t** best_prev = &large_objects; + size_t best_size = -1; + + _large_object_t** prev = &large_objects; + _large_object_t* walk = large_objects; + while (walk) { + if (walk.size >= size && walk.size < best_size) { + best_size = walk.size; + best = walk; + best_prev = prev; + + // Not going to do any better than this; just return it. + if (best_size + LARGE_OBJECT_HEADER_SIZE == _alignv(size + LARGE_OBJECT_HEADER_SIZE, CHUNK_SIZE)) + break; + } + + prev = &walk.next; + walk = walk.next; + } + + if (!best) { + // The large object freelist doesn't have an object big enough for this + // allocation. Allocate one or more pages from the OS, and treat that new + // sequence of pages as a fresh large object. It will be split if + // necessary. + size_t size_with_header = size + _large_object_t.sizeof; + size_t n_allocated = 0; + _page_t* page = allocate_pages(size_with_header, &n_allocated); + if (!page) { + return null; + } + + void* ptr = allocate_chunk(page, FIRST_ALLOCATABLE_CHUNK, chunk_kind.LARGE_OBJECT); + best = cast(_large_object_t*)ptr; + size_t page_header = ptr - cast(void*)page; + + best.next = large_objects; + best.size = best_size = n_allocated * PAGE_SIZE - page_header - LARGE_OBJECT_HEADER_SIZE; + assert(best_size >= size_with_header); + } + + allocate_chunk(get_page(best), get_chunk_index(best), chunk_kind.LARGE_OBJECT); + + _large_object_t* next = best.next; + *best_prev = next; + + size_t tail_size = (best_size - size) & ~CHUNK_MASK; + if (tail_size) { + // The best-fitting object has 1 or more aligned chunks free after the + // requested allocation; split the tail off into a fresh aligned object. + _page_t* start_page = get_page(best); + void* start = get_large_object_payload(best); + void* end = start + best_size; + + if (start_page == get_page(end - tail_size - 1)) { + + // The allocation does not span a page boundary; yay. + __assert_aligned(end, CHUNK_SIZE); + } else if (size < PAGE_SIZE - LARGE_OBJECT_HEADER_SIZE - CHUNK_SIZE) { + + // If the allocation itself smaller than a page, split off the head, then + // fall through to maybe split the tail. + assert(cast(size_t)end == _alignv(cast(size_t)end, PAGE_SIZE)); + + size_t first_page_size = PAGE_SIZE - (cast(size_t)start & PAGE_MASK); + _large_object_t* head = best; + allocate_chunk(start_page, get_chunk_index(start), chunk_kind.FREE_LARGE_OBJECT); + head.size = first_page_size; + head.next = large_objects; + large_objects = head; + + maybe_repurpose_single_chunk_large_objects_head(); + + _page_t* next_page = start_page + 1; + void* ptr = allocate_chunk(next_page, FIRST_ALLOCATABLE_CHUNK, chunk_kind.LARGE_OBJECT); + best = cast(_large_object_t*)ptr; + best.size = best_size = best_size - first_page_size - CHUNK_SIZE - LARGE_OBJECT_HEADER_SIZE; + assert(best_size >= size); + + start = get_large_object_payload(best); + tail_size = (best_size - size) & ~CHUNK_MASK; + } else { + + // A large object that spans more than one page will consume all of its + // tail pages. Therefore if the split traverses a page boundary, round up + // to page size. + __assert_aligned(end, PAGE_SIZE); + size_t first_page_size = PAGE_SIZE - (cast(size_t)start & PAGE_MASK); + size_t tail_pages_size = _alignv(size - first_page_size, PAGE_SIZE); + size = first_page_size + tail_pages_size; + tail_size = best_size - size; + } + best.size -= tail_size; + + size_t tail_idx = get_chunk_index(end - tail_size); + while (tail_idx < FIRST_ALLOCATABLE_CHUNK && tail_size) { + + // We would be splitting in a page header; don't do that. + tail_size -= CHUNK_SIZE; + tail_idx++; + } + + if (tail_size) { + _page_t *page = get_page(end - tail_size); + void* tail_ptr = allocate_chunk(page, tail_idx, chunk_kind.FREE_LARGE_OBJECT); + _large_object_t* tail = cast(_large_object_t*) tail_ptr; + tail.next = large_objects; + tail.size = tail_size - LARGE_OBJECT_HEADER_SIZE; + + debug { + size_t payloadsz = cast(size_t)get_large_object_payload(tail) + tail.size; + assert(payloadsz == _alignv(payloadsz, CHUNK_SIZE)); + } + + large_objects = tail; + maybe_repurpose_single_chunk_large_objects_head(); + } + } + + debug { + size_t payloadsz = cast(size_t)get_large_object_payload(best) + best.size; + assert(payloadsz == _alignv(payloadsz, CHUNK_SIZE)); + } + return best; +} + +_freelist_t* obtain_small_objects(chunk_kind kind) { + _freelist_t** whole_chunk_freelist = &small_object_freelists[chunk_kind.GRANULES_32]; + void *chunk; + if (*whole_chunk_freelist) { + chunk = *whole_chunk_freelist; + *whole_chunk_freelist = (*whole_chunk_freelist).next; + } else { + chunk = allocate_large_object(0); + if (!chunk) { + return null; + } + } + + void* ptr = allocate_chunk(get_page(chunk), get_chunk_index(chunk), kind); + void* end = ptr + CHUNK_SIZE; + _freelist_t* next = null; + size_t size = chunk_kind_to_granules(kind) * GRANULE_SIZE; + for (size_t i = size; i <= CHUNK_SIZE; i += size) { + _freelist_t* head = cast(_freelist_t*)(end - i); + head.next = next; + next = head; + } + return next; +} + +pragma(inline, true) +size_t size_to_granules(size_t size) { + return (size + GRANULE_SIZE - 1) >> GRANULE_SIZE_LOG_2; +} + +pragma(inline, true) +_freelist_t** get_small_object_freelist(chunk_kind kind) { + assert(kind < chunk_kind.SMALL_OBJECT_CHUNK_KINDS); + return &small_object_freelists[kind]; +} + +void* allocate_small(chunk_kind kind) { + _freelist_t** loc = get_small_object_freelist(kind); + if (!*loc) { + _freelist_t* freelist = obtain_small_objects(kind); + if (!freelist) + return null; + + *loc = freelist; + } + + _freelist_t* ret = *loc; + *loc = ret.next; + return cast(void*)ret; +} + +void* allocate_large(size_t size) { + _large_object_t* obj = allocate_large_object(size); + return obj ? get_large_object_payload(obj) : null; +} diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index ffb719ab4..537aeba9f 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -108,6 +108,16 @@ fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result< let mut cmd = Command::new(compiler); let output = compile.output.with_extension("core.wasm"); + + std::fs::write( + compile.artifacts_dir.join("libc.d"), + include_bytes!("../d-test-support/libc.d"), + )?; + std::fs::write( + compile.artifacts_dir.join("walloc.d"), + include_bytes!("../d-test-support/walloc.d"), + )?; + cmd.arg(&compile.component.path) .arg("-betterC") .arg("-mtriple=wasm32-unknown-unknown") @@ -118,10 +128,11 @@ fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result< .arg("--de") // deperecations are errors .arg("-w") // warnings are errors .arg("-L--no-entry") - .arg("--d-version=WitBindings_DummyLibc") // to provide bump allocator and `abort` .arg("--checkaction=halt") // to trap instead of using libc __assert .arg("-of") - .arg(&output); + .arg(&output) + .arg(compile.artifacts_dir.join("libc.d")) + .arg(compile.artifacts_dir.join("walloc.d")); runner.run_command(&mut cmd)?; From 32d623634ed5cdeb5f7b18db4d036cdd788295ff Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 00:35:20 -0700 Subject: [PATCH 33/55] Make emitting `wit_common.d` optional --- crates/d/src/lib.rs | 57 +++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index fef301ee2..aa76a55e7 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -24,6 +24,7 @@ struct DSig { #[derive(Default)] struct D { root_pkg: String, + common_module: String, used_interfaces: HashSet<(WorldKey, InterfaceId)>, export_stubs: Vec, @@ -60,20 +61,27 @@ pub struct Opts { #[cfg_attr(feature = "clap", arg(skip))] out_dir: Option, - #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] /// Whether stubs/declarations for exports should be emitted /// Only for testing purposes. + #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] emit_export_stubs: bool, - /// Add the specified suffix to the name of the custome section containing + /// Add the specified suffix to the name of the custom section containing /// the component type. #[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))] pub type_section_suffix: Option, - /// Add the specified suffix to the name of the custome section containing - /// the component type. + /// Choose root package other than `wit` to nest everything under. #[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))] pub root_package: Option, + + /// Whether the generated bindings should be self-contained + /// + /// Instead of relying on DRuntime (and wasi-libc) to define + /// the common types, and `cabi_realloc`, `wit.common` is + /// emitted alongside the bindings. + #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] + pub self_contained: bool, } impl Opts { @@ -368,6 +376,11 @@ impl WorldGenerator for D { fn preprocess(&mut self, resolve: &Resolve, world_id: WorldId) { self.root_pkg = self.opts.root_package.as_deref().unwrap_or("wit").into(); + self.common_module = if self.opts.self_contained { + format!("{}.common", self.root_pkg) + } else { + "core.sys.wasi.wit_common".into() + }; self.world_fqn = get_world_fqn(&self.root_pkg, world_id, resolve); self.world_id = Some(world_id); @@ -797,7 +810,7 @@ impl WorldGenerator for D { )); world_src.push_str(&format!("module {};\n\n", self.world_fqn)); - world_src.push_str(&format!("import {}.common;\n\n", self.root_pkg)); + world_src.push_str(&format!("import {};\n\n", self.common_module)); world_src.push_str( &self .interface_imports @@ -917,9 +930,11 @@ impl WorldGenerator for D { files.push(world_filepath.to_str().unwrap(), world_src.as_bytes()); - let mut wit_common_file = format!("module {}.common;\n\n", self.root_pkg).into_bytes(); - wit_common_file.extend_from_slice(include_bytes!("wit_common.d").as_slice()); - files.push("wit/common.d", &wit_common_file); + if self.opts.self_contained { + let mut wit_common_file = format!("module {};\n\n", self.common_module).into_bytes(); + wit_common_file.extend_from_slice(include_bytes!("wit_common.d").as_slice()); + files.push("wit/common.d", &wit_common_file); + } Ok(()) } } @@ -1088,7 +1103,7 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str(&format!("module {};\n\n", self.fqn)); self.src - .push_str(&format!("import {}.common;\n\n", self.r#gen.root_pkg)); + .push_str(&format!("import {};\n\n", self.r#gen.common_module)); if self.direction.is_some() && let Some(WorldKey::Interface(_)) = self.name { @@ -1639,7 +1654,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { )); } - self.src.push_str("\nvoid witFree() {\n"); + self.src.push_str("\nvoid witFree() @nogc nothrow {\n"); for field in &record.fields { let lower_name = field.name.to_lower_camel_case(); let escaped_name = escape_d_identifier(&lower_name); @@ -1651,8 +1666,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str("}\n"); if self.can_have_wit_clone(Type::Id(id)) { - self.src - .push_str(&format!("\n{escaped_name} witClone() const {{\n")); + self.src.push_str(&format!( + "\n{escaped_name} witClone() const @nogc nothrow {{\n" + )); self.src .push_str(&format!("{escaped_name} clone = void;\n")); for field in &record.fields { @@ -2108,7 +2124,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } } - self.src.push_str("\nvoid witFree() {\n"); + self.src.push_str("\nvoid witFree() @nogc nothrow {\n"); if self.needs_wit_free(Type::Id(id)) { self.src.push_str("switch (_tag) with (Tag) {\n"); for case in &variant.cases { @@ -2127,8 +2143,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str("}\n"); if self.can_have_wit_clone(Type::Id(id)) { - self.src - .push_str(&format!("\n{escaped_name} witClone() const {{\n")); + self.src.push_str(&format!( + "\n{escaped_name} witClone() const @nogc nothrow {{\n" + )); self.src.push_str("final switch (_tag) {\n"); for case in &variant.cases { let lower_case_name = case.name.to_lower_camel_case(); @@ -2587,9 +2604,9 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!( "auto {list_src} = {}; - auto {list} = {}.common.malloc({list_src}.length * ({size_str})); - scope(exit) {{ {1}.common.free({list}); }}\n", - operands[0], self.r#gen.r#gen.root_pkg + auto {list} = {}.malloc({list_src}.length * ({size_str})); + scope(exit) {{ {1}.free({list}); }}\n", + operands[0], self.r#gen.r#gen.common_module )); self.push_str(&format!( @@ -2656,8 +2673,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("auto {list_src} = {};\n", operands[0])); self.push_str(&format!("auto {list_len} = {};\n", operands[1])); self.push_str(&format!( - "auto {list} = {}.common.mallocSlice!({elem_type_name})({list_len});\n", - self.r#gen.r#gen.root_pkg + "auto {list} = {}.mallocSlice!({elem_type_name})({list_len});\n", + self.r#gen.r#gen.common_module )); self.push_str(&format!( From 61878714cf9e401471bcb0511509bde12a81723d Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 17:13:00 -0700 Subject: [PATCH 34/55] Add `--required-d-versions` --- crates/d/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index aa76a55e7..6159e71f1 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -82,6 +82,11 @@ pub struct Opts { /// emitted alongside the bindings. #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] pub self_contained: bool, + + /// A series of D versions that all the generated bindings + /// will be gated behind. + #[cfg_attr(feature = "clap", arg(long, value_name = "VERSION"))] + pub required_d_versions: Vec, } impl Opts { @@ -1102,8 +1107,12 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str(&format!("module {};\n\n", self.fqn)); + for version in &self.r#gen.opts.required_d_versions { + self.src.push_str(&format!("version({}):\n", version)); + } + self.src - .push_str(&format!("import {};\n\n", self.r#gen.common_module)); + .push_str(&format!("\nimport {};\n\n", self.r#gen.common_module)); if self.direction.is_some() && let Some(WorldKey::Interface(_)) = self.name { From 34d312407acd48ead26d920d13fb2584ed4c52e0 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sat, 25 Jul 2026 12:09:56 -0700 Subject: [PATCH 35/55] Tweak test runner --- .../test/d-test-support/{libc.d => runtime.d} | 10 ++++++ crates/test/d-test-support/walloc.d | 33 +++++++++++++++++-- crates/test/src/d.rs | 14 +++++--- 3 files changed, 50 insertions(+), 7 deletions(-) rename crates/test/d-test-support/{libc.d => runtime.d} (57%) diff --git a/crates/test/d-test-support/libc.d b/crates/test/d-test-support/runtime.d similarity index 57% rename from crates/test/d-test-support/libc.d rename to crates/test/d-test-support/runtime.d index e225ec9a7..c03ed2c9f 100644 --- a/crates/test/d-test-support/libc.d +++ b/crates/test/d-test-support/runtime.d @@ -19,3 +19,13 @@ private int memcmp(const void* ptr1, const void* ptr2, size_t size) return 0; } + +void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsz) +{ + import ldc.intrinsics : llvm_memcpy; + + //enforceRawArraysConformable("copy", elemsz, src[0..srclen], dst[0..dstlen]); + assert(srclen == dstlen); + + llvm_memcpy!size_t(dst, src, dstlen * elemsz, 0); +} diff --git a/crates/test/d-test-support/walloc.d b/crates/test/d-test-support/walloc.d index fca63c6a8..b0b7ccf61 100644 --- a/crates/test/d-test-support/walloc.d +++ b/crates/test/d-test-support/walloc.d @@ -1,4 +1,6 @@ // From https://github.com/Inochi2D/numem/blob/main/modules/hookset-wasm/source/walloc.d +// Modified to include double-free detection + /** A small malloc implementation for use in WebAssembly targets @@ -10,6 +12,7 @@ (See accompanying LICENSE file or copy at https://github.com/wingo/walloc/blob/master/LICENSE.md) */ + module walloc; import ldc.intrinsics : llvm_wasm_memory_grow, @@ -18,18 +21,44 @@ import ldc.intrinsics : extern(C) @nogc nothrow: +/// MODIFIED FOR wit-bindgen TESTS +enum MAX_ALLOCATIONS = 32; +void*[MAX_ALLOCATIONS] activePointers; +/// END + void* malloc(size_t size) @nogc nothrow @system { if (size == 0) return null; size_t granules = size_to_granules(size); chunk_kind kind = granules_to_chunk_kind(granules); - return (kind == chunk_kind.LARGE_OBJECT) ? allocate_large(size) : allocate_small(kind); + + /// MODIFIED FOR wit-bindgen TESTS + auto result = (kind == chunk_kind.LARGE_OBJECT) ? allocate_large(size) : allocate_small(kind); + assert(result !is null); + foreach (ref ptr; activePointers) { + if (ptr !is null) continue; + ptr = result; + return result; + } + assert(0); + /// END } export void free(void *ptr) @nogc nothrow @system { - if (!ptr) return; + /// MODIFIED FOR wit-bindgen TESTS + assert(ptr !is null); + + bool found = false; + foreach (ref existingPtr; activePointers) { + if (ptr !is existingPtr) continue; + existingPtr = null; + found = true; + break; + } + assert(found); + /// END _page_t* page = get_page(ptr); size_t chunk = get_chunk_index(ptr); diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index 537aeba9f..53d5da48a 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -33,6 +33,9 @@ impl LanguageMethods for D { config.async_ || config.error_context || name == "map.wit" } + fn default_bindgen_args(&self) -> &[&str] { + &["--self-contained"] + } fn default_bindgen_args_for_codegen(&self) -> &[&str] { &["--emit-export-stubs"] } @@ -110,8 +113,8 @@ fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result< let output = compile.output.with_extension("core.wasm"); std::fs::write( - compile.artifacts_dir.join("libc.d"), - include_bytes!("../d-test-support/libc.d"), + compile.artifacts_dir.join("runtime.d"), + include_bytes!("../d-test-support/runtime.d"), )?; std::fs::write( compile.artifacts_dir.join("walloc.d"), @@ -119,19 +122,20 @@ fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result< )?; cmd.arg(&compile.component.path) - .arg("-betterC") + .arg("-betterC") // don't allow features needing DRuntime .arg("-mtriple=wasm32-unknown-unknown") - .arg("-fvisibility=hidden") // important to make sure unused symbols don't get linked .arg("-I") .arg(&compile.bindings_dir) .arg("-i") // compile included dependencies .arg("--de") // deperecations are errors .arg("-w") // warnings are errors .arg("-L--no-entry") + .arg("-L--no-export-dynamic") // important to make sure unused symbols don't get linked .arg("--checkaction=halt") // to trap instead of using libc __assert + .arg("-g") // debug info .arg("-of") .arg(&output) - .arg(compile.artifacts_dir.join("libc.d")) + .arg(compile.artifacts_dir.join("runtime.d")) .arg(compile.artifacts_dir.join("walloc.d")); runner.run_command(&mut cmd)?; From 28958f3b337f778208153a0273d5bfb7422cf781 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sat, 25 Jul 2026 12:13:01 -0700 Subject: [PATCH 36/55] Fix memory leaks --- crates/d/src/lib.rs | 56 ++++++++++++++++++++++++++++++-- crates/d/src/wit_common.d | 59 ++++++++++++++++++++++++++++++++-- tests/runtime/flavorful/test.d | 5 ++- 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 6159e71f1..d021cb464 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1312,8 +1312,18 @@ impl<'a> DInterfaceGenerator<'a> { ); let ret_area_decl = f.emit_ret_area_if_needed(); - let FunctionBindgen { src, .. } = f; + let FunctionBindgen { + src, + needs_deallocate, + .. + } = f; self.src.push_str(&ret_area_decl); + if needs_deallocate { + self.src.push_str(&format!( + "{}.DeallocateBuffer deallocate;\n", + self.r#gen.common_module + )); + } self.src.push_str(&src); self.src.push_str("}\n"); @@ -1503,6 +1513,7 @@ impl<'a> DInterfaceGenerator<'a> { src, return_pointer_area_size, return_pointer_area_align, + needs_deallocate, .. } = f; self.return_pointer_area_size = self.return_pointer_area_size.max(return_pointer_area_size); @@ -1511,6 +1522,12 @@ impl<'a> DInterfaceGenerator<'a> { .max(return_pointer_area_align); self.src.push_str(&ret_area_decl); + if needs_deallocate { + self.src.push_str(&format!( + "{}.DeallocateBuffer deallocate;\n", + self.r#gen.common_module + )); + } self.src.push_str(&src); self.src.push_str("}\n"); @@ -2355,6 +2372,7 @@ struct FunctionBindgen<'a, 'b> { payloads: Vec, return_pointer_area_size: ArchitectureSize, return_pointer_area_align: Alignment, + needs_deallocate: bool, } fn tempname(base: &str, idx: usize) -> String { @@ -2373,6 +2391,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { payloads: Default::default(), return_pointer_area_size: Default::default(), return_pointer_area_align: Default::default(), + needs_deallocate: false, } } @@ -2613,11 +2632,15 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!( "auto {list_src} = {}; - auto {list} = {}.malloc({list_src}.length * ({size_str})); - scope(exit) {{ {1}.free({list}); }}\n", + auto {list} = {}.malloc({list_src}.length * ({size_str}));\n", operands[0], self.r#gen.r#gen.common_module )); + if matches!(self.r#gen.direction, Some(Direction::Import)) { + self.needs_deallocate = true; + self.push_str(&format!("deallocate ~= {list};\n")); + } + self.push_str(&format!( "foreach ({block_element}_idx, const ref {block_element}; {list_src}) {{\n" )); @@ -2628,6 +2651,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { //self.push_str(&format!("_targetElem = {};", body.1[0])); self.push_str("\n}\n"); + if !matches!(self.r#gen.direction, Some(Direction::Import)) { + self.push_str(&format!( + "{}.free({list_src}.ptr);\n", + self.r#gen.r#gen.common_module + )); + } + results.push(format!("{list}")); results.push(format!("{}.length", operands[0])); } @@ -2686,6 +2716,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.r#gen.r#gen.common_module )); + if matches!(self.r#gen.direction, Some(Direction::Export)) { + self.needs_deallocate = true; + self.push_str(&format!("deallocate ~= cast(void*){list}.ptr;\n")); + } + self.push_str(&format!( "foreach ({block_element}_idx, ref {block_element}; {list}) {{\n", )); @@ -2696,6 +2731,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("{block_element} = {};", block_results[0])); self.push_str("\n}\n"); + if !matches!(self.r#gen.direction, Some(Direction::Export)) { + self.push_str(&format!( + "{}.free({list_src});\n", + self.r#gen.r#gen.common_module + )); + } + let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); results.push(format!("{list_name}({list})")); } @@ -3175,6 +3217,10 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { "__import_{escaped_name}({});\n", operands.iter().cloned().collect::>().join(", ") )); + + if self.needs_deallocate { + self.push_str(&format!("deallocate.purge();\n")); + } } abi::Instruction::CallInterface { func, async_ } => { if *async_ { @@ -3227,6 +3273,10 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .join(", "), ); self.src.push_str(");\n"); + + if self.needs_deallocate { + self.push_str(&format!("deallocate.purge();\n")); + } } abi::Instruction::Return { amt, .. } => match amt { 0 => {} diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index f57306bb9..468ff31cf 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -352,9 +352,10 @@ auto ref T reinterpretCast(T, U)(auto ref U from) @trusted if (T.sizeof == U.siz return tmp(from).to; } -auto mallocSlice(T)(size_t count) @nogc nothrow { +T[] mallocSlice(T)(size_t count) @nogc nothrow { + if (count == 0) return []; auto ptr = malloc(count*T.sizeof); - if (ptr is null) return null; + if (ptr is null) return []; return (cast(T*)ptr)[0..count]; } @@ -444,6 +445,60 @@ template witExportsIn(T) { } } +struct DeallocateBuffer { + @nogc nothrow: + struct Page { + void*[32] slots; + static assert(slots.length < 256); + + ubyte cursor; + Page* next; + } + + Page first; + Page* head; + + @disable this(this); + + private void allocNewPage() { + Page* page = cast(Page*)malloc(Page.sizeof); + if (page is null) abort(); + + *page = Page.init; + page.next = head; + head = page; + } + + void opOpAssign(string op: "~")(void* ptr) { + import core.builtins : unlikely; + + if (unlikely(ptr is null)) return; + if (/*unlikely?*/(head is null)) head = &first; + + if (head.cursor >= head.slots.length) allocNewPage(); + + head.slots[head.cursor++] = ptr; + } + + void purge() { + auto page = head; + while (page) { + foreach (ptr; page.slots[0..page.cursor]) free(ptr); + + auto next = page.next; + if (page != &first) free(page); + page = next; + } + + first = Page.init; + head = null; + } + + ~this() { + purge(); + } +} + @wasmExport!("cabi_realloc") void* cabi_realloc(void *ptr, size_t oldSize, size_t alignment, size_t newSize) { if (newSize == 0) return cast(void*)alignment; diff --git a/tests/runtime/flavorful/test.d b/tests/runtime/flavorful/test.d index 630cfa589..4adf93dc5 100644 --- a/tests/runtime/flavorful/test.d +++ b/tests/runtime/flavorful/test.d @@ -63,7 +63,10 @@ Tuple!(ListTypedef2, ListTypedef3) listTypedefs(in ListTypedef a, in ListTypedef cast(WitString)"typedef4".witList ]; - return (const Tuple!(ListTypedef2, ListTypedef3)((cast(immutable ubyte[])"typedef3").witList, strings[].witList)).witClone; + return (const Tuple!(ListTypedef2, ListTypedef3)( + (cast(immutable ubyte[])"typedef3").witList, + strings[].witList + )).witClone; } From e62ceb243d1b1194d235151dd37f462e8f390f6a Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sat, 25 Jul 2026 22:08:02 -0700 Subject: [PATCH 37/55] Fixes for 0.60.0 --- crates/d/src/lib.rs | 8 +++++--- crates/test/src/d.rs | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index d021cb464..395483f06 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -379,7 +379,7 @@ impl WorldGenerator for D { false } - fn preprocess(&mut self, resolve: &Resolve, world_id: WorldId) { + fn preprocess(&mut self, resolve: &Resolve, world_id: WorldId) -> Result<()> { self.root_pkg = self.opts.root_package.as_deref().unwrap_or("wit").into(); self.common_module = if self.opts.self_contained { format!("{}.common", self.root_pkg) @@ -476,6 +476,8 @@ impl WorldGenerator for D { _ => {} } } + + Ok(()) } fn import_interface( @@ -2642,7 +2644,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } self.push_str(&format!( - "foreach ({block_element}_idx, const ref {block_element}; {list_src}) {{\n" + "foreach ({block_element}_idx, ref {block_element}; {list_src}) {{\n" )); self.push_str(&format!( "auto {base} = {list} + {block_element}_idx * ({size_str});\n" @@ -2772,7 +2774,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let size_str = self.r#gen.sizes.size(element).format("size_t.sizeof"); self.push_str(&format!( - "foreach ({block_element}_idx, const ref {block_element}; {arr_src}) {{\n" + "foreach ({block_element}_idx, ref {block_element}; {arr_src}) {{\n" )); self.push_str(&format!( "const auto {base} = {arr_dst} + {block_element}_idx * {size_str};\n" diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index 53d5da48a..c7129bf67 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -26,11 +26,12 @@ impl LanguageMethods for D { fn should_fail_verify( &self, + _runner: &Runner, name: &str, config: &crate::config::WitConfig, _args: &[String], ) -> bool { - config.async_ || config.error_context || name == "map.wit" + config.async_ || config.error_context || name == "map.wit" || name == "issue1642.wit" } fn default_bindgen_args(&self) -> &[&str] { From f1961c2984caa0bbe43d109c2d595c74821699c6 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 28 Jul 2026 00:56:44 -0700 Subject: [PATCH 38/55] Use LDC 1.42, etc. --- .github/workflows/main.yml | 2 +- crates/d/src/lib.rs | 10 ++++++---- crates/test/d-test-support/walloc.d | 13 ++++++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 55356a56c..de1337521 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -124,7 +124,7 @@ jobs: - name: Setup D uses: dlang-community/setup-dlang@v2 with: - compiler: ldc-1.41 + compiler: ldc-1.42 if: matrix.lang == 'd' # Hacky work-around for https://github.com/dotnet/runtime/issues/80619 diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 395483f06..263be7ca6 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -2634,7 +2634,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!( "auto {list_src} = {}; - auto {list} = {}.malloc({list_src}.length * ({size_str}));\n", + auto {list} = {list_src}.length ? {}.malloc({list_src}.length * ({size_str})) : null; + assert({list_src}.length || {list});\n", operands[0], self.r#gen.r#gen.common_module )); @@ -2655,7 +2656,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { if !matches!(self.r#gen.direction, Some(Direction::Import)) { self.push_str(&format!( - "{}.free({list_src}.ptr);\n", + "if ({list_src}.length) {}.free({list_src}.ptr);\n", self.r#gen.r#gen.common_module )); } @@ -2714,7 +2715,8 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("auto {list_src} = {};\n", operands[0])); self.push_str(&format!("auto {list_len} = {};\n", operands[1])); self.push_str(&format!( - "auto {list} = {}.mallocSlice!({elem_type_name})({list_len});\n", + "auto {list} = {list_len} ? {}.mallocSlice!({elem_type_name})({list_len}) : []; + assert({list_len} || {list}.ptr);\n", self.r#gen.r#gen.common_module )); @@ -2735,7 +2737,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { if !matches!(self.r#gen.direction, Some(Direction::Export)) { self.push_str(&format!( - "{}.free({list_src});\n", + "if ({list_len}) {}.free({list_src});\n", self.r#gen.r#gen.common_module )); } diff --git a/crates/test/d-test-support/walloc.d b/crates/test/d-test-support/walloc.d index b0b7ccf61..f85195d82 100644 --- a/crates/test/d-test-support/walloc.d +++ b/crates/test/d-test-support/walloc.d @@ -23,7 +23,11 @@ extern(C) @nogc nothrow: /// MODIFIED FOR wit-bindgen TESTS enum MAX_ALLOCATIONS = 32; -void*[MAX_ALLOCATIONS] activePointers; +extern(D) void*[MAX_ALLOCATIONS] activePointers; +extern(D) size_t[MAX_ALLOCATIONS] activeAllocSizes; + +// extern(C) to make it "public" for the `lists` test +extern(C) size_t walloc_allocated_bytes = 0; /// END void* malloc(size_t size) @nogc nothrow @system { @@ -36,9 +40,11 @@ void* malloc(size_t size) @nogc nothrow @system { /// MODIFIED FOR wit-bindgen TESTS auto result = (kind == chunk_kind.LARGE_OBJECT) ? allocate_large(size) : allocate_small(kind); assert(result !is null); - foreach (ref ptr; activePointers) { + foreach (i, ref ptr; activePointers) { if (ptr !is null) continue; ptr = result; + activeAllocSizes[i] = size; + walloc_allocated_bytes += size; return result; } assert(0); @@ -51,9 +57,10 @@ void free(void *ptr) @nogc nothrow @system { assert(ptr !is null); bool found = false; - foreach (ref existingPtr; activePointers) { + foreach (i, ref existingPtr; activePointers) { if (ptr !is existingPtr) continue; existingPtr = null; + walloc_allocated_bytes -= activeAllocSizes[i]; found = true; break; } From aeceb79056e21696475202395e22ccce59b71075 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 28 Jul 2026 00:59:04 -0700 Subject: [PATCH 39/55] Clippy --- crates/d/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 263be7ca6..f3f043889 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1110,7 +1110,7 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str(&format!("module {};\n\n", self.fqn)); for version in &self.r#gen.opts.required_d_versions { - self.src.push_str(&format!("version({}):\n", version)); + self.src.push_str(&format!("version({version}):\n")); } self.src From 65eee4c3d923f9d9a626b75bb83b38229fd85178 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 28 Jul 2026 21:19:42 -0700 Subject: [PATCH 40/55] self-contained only, fix malloc asserts, witClone, and `variant` lower --- crates/d/src/lib.rs | 30 ++++++++++++++++-------------- crates/d/src/wit_common.d | 4 +++- crates/test/src/d.rs | 3 --- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index f3f043889..c51d795cb 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -75,6 +75,8 @@ pub struct Opts { #[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))] pub root_package: Option, + // TODO: find new home for wit_common; dub package? + /* /// Whether the generated bindings should be self-contained /// /// Instead of relying on DRuntime (and wasi-libc) to define @@ -82,7 +84,7 @@ pub struct Opts { /// emitted alongside the bindings. #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] pub self_contained: bool, - + */ /// A series of D versions that all the generated bindings /// will be gated behind. #[cfg_attr(feature = "clap", arg(long, value_name = "VERSION"))] @@ -381,11 +383,7 @@ impl WorldGenerator for D { fn preprocess(&mut self, resolve: &Resolve, world_id: WorldId) -> Result<()> { self.root_pkg = self.opts.root_package.as_deref().unwrap_or("wit").into(); - self.common_module = if self.opts.self_contained { - format!("{}.common", self.root_pkg) - } else { - "core.sys.wasi.wit_common".into() - }; + self.common_module = format!("{}.common", self.root_pkg); self.world_fqn = get_world_fqn(&self.root_pkg, world_id, resolve); self.world_id = Some(world_id); @@ -937,11 +935,10 @@ impl WorldGenerator for D { files.push(world_filepath.to_str().unwrap(), world_src.as_bytes()); - if self.opts.self_contained { - let mut wit_common_file = format!("module {};\n\n", self.common_module).into_bytes(); - wit_common_file.extend_from_slice(include_bytes!("wit_common.d").as_slice()); - files.push("wit/common.d", &wit_common_file); - } + let mut wit_common_file = format!("module {};\n\n", self.common_module).into_bytes(); + wit_common_file.extend_from_slice(include_bytes!("wit_common.d").as_slice()); + files.push("wit/common.d", &wit_common_file); + Ok(()) } } @@ -2635,7 +2632,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!( "auto {list_src} = {}; auto {list} = {list_src}.length ? {}.malloc({list_src}.length * ({size_str})) : null; - assert({list_src}.length || {list});\n", + assert(!{list_src}.length || {list});\n", operands[0], self.r#gen.r#gen.common_module )); @@ -2716,7 +2713,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("auto {list_len} = {};\n", operands[1])); self.push_str(&format!( "auto {list} = {list_len} ? {}.mallocSlice!({elem_type_name})({list_len}) : []; - assert({list_len} || {list}.ptr);\n", + assert(!{list_len} || {list}.ptr);\n", self.r#gen.r#gen.common_module )); @@ -2962,7 +2959,12 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { if let Some(ty) = case.ty.as_ref() { let ty_name = self.r#gen.type_name(ty, self.r#gen.fqn); self.push_str(&format!( - "const ref {ty_name} {payload} = {}.get{upper_escaped_name}();\n", + "{}ref {ty_name} {payload} = {}.get{upper_escaped_name}();\n", + if matches!(self.r#gen.direction, Some(Direction::Import)) { + "const " + } else { + "" + }, operands[0], )); } diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 468ff31cf..3b4041fff 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -311,7 +311,7 @@ void witFree(T : Tuple!U, U...)(scope ref T val) { } } T witClone(T : Tuple!U, U...)(in T val) { - T clone; + T clone = void; static foreach (F; T.tupleof) { __traits(child, clone, F) = __traits(child, val, F).witClone; } @@ -499,6 +499,8 @@ struct DeallocateBuffer { } } +version (CRuntime_WASI) {} +else @wasmExport!("cabi_realloc") void* cabi_realloc(void *ptr, size_t oldSize, size_t alignment, size_t newSize) { if (newSize == 0) return cast(void*)alignment; diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index c7129bf67..8870be079 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -34,9 +34,6 @@ impl LanguageMethods for D { config.async_ || config.error_context || name == "map.wit" || name == "issue1642.wit" } - fn default_bindgen_args(&self) -> &[&str] { - &["--self-contained"] - } fn default_bindgen_args_for_codegen(&self) -> &[&str] { &["--emit-export-stubs"] } From 1611f8e96e4e0edcaaf222089561ad2d868db792 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 29 Jul 2026 12:21:20 -0700 Subject: [PATCH 41/55] Fix more memory leaks --- crates/d/src/lib.rs | 33 +++++++++++++++++++++-------- crates/d/src/wit_common.d | 7 +++++- crates/test/d-test-support/walloc.d | 2 +- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index c51d795cb..249a7cc0d 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -2638,7 +2638,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { if matches!(self.r#gen.direction, Some(Direction::Import)) { self.needs_deallocate = true; - self.push_str(&format!("deallocate ~= {list};\n")); + self.push_str(&format!("if ({list_src}.length) deallocate ~= {list};\n")); } self.push_str(&format!( @@ -2671,12 +2671,17 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let len = tempname("_len", tmp); self.push_str(&format!( - "auto {ptr} = cast({elem_name}*)({}); - auto {len} = {}; + "auto {len} = {}; + auto {ptr} = {len} ? cast({elem_name}*)({}) : null; ", - operands[0], operands[1] + operands[1], operands[0] )); + if matches!(self.r#gen.direction, Some(Direction::Export)) { + self.needs_deallocate = true; + self.push_str(&format!("if ({len}) deallocate ~= cast(){ptr};\n")); + } + results.push(format!("{list_name}({ptr}[0..{len}])")); } abi::Instruction::StringLift => { @@ -2686,12 +2691,17 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let len = tempname("_len", tmp); self.push_str(&format!( - "auto {ptr} = cast(char*)({}); - auto {len} = {}; + "auto {len} = {}; + auto {ptr} = {len} ? cast(char*)({}) : null; ", - operands[0], operands[1] + operands[1], operands[0] )); + if matches!(self.r#gen.direction, Some(Direction::Export)) { + self.needs_deallocate = true; + self.push_str(&format!("if ({len}) deallocate ~= cast(){ptr};\n")); + } + results.push(format!("WitString({ptr}[0..{len}])")); } abi::Instruction::ListLift { ty, element, .. } => { @@ -2719,7 +2729,9 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { if matches!(self.r#gen.direction, Some(Direction::Export)) { self.needs_deallocate = true; - self.push_str(&format!("deallocate ~= cast(void*){list}.ptr;\n")); + self.push_str(&format!( + "if ({list_len}) deallocate ~= cast(void*){list}.ptr;\n" + )); } self.push_str(&format!( @@ -2732,11 +2744,14 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("{block_element} = {};", block_results[0])); self.push_str("\n}\n"); - if !matches!(self.r#gen.direction, Some(Direction::Export)) { + if matches!(self.r#gen.direction, Some(Direction::Import)) { self.push_str(&format!( "if ({list_len}) {}.free({list_src});\n", self.r#gen.r#gen.common_module )); + } else { + self.needs_deallocate = true; + self.push_str(&format!("if ({list_len}) deallocate ~= {list_src};\n")); } let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 3b4041fff..796d92d84 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -499,7 +499,12 @@ struct DeallocateBuffer { } } -version (CRuntime_WASI) {} +version (CRuntime_WASI) { + version (WASIp1) {} + else version = LibcDefinesCABIRealloc; +} + +version (LibcDefinesCABIRealloc) {} else @wasmExport!("cabi_realloc") void* cabi_realloc(void *ptr, size_t oldSize, size_t alignment, size_t newSize) { diff --git a/crates/test/d-test-support/walloc.d b/crates/test/d-test-support/walloc.d index f85195d82..83bbc3147 100644 --- a/crates/test/d-test-support/walloc.d +++ b/crates/test/d-test-support/walloc.d @@ -22,7 +22,7 @@ import ldc.intrinsics : extern(C) @nogc nothrow: /// MODIFIED FOR wit-bindgen TESTS -enum MAX_ALLOCATIONS = 32; +enum MAX_ALLOCATIONS = 2048; extern(D) void*[MAX_ALLOCATIONS] activePointers; extern(D) size_t[MAX_ALLOCATIONS] activeAllocSizes; From a04fe0517099106f43c94c1ad4d4b90641d1ff2e Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 29 Jul 2026 21:23:45 -0700 Subject: [PATCH 42/55] Add `tuple` helper. --- crates/d/src/wit_common.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 796d92d84..67d155aa7 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -50,6 +50,8 @@ struct Tuple(Types...) if (is(Types)) { alias expand this; } +inout(Tuple!Types) tuple(Types...)(inout Types vals) => inout Tuple!Types(vals); + mixin template WitFlags(T) if (__traits(isUnsigned, T)) { private alias F = typeof(this); From df69eb43eb2e158f5663424283d8d374b8278baa Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 29 Jul 2026 21:35:49 -0700 Subject: [PATCH 43/55] `runtime` tests up through `lists` --- tests/runtime/fixed-length-lists/test.d | 8 +- tests/runtime/flavorful/test.d | 8 +- tests/runtime/gated-features/runner.d | 14 + tests/runtime/gated-features/test.d | 15 + tests/runtime/list-in-variant/runner.d | 88 +++++ tests/runtime/list-in-variant/test.d | 94 ++++++ tests/runtime/lists/runner.d | 422 ++++++++++++++++++++++++ tests/runtime/lists/test.d | 122 +++++++ 8 files changed, 763 insertions(+), 8 deletions(-) create mode 100644 tests/runtime/gated-features/runner.d create mode 100644 tests/runtime/gated-features/test.d create mode 100644 tests/runtime/list-in-variant/runner.d create mode 100644 tests/runtime/list-in-variant/test.d create mode 100644 tests/runtime/lists/runner.d create mode 100644 tests/runtime/lists/test.d diff --git a/tests/runtime/fixed-length-lists/test.d b/tests/runtime/fixed-length-lists/test.d index 253310987..88a201de7 100644 --- a/tests/runtime/fixed-length-lists/test.d +++ b/tests/runtime/fixed-length-lists/test.d @@ -19,13 +19,13 @@ void listParam3(in int[20] a) { @witExport("test:fixed-length-lists/to-test", "list-minmax16") Tuple!(ushort[4], short[4]) listMinmax16(in ushort[4] a, in short[4] b) { - return Tuple!(ushort[4], short[4])(a, b); + return tuple(a, b); } @witExport("test:fixed-length-lists/to-test", "list-minmax-float") Tuple!(float[2], double[2]) listMinmaxFloat(in float[2] a, in double[2] b) { - return Tuple!(float[2], double[2])(a, b); + return tuple(a, b); } @witExport("test:fixed-length-lists/to-test", "list-roundtrip") @@ -36,12 +36,12 @@ ubyte[8] listResult() => ['0', '1', 'A', 'B', 'a', 'b', 128, 255]; @witExport("test:fixed-length-lists/to-test", "nested-roundtrip") Tuple!(uint[2][2], int[2][2]) nestedRoundtrip(in uint[2][2] a, in int[2][2] b) { - return Tuple!(uint[2][2], int[2][2])(a, b); + return tuple(a, b); } @witExport("test:fixed-length-lists/to-test", "large-roundtrip") Tuple!(uint[2][2], int[4][4]) largeRoundtrip(in uint[2][2] a, in int[4][4] b) { - return Tuple!(uint[2][2], int[4][4])(a, b); + return tuple(a, b); } @witExport("test:fixed-length-lists/to-test", "nightmare-on-cpp") diff --git a/tests/runtime/flavorful/test.d b/tests/runtime/flavorful/test.d index 4adf93dc5..d1b25e209 100644 --- a/tests/runtime/flavorful/test.d +++ b/tests/runtime/flavorful/test.d @@ -63,10 +63,10 @@ Tuple!(ListTypedef2, ListTypedef3) listTypedefs(in ListTypedef a, in ListTypedef cast(WitString)"typedef4".witList ]; - return (const Tuple!(ListTypedef2, ListTypedef3)( + return tuple( (cast(immutable ubyte[])"typedef3").witList, strings[].witList - )).witClone; + ).witClone; } @@ -85,11 +85,11 @@ Tuple!(WitList!bool, WitList!(Result!()), WitList!MyErrno) listOfVariants(in Wit static immutable bool[] boolsOut = [false, true]; static immutable Result!(void)[] resultsOut = [Result!().err, Result!().ok]; static immutable MyErrno[] enumsOut = [MyErrno.a, MyErrno.b]; - return (const Tuple!(WitList!bool, WitList!(Result!()), WitList!MyErrno)( + return tuple( boolsOut.witList, resultsOut.witList, enumsOut.witList - )).witClone; + ).witClone; } alias Exports = wit.test.flavorful.test.Exports!( diff --git a/tests/runtime/gated-features/runner.d b/tests/runtime/gated-features/runner.d new file mode 100644 index 000000000..14c95ca9f --- /dev/null +++ b/tests/runtime/gated-features/runner.d @@ -0,0 +1,14 @@ +//@ args = '--features y' + +import wit.foo.bar.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + y(); + z(); +} + +alias Exports = wit.foo.bar.runner.Exports!( + run +); diff --git a/tests/runtime/gated-features/test.d b/tests/runtime/gated-features/test.d new file mode 100644 index 000000000..39a1c7eff --- /dev/null +++ b/tests/runtime/gated-features/test.d @@ -0,0 +1,15 @@ +//@ args = '--features y' + +import wit.foo.bar.test; +import wit.common; + +@witExport("foo:bar/bindings@1.2.3", "y") +void y() {} + +@witExport("foo:bar/bindings@1.2.3", "z") +void z() {} + +alias Exports = wit.foo.bar.test.Exports!( + y, + z +); diff --git a/tests/runtime/list-in-variant/runner.d b/tests/runtime/list-in-variant/runner.d new file mode 100644 index 000000000..e273c499d --- /dev/null +++ b/tests/runtime/list-in-variant/runner.d @@ -0,0 +1,88 @@ +import wit.test.list_in_variant.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + const WitString[2] hw = ["hello".witList, "world".witList]; + { + auto result = listInOption(some(hw[].witList)); + scope(exit) result.witFree; + + assert(result == "hello,world"); + } + { + auto result = listInOption(none!(WitList!WitString)); + scope(exit) result.witFree; + + assert(result == "none"); + } + + const WitString[3] fbb_data = ["foo".witList, "bar".witList, "baz".witList]; + auto fbb = PayloadOrEmpty.withData(fbb_data.witList); + { + auto result = listInVariant(fbb); + scope(exit) result.witFree; + + assert(result == "foo,bar,baz"); + } + { + auto result = listInVariant(PayloadOrEmpty.empty); + scope(exit) result.witFree; + + assert(result == "empty"); + } + + const WitString[3] abc = ["a".witList, "b".witList, "c".witList]; + { + auto result = listInResult(Result!(WitList!WitString, WitString).ok(abc[].witList)); + scope(exit) result.witFree; + + assert(result == "a,b,c"); + } + { + auto result = listInResult(Result!(WitList!WitString, WitString).err("oops".witList)); + scope(exit) result.witFree; + + assert(result == "err:oops"); + } + + const WitString[2] hw2 = ["hello".witList, "world".witList]; + auto s1 = listInOptionWithReturn(some(hw2.witList)); + { + auto result = s1.count; + scope(exit) result.witFree; + + assert(result == 2); + } + { + auto result = s1.label; + scope(exit) result.witFree; + + assert(result == "hello,world"); + } + auto s2 = listInOptionWithReturn(none!(WitList!WitString)); + { + auto result = s2.count; + scope(exit) result.witFree; + + assert(result == 0); + } + { + auto result = s2.label; + scope(exit) result.witFree; + + assert(result == "none"); + } + + const WitString[3] xyz = ["x".witList, "y".witList, "z".witList]; + { + auto result = topLevelList(xyz.witList); + scope(exit) result.witFree; + + assert(result == "x,y,z"); + } +} + +alias Exports = wit.test.list_in_variant.runner.Exports!( + run +); diff --git a/tests/runtime/list-in-variant/test.d b/tests/runtime/list-in-variant/test.d new file mode 100644 index 000000000..c1946a57c --- /dev/null +++ b/tests/runtime/list-in-variant/test.d @@ -0,0 +1,94 @@ +import wit.test.list_in_variant.test; +import wit.common; + +// Allocates directly with `malloc`, so no witClone needed. +extern(C) void* malloc(size_t size); + +char[] commaJoin(in WitString[] strs) { + if (strs.length == 0) return null; + + size_t total = 0; + foreach (i, str; strs) { + total += str.length; + + if (i+1 != strs.length) { + total += 1; // comma + } + } + + void* ptr = malloc(total); + assert(ptr); + char[] chars = cast(char[])ptr[0..total]; + + size_t cursor = 0; + foreach (i, str; strs) { + foreach (chr; str) { + chars[cursor++] = chr; + } + + if (i+1 != strs.length) { + chars[cursor++] = ','; + } + } + + return chars; +} + +@witExport("test:list-in-variant/to-test", "list-in-option") +WitString listInOption(in Option!(WitList!WitString) data) { + if (data.isSome) { + return data.unwrap.commaJoin.witList; // no clone + } + return "none".witList.witClone; +} + +@witExport("test:list-in-variant/to-test", "list-in-variant") +WitString listInVariant(in PayloadOrEmpty data) { + if (data.isWithData) { + return data.getWithData.commaJoin.witList; // no clone + } + return "empty".witList.witClone; +} + +@witExport("test:list-in-variant/to-test", "list-in-result") +WitString listInResult(in Result!(WitList!WitString, WitString) data) { + if (data.isOk) { + return data.unwrap.commaJoin.witList; + } + + + auto errStr = data.unwrapErr; + void* ptr = malloc(errStr.length+4); + assert(ptr); + char[] chars = cast(char[])ptr[0..errStr.length+4]; + + chars[0..4] = "err:"; + foreach (i, ref chr; chars[4..$]) { + chr = errStr[i]; + } + + return chars.witList; // no clone +} + +@witExport("test:list-in-variant/to-test", "list-in-option-with-return") +Summary listInOptionWithReturn(in Option!(WitList!WitString) data) { + if (data.isSome) { + auto items = data.unwrap(); + return Summary(items.length, items.commaJoin.witList); // no clone + } + + return Summary(0, "none".witList.witClone); +} + +@witExport("test:list-in-variant/to-test", "top-level-list") +WitString topLevelList(in WitList!WitString data) { + return data.commaJoin.witList; // no clone +} + +alias Exports = wit.test.list_in_variant.test.Exports!( + listInOption, + listInVariant, + listInResult, + listInOptionWithReturn, + topLevelList +); diff --git a/tests/runtime/lists/runner.d b/tests/runtime/lists/runner.d new file mode 100644 index 000000000..d252b9ee5 --- /dev/null +++ b/tests/runtime/lists/runner.d @@ -0,0 +1,422 @@ +import wit.test.lists.runner; +import wit.common; + +extern extern(C) size_t walloc_allocated_bytes; + +extern(C) void* malloc(size_t size); +extern(C) void free(void* ptr); + +@witExport("$root", "run") +void run() { + auto allocedAtFuncStart = walloc_allocated_bytes; + auto allocedAtFuncStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtFuncStart + && allocatedBytes == allocedAtFuncStart2 + ); + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + emptyListParam(WitList!ubyte()); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + emptyStringParam("".witList); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + assert(!emptyListResult().length); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + assert(!emptyStringResult().length); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ubyte[4] inputs = [1, 2, 3, 4]; + listParam(inputs.witList); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + listParam2("foo".witList); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + immutable WitString[3] inputs = ["foo".witList, "bar".witList, "baz".witList]; + listParam3(inputs.witList); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + immutable WitString[2] inputs = ["foo".witList, "bar".witList]; + immutable WitString[1] inputs2 = ["baz".witList]; + + immutable WitList!WitString[2] inputs3 = [inputs.witList, inputs2.witList]; + listParam4(inputs3.witList); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + immutable Tuple!(ubyte, uint, ubyte)[2] inputs = [ + tuple(ubyte(1), uint(2), ubyte(3)), + tuple(ubyte(4), uint(5), ubyte(6)) + ]; + + listParam5(inputs.witList); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + enum len = 1000; + auto ptr = cast(WitString*)malloc(WitString.sizeof*len); + assert(ptr); + scope(exit) free(ptr); + + foreach (ref str; ptr[0..len]) { + str = cast()"string".witList; + } + + listParamLarge(ptr[0..len].witList); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + auto result = listResult(); + scope(exit) result.witFree; + + static immutable ubyte[5] outputs = [1, 2, 3, 4, 5]; + assert(result == outputs); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + auto result = listResult2(); + scope(exit) result.witFree; + + assert(result == "hello!"); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + auto result = listResult3(); + scope(exit) result.witFree; + + immutable WitString[2] outputs = ["hello,".witList, "world!".witList]; + assert(result == outputs); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ubyte[0] inputs = []; + + auto result = listRoundtrip(inputs.witList); + scope(exit) result.witFree; + + assert(result == inputs); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ubyte[1] inputs = ['x']; + + auto result = listRoundtrip(inputs.witList); + scope(exit) result.witFree; + + assert(result == inputs); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ubyte[5] inputs = ['h', 'e', 'l', 'l', 'o']; + + auto result = listRoundtrip(inputs.witList); + scope(exit) result.witFree; + + assert(result == inputs); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable string input = "x"; + + auto result = stringRoundtrip(input.witList); + scope(exit) result.witFree; + + assert(result == input); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable string input = ""; + + auto result = stringRoundtrip(input.witList); + scope(exit) result.witFree; + + assert(result == input); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable string input = "hello"; + + auto result = stringRoundtrip(input.witList); + scope(exit) result.witFree; + + assert(result == input); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable string input = "hello âš‘ world"; + + auto result = stringRoundtrip(input.witList); + scope(exit) result.witFree; + + assert(result == input); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ubyte[2] inputs1 = [ubyte.min, ubyte.max]; + static immutable byte[2] inputs2 = [byte.min, byte.max]; + + auto result = listMinmax8(inputs1.witList, inputs2.witList); + scope(exit) result.witFree; + + assert(result[0] == inputs1); + assert(result[1] == inputs2); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ushort[2] inputs1 = [ushort.min, ushort.max]; + static immutable short[2] inputs2 = [short.min, short.max]; + + auto result = listMinmax16(inputs1.witList, inputs2.witList); + scope(exit) result.witFree; + + assert(result[0] == inputs1); + assert(result[1] == inputs2); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable uint[2] inputs1 = [uint.min, uint.max]; + static immutable int[2] inputs2 = [int.min, int.max]; + + auto result = listMinmax32(inputs1.witList, inputs2.witList); + scope(exit) result.witFree; + + assert(result[0] == inputs1); + assert(result[1] == inputs2); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ulong[2] inputs1 = [ulong.min, ulong.max]; + static immutable long[2] inputs2 = [long.min, long.max]; + + auto result = listMinmax64(inputs1.witList, inputs2.witList); + scope(exit) result.witFree; + + assert(result[0] == inputs1); + assert(result[1] == inputs2); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable float[2] inputs1 = [-float.infinity, float.infinity]; + static immutable double[2] inputs2 = [-double.infinity, double.infinity]; + + auto result = listMinmaxFloat(inputs1.witList, inputs2.witList); + scope(exit) result.witFree; + + assert(result[0] == inputs1); + assert(result[1] == inputs2); + } + + { + auto allocedAtScopeStart = walloc_allocated_bytes; + auto allocedAtScopeStart2 = allocatedBytes; + scope(exit) assert( + walloc_allocated_bytes == allocedAtScopeStart + && allocatedBytes == allocedAtScopeStart2 + ); + + static immutable ubyte[10] textPlain = ['t', 'e', 'x', 't', '/', 'p', 'l', 'a', 'i', 'n']; + static immutable ubyte[9] notFound = ['N', 'o', 't', ' ', 'f', 'o', 'u', 'n', 'd']; + + immutable Tuple!(WitString, WitList!ubyte)[2] headers = [ + tuple("Content-Type".witList, textPlain.witList), + tuple("Content-Length".witList, notFound.witList) + ]; + + auto result = wasiHttpHeadersRoundtrip(headers.witList); + scope(exit) result.witFree; + + assert(result[0][0] == "Content-Type"); + assert(result[0][1] == textPlain); + assert(result[1][0] == "Content-Length"); + assert(result[1][1] == notFound); + } +} + +alias Exports = wit.test.lists.runner.Exports!( + run +); diff --git a/tests/runtime/lists/test.d b/tests/runtime/lists/test.d new file mode 100644 index 000000000..a7a788106 --- /dev/null +++ b/tests/runtime/lists/test.d @@ -0,0 +1,122 @@ +import wit.test.lists.test; +import wit.common; + +@witExport("test:lists/to-test", "empty-list-param") +void emptyListParam(in WitList!ubyte a) { +} + +@witExport("test:lists/to-test", "empty-string-param") +void emptyStringParam(in WitString a) { +} + +@witExport("test:lists/to-test", "empty-list-result") +WitList!ubyte emptyListResult() { + return WitList!ubyte(); +} + +@witExport("test:lists/to-test", "empty-string-result") +WitString emptyStringResult() { + return WitString(); +} + +@witExport("test:lists/to-test", "list-param") +void listParam(in WitList!ubyte a) { +} + +@witExport("test:lists/to-test", "list-param2") +void listParam2(in WitString a) { +} + +@witExport("test:lists/to-test", "list-param3") +void listParam3(in WitList!WitString a) { +} + +@witExport("test:lists/to-test", "list-param4") +void listParam4(in WitList!(WitList!WitString) a) { +} + +@witExport("test:lists/to-test", "list-param5") +void listParam5(in WitList!(Tuple!(ubyte, uint, ubyte)) a) { +} + +@witExport("test:lists/to-test", "list-param-large") +void listParamLarge(in WitList!WitString a) { +} + +@witExport("test:lists/to-test", "list-result") +WitList!ubyte listResult() { + immutable ubyte[5] outputs = [1, 2, 3, 4, 5]; + return outputs.witList.witClone; +} + +@witExport("test:lists/to-test", "list-result2") +WitString listResult2() { + return "hello!".witList.witClone; +} + +@witExport("test:lists/to-test", "list-result3") +WitList!WitString listResult3() { + immutable WitString[2] outputs = ["hello,".witList, "world!".witList]; + return outputs.witList.witClone; +} + +template listMinmax(T, U, string suffix) { + @witExport("test:lists/to-test", "list-minmax"~suffix) + Tuple!(WitList!T, WitList!U) listMinmax(in WitList!T a, in WitList!U b) { + return tuple(a, b).witClone; + } +} + +@witExport("test:lists/to-test", "list-roundtrip") +WitList!ubyte listRoundtrip(in WitList!ubyte a) { + return a.witClone; +} + +@witExport("test:lists/to-test", "string-roundtrip") +WitString stringRoundtrip(in WitString a) { + return a.witClone; +} + +@witExport("test:lists/to-test", "wasi-http-headers-roundtrip") +WitList!(Tuple!(WitString, WitList!ubyte)) wasiHttpHeadersRoundtrip(in WitList!(Tuple!(WitString, WitList!ubyte)) a) { + return a.witClone; +} + + +extern extern(C) size_t walloc_allocated_bytes; +@witExport("test:lists/to-test", "allocated-bytes") +size_t allocatedBytes() { + return walloc_allocated_bytes; +} + + +alias Exports = wit.test.lists.test.Exports!( + emptyListParam, + emptyStringParam, + emptyListResult, + emptyStringResult, + + listParam, + listParam2, + listParam3, + listParam4, + listParam5, + listParamLarge, + listResult, + listResult2, + listResult3, + + listMinmax!(ubyte, byte, "8"), + listMinmax!(ushort, short, "16"), + listMinmax!(uint, int, "32"), + listMinmax!(ulong, long, "64"), + listMinmax!(float, double, "-float"), + + listRoundtrip, + + stringRoundtrip, + + wasiHttpHeadersRoundtrip, + + allocatedBytes +); From 7d0bb36c3751a4f4d5d6e2c4316fb343b7d149d8 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 30 Jul 2026 01:10:23 -0700 Subject: [PATCH 44/55] tests through `records` and fixes --- crates/d/src/lib.rs | 64 ++++++++++----------- crates/d/src/wit_common.d | 14 ++--- tests/runtime/lists-alias/runner.d | 16 ++++++ tests/runtime/lists-alias/test.d | 17 ++++++ tests/runtime/many-arguments/runner.d | 11 ++++ tests/runtime/many-arguments/test.d | 16 ++++++ tests/runtime/numbers/runner.d | 50 ++++++++++++++++ tests/runtime/numbers/test.d | 32 +++++++++++ tests/runtime/options/runner.d | 43 ++++++++++++++ tests/runtime/options/test.d | 42 ++++++++++++++ tests/runtime/package-with-version/runner.d | 11 ++++ tests/runtime/package-with-version/test.d | 17 ++++++ tests/runtime/records/runner.d | 47 +++++++++++++++ tests/runtime/records/test.d | 50 ++++++++++++++++ 14 files changed, 390 insertions(+), 40 deletions(-) create mode 100644 tests/runtime/lists-alias/runner.d create mode 100644 tests/runtime/lists-alias/test.d create mode 100644 tests/runtime/many-arguments/runner.d create mode 100644 tests/runtime/many-arguments/test.d create mode 100644 tests/runtime/numbers/runner.d create mode 100644 tests/runtime/numbers/test.d create mode 100644 tests/runtime/options/runner.d create mode 100644 tests/runtime/options/test.d create mode 100644 tests/runtime/package-with-version/runner.d create mode 100644 tests/runtime/package-with-version/test.d create mode 100644 tests/runtime/records/runner.d create mode 100644 tests/runtime/records/test.d diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 249a7cc0d..dc64d0e88 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1280,7 +1280,7 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str("static "); } self.src.push_str(&format!( - "{} {}({}) @nogc nothrow {{\n", + "{} {}({}) @trusted nothrow {{\n", d_sig.result, d_sig.name, d_sig @@ -1353,7 +1353,7 @@ impl<'a> DInterfaceGenerator<'a> { self.src.push_str("static "); } self.src.push_str(&format!( - "private extern(C) {} __import_{}({}) @nogc nothrow;\n", + "private extern(C) {} __import_{}({}) nothrow;\n", match wasm_sig.results.len() { 0 => "void", 1 => wasm_type(wasm_sig.results[0]), @@ -1727,11 +1727,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "struct {escaped_name} {{ - @nogc nothrow: - package({}) uint __handle = 0; - package({0}) this(uint handle) {{ + package({0}) this(uint handle) @safe @nogc nothrow {{ __handle = handle; }} @@ -1782,8 +1780,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } } - self.src - .push_str("\nvoid drop() {\n__import_drop(__handle);\n}\n"); + self.src.push_str( + "\nvoid drop() @trusted @nogc nothrow {\n__import_drop(__handle);\n}\n", + ); self.src.push_str(&format!( "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", self.wasm_import_module.unwrap(), @@ -1797,8 +1796,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { .replace("-", "_"), name.replace("-", "_") )); - self.src - .push_str("static private extern(C) void __import_drop(uint);\n\n"); + self.src.push_str( + "static private extern(C) void __import_drop(uint) @nogc nothrow;\n\n", + ); self.src.push_str("alias witFree = drop;\n"); self.src.push_str(&format!( @@ -1808,18 +1808,16 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { alias borrow this; struct Borrow {{ - @nogc nothrow: - package({}) uint __handle = 0; - package({0}) this(uint handle) {{ + package({0}) this(uint handle) @safe @nogc nothrow {{ __handle = handle; }} @disable this(); - void witFree() {{}} - Borrow witClone() const {{ return Borrow(__handle); }} + void witFree() @safe @nogc nothrow {{}} + Borrow witClone() const @safe @nogc nothrow {{ return Borrow(__handle); }} ", self.r#gen.root_pkg )); @@ -1883,11 +1881,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( "struct {escaped_name} {{ - @nogc nothrow: - package({}) uint __handle = 0; - package({0}) this(uint handle) {{ + package({0}) this(uint handle) @safe @nogc nothrow {{ __handle = handle; }} @@ -1899,6 +1895,8 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str(&format!( " static {escaped_name} makeNew(T)(scope void delegate(out T) dg) if (is(T == struct)) {{ + if (dg is null) return {escaped_name}.init; + auto ptr = cast(T*)malloc(T.sizeof); if (ptr is null) return {escaped_name}.init; @@ -1908,7 +1906,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { ", )); self.src.push_str(&format!( - "@wasmImport!(\"{}\", \"[resource-new]{}\")\n", + "@wasmImport!(\"[export]{}\", \"[resource-new]{}\")\n", self.wasm_import_module.unwrap(), name )); @@ -1924,9 +1922,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { .push_str("static private extern(C) uint __import_makeNew(void*);\n\n"); self.src - .push_str("T* rep(T)() if (is(T == struct)) {\nreturn cast(T*)__import_rep(__handle);\n}\n"); + .push_str("T* rep(T)() @nogc nothrow if (is(T == struct)) {\nreturn cast(T*)__import_rep(__handle);\n}\n"); self.src.push_str(&format!( - "@wasmImport!(\"{}\", \"[resource-rep]{}\")\n", + "@wasmImport!(\"[export]{}\", \"[resource-rep]{}\")\n", self.wasm_import_module.unwrap(), name )); @@ -1941,10 +1939,11 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src .push_str("static private extern(C) void __import_rep(uint);\n\n"); - self.src - .push_str("void drop() {\n__import_drop(__handle);\n}\n"); + self.src.push_str( + "void drop() @trusted @nogc nothrow {\n__import_drop(__handle);\n}\n", + ); self.src.push_str(&format!( - "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", + "@wasmImport!(\"[export]{}\", \"[resource-drop]{}\")\n", self.wasm_import_module.unwrap(), name )); @@ -1956,28 +1955,27 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { .replace("-", "_"), name.replace("-", "_") )); - self.src - .push_str("static private extern(C) void __import_drop(uint);\n\n"); + self.src.push_str( + "static private extern(C) void __import_drop(uint) @nogc nothrow;\n\n", + ); self.src.push_str("alias witFree = drop;\n"); self.src.push_str(&format!( "// TODO: make RAII? disable copy for the own - Borrow borrow() => Borrow(__handle); + Borrow borrow() @safe @nogc nothrow => Borrow(__handle); alias borrow this; struct Borrow {{ - @nogc nothrow: - package({}) uint __handle = 0; - package({0}) this(uint handle) {{ + package({0}) this(uint handle) @safe @nogc nothrow {{ __handle = handle; }} @disable this(); - void witFree() {{}} - Borrow witClone() const {{ return Borrow(__handle); }} + void witFree() @safe @nogc nothrow {{}} + Borrow witClone() const @safe @nogc nothrow {{ return Borrow(__handle); }} ", self.r#gen.root_pkg @@ -3106,9 +3104,9 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { bool {is_some} = ({op0}) != 0; if ({is_some}) {{ {some} - {resultname} = {type_name}.some({some_value}); + {resultname} = {type_name}.makeSome({some_value}); }} else {{ - {resultname} = {type_name}.none; + {resultname} = {type_name}.makeNone; }} " )); diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 67d155aa7..3077bcbf3 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -145,11 +145,11 @@ private: _value = value; } public: - static inout(Option) some(inout T value) @safe @nogc nothrow { + static inout(Option) makeSome(inout T value) @safe @nogc nothrow { return inout Option(true, value); } - static Option none() @safe @nogc nothrow { + static Option makeNone() @safe @nogc nothrow { return Option(false, T.init); } @@ -169,11 +169,11 @@ public: } auto some(T)(inout T value) @safe @nogc nothrow { - return Option!T.some(value); + return Option!T.makeSome(value); } auto none(T)() @safe @nogc nothrow { - return Option!T.none; + return Option!T.makeNone; } /// Based on Rust's Result @@ -256,12 +256,12 @@ void witFree(T : Option!U, U)(scope ref T val) { T witClone(T : Option!U, U)(in T val) { if (val.isSome) { static if (!is(U == void)) { - return T.some(val.unwrap.witClone); + return T.makeSome(val.unwrap.witClone); } else { - return T.some; + return T.makeSome; } } else { - return T.none; + return T.makeNone; } } diff --git a/tests/runtime/lists-alias/runner.d b/tests/runtime/lists-alias/runner.d new file mode 100644 index 000000000..282c8fe2b --- /dev/null +++ b/tests/runtime/lists-alias/runner.d @@ -0,0 +1,16 @@ +import wit.my.lists.runner; +import cat = wit.my.lists.runner.imports.cat; +import wit.common; + +@witExport("$root", "run") +void run() { + cat.foo((cast(immutable ubyte[])"hello").witList); + + WitList!ubyte t = cat.bar(); + scope(exit) t.witFree; + assert(t == (cast(immutable ubyte[])"world").witList); +} + +alias Exports = wit.my.lists.runner.Exports!( + run +); diff --git a/tests/runtime/lists-alias/test.d b/tests/runtime/lists-alias/test.d new file mode 100644 index 000000000..f0ebf6f18 --- /dev/null +++ b/tests/runtime/lists-alias/test.d @@ -0,0 +1,17 @@ +import wit.my.lists.test; +import wit.common; + +@witExport("cat", "foo") +void foo(in WitList!ubyte x) { + assert(x == (cast(immutable ubyte[])"hello").witList); +} + +@witExport("cat", "bar") +WitList!ubyte bar() { + return (cast(immutable ubyte[])"world").witList.witClone; +} + +alias Exports = wit.my.lists.test.Exports!( + foo, + bar +); diff --git a/tests/runtime/many-arguments/runner.d b/tests/runtime/many-arguments/runner.d new file mode 100644 index 000000000..0cc1df1a8 --- /dev/null +++ b/tests/runtime/many-arguments/runner.d @@ -0,0 +1,11 @@ +import wit.test.many_arguments.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + manyArguments(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); +} + +alias Exports = wit.test.many_arguments.runner.Exports!( + run +); diff --git a/tests/runtime/many-arguments/test.d b/tests/runtime/many-arguments/test.d new file mode 100644 index 000000000..053122ece --- /dev/null +++ b/tests/runtime/many-arguments/test.d @@ -0,0 +1,16 @@ +import wit.test.many_arguments.test; +import wit.common; + +import std.meta : Repeat, AliasSeq; + +@witExport("test:many-arguments/to-test", "many-arguments") +void manyArguments(Repeat!(16, ulong) args) { + assert(args == AliasSeq!( + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16 + )); +} + +alias Exports = wit.test.many_arguments.test.Exports!( + manyArguments +); diff --git a/tests/runtime/numbers/runner.d b/tests/runtime/numbers/runner.d new file mode 100644 index 000000000..336856e31 --- /dev/null +++ b/tests/runtime/numbers/runner.d @@ -0,0 +1,50 @@ +import wit.test.numbers.runner; +import wit.common; + +void doAsserts(alias func)() { + static if(is(typeof(func) P == function)) { + alias T = P[0]; + static if (is(T == dchar)) { + enum T a = 'a'; + enum T b = ' '; + enum T c = '🚩'; + } else static if (__traits(isFloating, T)) { + enum T a = 1.0; + enum T b = -T.infinity; + enum T c = T.infinity; + } else { + enum T a = 1; + enum T b = T.min; + enum T c = T.max; + } + } + + assert(func(a) == a); + assert(func(b) == b); + assert(func(c) == c); +} + +@witExport("$root", "run") +void run() { + doAsserts!roundtripU8; + doAsserts!roundtripS8; + doAsserts!roundtripU16; + doAsserts!roundtripS16; + doAsserts!roundtripU32; + doAsserts!roundtripS32; + doAsserts!roundtripU64; + doAsserts!roundtripS64; + doAsserts!roundtripF32; + doAsserts!roundtripF64; + doAsserts!roundtripChar; + + setScalar(2); + assert(getScalar() == 2); + + setScalar(4); + assert(getScalar() == 4); +} + +alias Exports = wit.test.numbers.runner.Exports!( + run +); diff --git a/tests/runtime/numbers/test.d b/tests/runtime/numbers/test.d new file mode 100644 index 000000000..4f4b27d88 --- /dev/null +++ b/tests/runtime/numbers/test.d @@ -0,0 +1,32 @@ +import wit.test.numbers.test; +import wit.common; + +template roundtrip(T, string suffix) { + @witExport("test:numbers/numbers", "roundtrip-"~suffix) + T roundtrip(T val) => val; +} + +uint scalar; + +@witExport("test:numbers/numbers", "get-scalar") +auto getScalar() => scalar; + +@witExport("test:numbers/numbers", "set-scalar") +void setScalar(uint val) { scalar = val; } + +alias Exports = wit.test.numbers.test.Exports!( + roundtrip!(ubyte, "u8"), + roundtrip!(byte, "s8"), + roundtrip!(ushort, "u16"), + roundtrip!(short, "s16"), + roundtrip!(uint, "u32"), + roundtrip!(int, "s32"), + roundtrip!(ulong, "u64"), + roundtrip!(long, "s64"), + roundtrip!(float, "f32"), + roundtrip!(double, "f64"), + roundtrip!(dchar, "char"), + + getScalar, + setScalar +); diff --git a/tests/runtime/options/runner.d b/tests/runtime/options/runner.d new file mode 100644 index 000000000..c1eb9c0d7 --- /dev/null +++ b/tests/runtime/options/runner.d @@ -0,0 +1,43 @@ +import wit.test.options.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + optionNoneParam(none!WitString); + optionSomeParam("foo".witList.some); + assert(optionNoneResult().isNone); + { + auto result = optionSomeResult(); + scope(exit) result.witFree; + + assert(result == "foo".witList.some); + } + { + auto result = optionRoundtrip("foo".witList.some); + scope(exit) result.witFree; + + assert(result == "foo".witList.some); + } + { + auto result = doubleOptionRoundtrip(uint(42).some.some); + scope(exit) result.witFree; + + assert(result == uint(42).some.some); + } + { + auto result = doubleOptionRoundtrip(none!uint.some); + scope(exit) result.witFree; + + assert(result == none!uint.some); + } + { + auto result = doubleOptionRoundtrip(none!(Option!uint)); + scope(exit) result.witFree; + + assert(result == none!(Option!uint)); + } +} + +alias Exports = wit.test.options.runner.Exports!( + run +); diff --git a/tests/runtime/options/test.d b/tests/runtime/options/test.d new file mode 100644 index 000000000..eba2ffd81 --- /dev/null +++ b/tests/runtime/options/test.d @@ -0,0 +1,42 @@ +import wit.test.options.test; +import wit.common; + +import std.meta : Repeat, AliasSeq; + +@witExport("test:options/to-test", "option-none-param") +void optionNoneParam(in Option!WitString a) { +} + +@witExport("test:options/to-test", "option-some-param") +void optionSomeParam(in Option!WitString a) { +} + +@witExport("test:options/to-test", "option-none-result") +Option!WitString optionNoneResult() { + return none!WitString; +} + +@witExport("test:options/to-test", "option-some-result") +Option!WitString optionSomeResult() { + return "foo".witList.witClone.some; +} + +@witExport("test:options/to-test", "option-roundtrip") +Option!WitString optionRoundtrip(in Option!WitString a) { + return a.witClone; +} +@witExport("test:options/to-test", "double-option-roundtrip") +Option!(Option!uint) doubleOptionRoundtrip(in Option!(Option!uint) a) { + return a.witClone; +} + + +alias Exports = wit.test.options.test.Exports!( + optionNoneParam, + optionSomeParam, + optionNoneResult, + optionSomeResult, + + optionRoundtrip, + doubleOptionRoundtrip +); diff --git a/tests/runtime/package-with-version/runner.d b/tests/runtime/package-with-version/runner.d new file mode 100644 index 000000000..b75b32784 --- /dev/null +++ b/tests/runtime/package-with-version/runner.d @@ -0,0 +1,11 @@ +import wit.my.inline.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + Bar.makeNew().drop; +} + +alias Exports = wit.my.inline.runner.Exports!( + run +); diff --git a/tests/runtime/package-with-version/test.d b/tests/runtime/package-with-version/test.d new file mode 100644 index 000000000..4d50ac49c --- /dev/null +++ b/tests/runtime/package-with-version/test.d @@ -0,0 +1,17 @@ +import wit.my.inline.test; +import wit.common; + +import std.meta : Repeat, AliasSeq; + +@witExport("my:inline/foo@0.0.0", "bar") +struct BarImpl { + @witExport("my:inline/foo@0.0.0", "[constructor]bar") + static Bar constructor() { + return Bar.makeNew((out typeof(this) self) { + }); + } +} + +alias Exports = wit.my.inline.test.Exports!( + BarImpl +); diff --git a/tests/runtime/records/runner.d b/tests/runtime/records/runner.d new file mode 100644 index 000000000..d1476e0d5 --- /dev/null +++ b/tests/runtime/records/runner.d @@ -0,0 +1,47 @@ +import wit.test.records.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + assert(multipleResults() == tuple(ubyte(4), ushort(5))); + + assert(swapTuple(tuple(ubyte(1), uint(2))) == tuple(uint(2), ubyte(1))); + assert(roundtripFlags1(F1.a) == F1.a); + assert(roundtripFlags1(F1()) == F1()); + assert(roundtripFlags1(F1.b) == F1.b); + assert(roundtripFlags1(F1.a | F1.b) == (F1.a | F1.b)); + + assert(roundtripFlags2(F2.c) == F2.c); + assert(roundtripFlags2(F2()) == F2()); + assert(roundtripFlags2(F2.d) == F2.d); + assert(roundtripFlags2(F2.c | F2.e) == (F2.c | F2.e)); + + assert( + roundtripFlags3(Flag8.b0, Flag16.b1, Flag32.b2) == + tuple(Flag8.b0, Flag16.b1, Flag32.b2) + ); + + { + auto r = roundtripRecord1(R1( + a: 8, + b: F1() + )); + assert(r.a == 8); + assert(r.b == F1()); + } + + { + auto r = roundtripRecord1(R1( + a: 0, + b: F1.a | F1.b + )); + assert(r.a == 0); + assert(r.b == (F1.a | F1.b)); + } + + assert(tuple1(tuple(ubyte(1))) == tuple(1)); +} + +alias Exports = wit.test.records.runner.Exports!( + run +); diff --git a/tests/runtime/records/test.d b/tests/runtime/records/test.d new file mode 100644 index 000000000..6bffc209b --- /dev/null +++ b/tests/runtime/records/test.d @@ -0,0 +1,50 @@ +import wit.test.records.test; +import wit.common; + +import std.meta : Repeat, AliasSeq; + +@witExport("test:records/to-test", "multiple-results") +Tuple!(ubyte, ushort) multipleResults() { + return tuple(ubyte(4), ushort(5)); +} + +@witExport("test:records/to-test", "swap-tuple") +Tuple!(uint, ubyte) swapTuple(in Tuple!(ubyte, uint) a) { + return tuple(a[1], a[0]); +} + +@witExport("test:records/to-test", "roundtrip-flags1") +F1 roundtripFlags1(in F1 a) { + return a; +} + +@witExport("test:records/to-test", "roundtrip-flags2") +F2 roundtripFlags2(in F2 a) { + return a; +} + +@witExport("test:records/to-test", "roundtrip-flags3") +Tuple!(Flag8, Flag16, Flag32) roundtripFlags3(in Flag8 a, in Flag16 b, in Flag32 c) { + return tuple(a, b, c); +} + +@witExport("test:records/to-test", "roundtrip-record1") +R1 roundtripRecord1(in R1 a) { + return a; +} + +@witExport("test:records/to-test", "tuple1") +Tuple!(ubyte) tuple1(in Tuple!(ubyte) a) { + return tuple(a[0]); +} + + +alias Exports = wit.test.records.test.Exports!( + multipleResults, + swapTuple, + roundtripFlags1, + roundtripFlags2, + roundtripFlags3, + roundtripRecord1, + tuple1 +); From e182480f5d484c2e42a3e39901260ed8f97c1f20 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Wed, 5 Aug 2026 11:26:23 -0700 Subject: [PATCH 45/55] Tweaks to `in`, and fixes for handle lifetime management --- crates/d/src/lib.rs | 103 ++++++++++++++------ crates/d/src/wit_common.d | 29 ++++++ crates/test/src/d.rs | 2 + tests/runtime/package-with-version/runner.d | 2 +- tests/runtime/records/test.d | 6 +- 5 files changed, 107 insertions(+), 35 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index dc64d0e88..64023821b 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -18,7 +18,6 @@ struct DSig { arguments: Vec<(String, DType)>, name: String, implicit_self: bool, - post_return: bool, } #[derive(Default)] @@ -238,7 +237,6 @@ fn escape_d_identifier(name: &str) -> &str { "Result" => "Result_", "bits" => "bits_", // part of WitFlags "borrow" => "borrow_", // part of the expansion of `resource` - "drop" => "drop_", // part of the expansion of `resource` "rep" => "rep_", // part of the expansion of `resource` "makeNew" => "makeNew_", // part of the expansion of `resource` "constructor" => "constructor_", // part of the expansion of `resource` @@ -1210,9 +1208,6 @@ impl<'a> DInterfaceGenerator<'a> { _ => false, }; - res.post_return = self.direction == Some(Direction::Export) - && abi::guest_export_needs_post_return(self.resolve, func); - res.result .push_str(&(self.optional_type_name(func.result.as_ref(), self.fqn))); @@ -1237,7 +1232,11 @@ impl<'a> DInterfaceGenerator<'a> { let escaped_param_name = escape_d_identifier(&lower_param_name); let needs_in_qualifier = match param { - Type::ErrorContext | Type::String | Type::Id(_) => true, + Type::ErrorContext | Type::String => true, + Type::Id(id) => match &self.resolve.types[*id].kind { + TypeDefKind::Handle(_) | TypeDefKind::Enum(_) | TypeDefKind::Flags(_) => false, + _ => true, + }, _ => false, }; @@ -1611,21 +1610,25 @@ impl<'a> DInterfaceGenerator<'a> { Type::String => true, Type::Id(id) => { let typeinfo = &self.r#gen.types.get(id); - typeinfo.has_list || typeinfo.has_resource + typeinfo.has_list } _ => false, } } - fn can_have_wit_clone(&self, ty: Type) -> bool { + fn needs_wit_drop(&self, ty: Type) -> bool { match ty { Type::Id(id) => { let typeinfo = &self.r#gen.types.get(id); - !typeinfo.has_own_handle + typeinfo.has_resource } - _ => true, + _ => false, } } + + fn can_have_wit_clone(&self, _ty: Type) -> bool { + true + } } impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { @@ -1690,6 +1693,17 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } self.src.push_str("}\n"); + self.src.push_str("\nvoid witDrop() @nogc nothrow {\n"); + for field in &record.fields { + let lower_name = field.name.to_lower_camel_case(); + let escaped_name = escape_d_identifier(&lower_name); + + if self.needs_wit_drop(field.ty) { + self.src.push_str(&format!("{escaped_name}.witDrop;\n")); + } + } + self.src.push_str("}\n"); + if self.can_have_wit_clone(Type::Id(id)) { self.src.push_str(&format!( "\n{escaped_name} witClone() const @nogc nothrow {{\n" @@ -1733,8 +1747,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { __handle = handle; }} - @disable this(); - ", self.r#gen.root_pkg )); @@ -1781,7 +1793,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } self.src.push_str( - "\nvoid drop() @trusted @nogc nothrow {\n__import_drop(__handle);\n}\n", + "\nvoid witDrop() @trusted @nogc nothrow {\nif (!__handle) return; __import_drop(__handle); __handle = 0;\n}\n", ); self.src.push_str(&format!( "@wasmImport!(\"{}\", \"[resource-drop]{}\")\n", @@ -1799,7 +1811,9 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str( "static private extern(C) void __import_drop(uint) @nogc nothrow;\n\n", ); - self.src.push_str("alias witFree = drop;\n"); + self.src.push_str("void witFree() @safe @nogc nothrow {}\n"); + + self.src.push_str("typeof(this) witClone() const @safe @nogc nothrow { return typeof(this)(__handle); }\n"); self.src.push_str(&format!( "// TODO: make RAII? disable copy for the own @@ -1814,9 +1828,10 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { __handle = handle; }} - @disable this(); - void witFree() @safe @nogc nothrow {{}} + void witDrop() @trusted @nogc nothrow {{ + if (!__handle) return; __import_drop(__handle); __handle = 0; + }} Borrow witClone() const @safe @nogc nothrow {{ return Borrow(__handle); }} ", self.r#gen.root_pkg @@ -1887,7 +1902,6 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { __handle = handle; }} - @disable this(); ", self.r#gen.root_pkg )); @@ -1911,7 +1925,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { name )); self.src.push_str(&format!( - "pragma(mangle, \"__wit_import_{}__:resource_new:{}\")\n", + "pragma(mangle, \"__wit_import_:export:{}__:resource_new:{}\")\n", self.wasm_import_module .unwrap() .replace("/", "__") @@ -1922,25 +1936,26 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { .push_str("static private extern(C) uint __import_makeNew(void*);\n\n"); self.src - .push_str("T* rep(T)() @nogc nothrow if (is(T == struct)) {\nreturn cast(T*)__import_rep(__handle);\n}\n"); + .push_str("T* rep(T)() const @nogc nothrow if (is(T == struct)) {\nreturn cast(T*)__import_rep(__handle);\n}\n"); self.src.push_str(&format!( "@wasmImport!(\"[export]{}\", \"[resource-rep]{}\")\n", self.wasm_import_module.unwrap(), name )); self.src.push_str(&format!( - "pragma(mangle, \"__wit_import_{}__:resource_rep:{}\")\n", + "pragma(mangle, \"__wit_import_:export:{}__:resource_rep:{}\")\n", self.wasm_import_module .unwrap() .replace("/", "__") .replace("-", "_"), name.replace("-", "_") )); - self.src - .push_str("static private extern(C) void __import_rep(uint);\n\n"); + self.src.push_str( + "static private extern(C) void* __import_rep(uint) @nogc nothrow;\n\n", + ); self.src.push_str( - "void drop() @trusted @nogc nothrow {\n__import_drop(__handle);\n}\n", + "void witDrop() @trusted @nogc nothrow {\nif (!__handle) return; __import_drop(__handle); __handle = 0;\n}\n", ); self.src.push_str(&format!( "@wasmImport!(\"[export]{}\", \"[resource-drop]{}\")\n", @@ -1948,7 +1963,7 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { name )); self.src.push_str(&format!( - "pragma(mangle, \"__wit_import_{}__:resource_drop:{}\")\n", + "pragma(mangle, \"__wit_import_:export:{}__:resource_drop:{}\")\n", self.wasm_import_module .unwrap() .replace("/", "__") @@ -1958,29 +1973,37 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { self.src.push_str( "static private extern(C) void __import_drop(uint) @nogc nothrow;\n\n", ); - self.src.push_str("alias witFree = drop;\n"); + self.src.push_str("void witFree() @safe @nogc nothrow {}\n"); + + self.src.push_str("typeof(this) witClone() const @safe @nogc nothrow { return typeof(this)(__handle); }\n"); self.src.push_str(&format!( "// TODO: make RAII? disable copy for the own - Borrow borrow() @safe @nogc nothrow => Borrow(__handle); - alias borrow this; + Borrow borrow() const @trusted @nogc nothrow => Borrow(__import_rep(__handle)); + //alias borrow this; struct Borrow {{ - package({}) uint __handle = 0; + package({}) void* __handle = null; - package({0}) this(uint handle) @safe @nogc nothrow {{ + package({0}) this(void* handle) @safe @nogc nothrow {{ __handle = handle; }} - @disable this(); + package({0}) this(uint handle) @safe @nogc nothrow {{ + __handle = cast(void*)handle; + }} void witFree() @safe @nogc nothrow {{}} - Borrow witClone() const @safe @nogc nothrow {{ return Borrow(__handle); }} + void witDrop() @safe @nogc nothrow {{}} + Borrow witClone() const @trusted @nogc nothrow {{ return Borrow(cast(void*)__handle); }} ", self.r#gen.root_pkg )); + self.src + .push_str("T* rep(T)() const @nogc nothrow if (is(T == struct)) {\nreturn cast(T*)__handle;\n}\n"); + self.src.push_str("}\n"); self.src.push_str("}\n"); @@ -2165,6 +2188,24 @@ impl<'a> InterfaceGenerator<'a> for DInterfaceGenerator<'a> { } self.src.push_str("}\n"); + self.src.push_str("\nvoid witDrop() @nogc nothrow {\n"); + if self.needs_wit_drop(Type::Id(id)) { + self.src.push_str("switch (_tag) with (Tag) {\n"); + for case in &variant.cases { + let lower_case_name = case.name.to_lower_camel_case(); + let escaped_lower_case_name = escape_d_identifier(&lower_case_name); + + if case.ty.is_some() && self.needs_wit_drop(case.ty.unwrap()) { + self.src.push_str(&format!( + "case {escaped_lower_case_name}: _get!(Tag.{escaped_lower_case_name}).witDrop; break;\n", + )); + } + } + self.src.push_str("default: break;\n"); + self.src.push_str("}\n"); + } + self.src.push_str("}\n"); + if self.can_have_wit_clone(Type::Id(id)) { self.src.push_str(&format!( "\n{escaped_name} witClone() const @nogc nothrow {{\n" diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 3077bcbf3..db241341f 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -246,6 +246,9 @@ public: void witFree(T)(scope ref T val) if (__traits(isArithmetic, T)) { // no-op } +void witDrop(T)(scope ref T val) if (__traits(isArithmetic, T)) { + // no-op +} T witClone(T)(in T val) if (__traits(isArithmetic, T)) { return val; } @@ -253,6 +256,9 @@ T witClone(T)(in T val) if (__traits(isArithmetic, T)) { void witFree(T : Option!U, U)(scope ref T val) { static if (!is(U == void)) if (val.isSome) val.unwrap.witFree; } +void witDrop(T : Option!U, U)(scope ref T val) { + static if (!is(U == void)) if (val.isSome) val.unwrap.witDrop; +} T witClone(T : Option!U, U)(in T val) { if (val.isSome) { static if (!is(U == void)) { @@ -272,6 +278,13 @@ void witFree(T : Result!(U, V), U, V)(scope ref T val) { static if (!is(U == void)) val.unwrap.witFree; } } +void witDrop(T : Result!(U, V), U, V)(scope ref T val) { + if (val.isErr) { + static if (!is(V == void)) val.unwrapErr.witDrop; + } else { + static if (!is(U == void)) val.unwrap.witDrop; + } +} T witClone(T : Result!(U, V), U, V)(in T val) { if (val.isErr) { static if (!is(V == void)) { @@ -295,6 +308,12 @@ void witFree(T : WitList!U, U)(scope ref T val) { if (val.ptr && val.length) free(val.ptr); val = null; } +void witDrop(T : WitList!U, U)(scope ref T val) { + foreach (ref e; val) { + e.witDrop; + } + val = null; +} T witClone(T : WitList!U, U)(in T val) { if (val.ptr == null || val.length == 0) return T(null); @@ -312,6 +331,11 @@ void witFree(T : Tuple!U, U...)(scope ref T val) { __traits(child, val, F).witFree; } } +void witDrop(T : Tuple!U, U...)(scope ref T val) { + static foreach (F; T.tupleof) { + __traits(child, val, F).witDrop; + } +} T witClone(T : Tuple!U, U...)(in T val) { T clone = void; static foreach (F; T.tupleof) { @@ -326,6 +350,11 @@ void witFree(T : U[L], U, size_t L)(scope ref T val) { e.witFree; } } +void witDrop(T : U[L], U, size_t L)(scope ref T val) { + foreach (ref e; val) { + e.witDrop; + } +} T witClone(T : U[L], U, size_t L)(in T val) { T clone; foreach (i, ref e; clone) { diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index 8870be079..6ee1bc7e9 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -131,6 +131,7 @@ fn compile(runner: &Runner, compile: &Compile<'_>, compiler: PathBuf) -> Result< .arg("-L--no-export-dynamic") // important to make sure unused symbols don't get linked .arg("--checkaction=halt") // to trap instead of using libc __assert .arg("-g") // debug info + .arg("--preview=in") // `in` is more restricting with this on; test it .arg("-of") .arg(&output) .arg(compile.artifacts_dir.join("runtime.d")) @@ -159,6 +160,7 @@ fn verify(runner: &Runner, verify: &Verify<'_>, compiler: PathBuf) -> Result<()> .arg("-c") // compile only .arg("--de") // deperecations are errors .arg("-w") // warnigns are errors + .arg("--preview=in") .arg("-of") .arg(verify.artifacts_dir.join("tmp.o")); runner.run_command(&mut cmd) diff --git a/tests/runtime/package-with-version/runner.d b/tests/runtime/package-with-version/runner.d index b75b32784..d11db58d3 100644 --- a/tests/runtime/package-with-version/runner.d +++ b/tests/runtime/package-with-version/runner.d @@ -3,7 +3,7 @@ import wit.common; @witExport("$root", "run") void run() { - Bar.makeNew().drop; + Bar.makeNew().witDrop; } alias Exports = wit.my.inline.runner.Exports!( diff --git a/tests/runtime/records/test.d b/tests/runtime/records/test.d index 6bffc209b..474ff070a 100644 --- a/tests/runtime/records/test.d +++ b/tests/runtime/records/test.d @@ -14,17 +14,17 @@ Tuple!(uint, ubyte) swapTuple(in Tuple!(ubyte, uint) a) { } @witExport("test:records/to-test", "roundtrip-flags1") -F1 roundtripFlags1(in F1 a) { +F1 roundtripFlags1(F1 a) { return a; } @witExport("test:records/to-test", "roundtrip-flags2") -F2 roundtripFlags2(in F2 a) { +F2 roundtripFlags2(F2 a) { return a; } @witExport("test:records/to-test", "roundtrip-flags3") -Tuple!(Flag8, Flag16, Flag32) roundtripFlags3(in Flag8 a, in Flag16 b, in Flag32 c) { +Tuple!(Flag8, Flag16, Flag32) roundtripFlags3(Flag8 a, Flag16 b, Flag32 c) { return tuple(a, b, c); } From ad8a8e256f7351a71ac9530ee9d5ff4afb854572 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 6 Aug 2026 17:10:25 -0700 Subject: [PATCH 46/55] Result construction helpers & make resource containing parameters mutable --- crates/d/src/lib.rs | 63 ++++++++++++++++++++++++-------- crates/d/src/wit_common.d | 31 +++++++++++----- tests/runtime/flavorful/runner.d | 6 +-- tests/runtime/flavorful/test.d | 8 ++-- 4 files changed, 76 insertions(+), 32 deletions(-) diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 64023821b..1ead09d47 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1231,22 +1231,32 @@ impl<'a> DInterfaceGenerator<'a> { let lower_param_name = name.to_lower_camel_case(); let escaped_param_name = escape_d_identifier(&lower_param_name); - let needs_in_qualifier = match param { - Type::ErrorContext | Type::String => true, + let qualifier = match param { + Type::ErrorContext => todo!(), + Type::String => "in ".to_owned(), Type::Id(id) => match &self.resolve.types[*id].kind { - TypeDefKind::Handle(_) | TypeDefKind::Enum(_) | TypeDefKind::Flags(_) => false, - _ => true, + TypeDefKind::Enum(_) | TypeDefKind::Flags(_) | TypeDefKind::Handle(_) => { + "".to_owned() + } + TypeDefKind::Future(_) | TypeDefKind::Map(_, _) | TypeDefKind::Stream(_) => { + todo!() + } + _ => { + if matches!(self.direction, Some(Direction::Export)) + && self.r#gen.types.get(*id).has_resource + { + "scope ref ".to_owned() + } else { + "in ".to_owned() + } + } }, - _ => false, + _ => "".to_owned(), }; res.arguments.push(( escaped_param_name.into(), - if needs_in_qualifier { - "in ".to_owned() - } else { - "".to_owned() - } + &self.type_name(¶m, self.fqn), + qualifier + &self.type_name(¶m, self.fqn), )); } @@ -2721,7 +2731,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("if ({len}) deallocate ~= cast(){ptr};\n")); } - results.push(format!("{list_name}({ptr}[0..{len}])")); + let result = format!("{list_name}({ptr}[0..{len}])"); + + let tmpvar = tempname("_list", self.tmp()); + self.push_str(&format!("auto {tmpvar} = {result};\n")); + results.push(tmpvar); } abi::Instruction::StringLift => { let tmp = self.tmp(); @@ -2741,7 +2755,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.push_str(&format!("if ({len}) deallocate ~= cast(){ptr};\n")); } - results.push(format!("WitString({ptr}[0..{len}])")); + let result = format!("WitString({ptr}[0..{len}])"); + + let tmpvar = tempname("_list", self.tmp()); + self.push_str(&format!("auto {tmpvar} = {result};\n")); + results.push(tmpvar); } abi::Instruction::ListLift { ty, element, .. } => { let Block { @@ -2794,7 +2812,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } let list_name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - results.push(format!("{list_name}({list})")); + let result = format!("{list_name}({list})"); + + let tmpvar = tempname("_witList", self.tmp()); + self.push_str(&format!("auto {tmpvar} = {result};\n")); + results.push(tmpvar); } abi::Instruction::FixedLengthListLift { size, id, .. } => { @@ -2900,7 +2922,16 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::HandleLift { ty, .. } => { let name = self.r#gen.type_name(&Type::Id(*ty), self.r#gen.fqn); - results.push(format!("{name}({})", operands[0])); + let result = format!("{name}({})", operands[0]); + + if matches!(self.r#gen.direction, Some(Direction::Export)) && operands[0] == "this" + { + results.push(result); + } else { + let tmpvar = tempname("_handle", self.tmp()); + self.push_str(&format!("auto {tmpvar} = {result};\n")); + results.push(tmpvar); + } } abi::Instruction::TupleLower { tuple, .. } => { @@ -3246,10 +3277,10 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { bool {is_err} = ({op0}) != 0; if ({is_err}) {{ {err} - {resultname} = {full_type}.err({err_value}); + {resultname} = {full_type}.makeErr({err_value}); }} else {{ {ok} - {resultname} = {full_type}.ok({ok_value}); + {resultname} = {full_type}.makeOk({ok_value}); }}\n" )); results.push(resultname); diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index db241341f..3e61a132d 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -199,20 +199,20 @@ private: public: static if (is(T == void)) { - static Result ok() @safe @nogc nothrow => Result(false, Storage.init); + static Result makeOk() @safe @nogc nothrow => Result(false, Storage.init); } else { - static Result ok(inout(T) value) @trusted @nogc nothrow { + static inout(Result) makeOk(inout(T) value) @trusted @nogc nothrow { Storage newStorage = Storage.init; newStorage.value = cast(T)value; - return Result(false, cast(inout Storage)newStorage); + return inout Result(false, cast(inout Storage)newStorage); } } static if (is(E == void)) { - static Result err() @safe @nogc nothrow => Result(true, Storage.init); + static Result makeErr() @safe @nogc nothrow => Result(true, Storage.init); } else { - static inout(Result) err(inout(E) error) @trusted @nogc nothrow { + static inout(Result) makeErr(inout(E) error) @trusted @nogc nothrow { Storage newStorage = Storage.init; newStorage.error = cast(E)error; @@ -242,6 +242,19 @@ public: } } +auto ok(E, T)(inout T value) @safe @nogc nothrow { + return Result!(T, E).makeOk(value); +} +auto ok(E)() @safe @nogc nothrow { + return Result!(void, E).makeOk(); +} + +auto err(T, E)(inout E value) @safe @nogc nothrow { + return Result!(T, E).makeErr(value); +} +auto err(T)() @safe @nogc nothrow { + return Result!(T, void).makeErr(); +} void witFree(T)(scope ref T val) if (__traits(isArithmetic, T)) { // no-op @@ -288,15 +301,15 @@ void witDrop(T : Result!(U, V), U, V)(scope ref T val) { T witClone(T : Result!(U, V), U, V)(in T val) { if (val.isErr) { static if (!is(V == void)) { - return T.err(val.unwrapErr.witClone); + return T.makeErr(val.unwrapErr.witClone); } else { - return T.err; + return T.makeErr; } } else { static if (!is(U == void)) { - return T.ok(val.unwrap.witClone); + return T.makeOk(val.unwrap.witClone); } else { - return T.ok; + return T.makeOk; } } } diff --git a/tests/runtime/flavorful/runner.d b/tests/runtime/flavorful/runner.d index decba03b1..43f575c9b 100644 --- a/tests/runtime/flavorful/runner.d +++ b/tests/runtime/flavorful/runner.d @@ -32,7 +32,7 @@ void run() { ); } - fListInVariant1(some("foo".witList), Result!(void, WitString).err("bar".witList)); + fListInVariant1(some("foo".witList), err!void("bar".witList)); { auto result = fListInVariant2(); @@ -73,14 +73,14 @@ void run() { { static immutable bool[] input1 = [true, false]; - static immutable Result!()[] input2 = [Result!().ok, Result!().err]; + static immutable Result!()[] input2 = [ok!void, err!void]; static immutable MyErrno[] input3 = [MyErrno.success, MyErrno.a]; auto result = listOfVariants(input1[].witList, input2[].witList, input3[].witList); scope(exit) result.witFree; static immutable bool[] output1 = [false, true]; - static immutable Result!()[] output2 = [Result!().err, Result!().ok]; + static immutable Result!()[] output2 = [err!void, ok!void]; static immutable MyErrno[] output3 = [MyErrno.a, MyErrno.b]; assert(result[0] == output1); assert(result[1] == output2); diff --git a/tests/runtime/flavorful/test.d b/tests/runtime/flavorful/test.d index d1b25e209..a384b790b 100644 --- a/tests/runtime/flavorful/test.d +++ b/tests/runtime/flavorful/test.d @@ -46,9 +46,9 @@ Result!(void, MyErrno) errnoResult() { if (first) { first = false; - return Result!(void, MyErrno).err(MyErrno.b); + return MyErrno.b.err!void; } else { - return Result!(void, MyErrno).ok(); + return ok!MyErrno; } } @@ -76,14 +76,14 @@ Tuple!(WitList!bool, WitList!(Result!()), WitList!MyErrno) listOfVariants(in Wit static immutable bool[] boolsCmp = [true, false]; assert(bools == boolsCmp[]); - static immutable Result!()[] resultsCmp = [Result!().ok, Result!().err]; + static immutable Result!()[] resultsCmp = [ok!void, err!void]; assert(results == resultsCmp[]); static immutable MyErrno[] enumsCmp = [MyErrno.success, MyErrno.a]; assert(enums == enumsCmp[]); static immutable bool[] boolsOut = [false, true]; - static immutable Result!(void)[] resultsOut = [Result!().err, Result!().ok]; + static immutable Result!(void)[] resultsOut = [err!void, ok!void]; static immutable MyErrno[] enumsOut = [MyErrno.a, MyErrno.b]; return tuple( boolsOut.witList, From 383d8c4cf5b0fdbb2f929bd55ff304f44be0d50e Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 6 Aug 2026 17:14:09 -0700 Subject: [PATCH 47/55] Start on resource-related tests --- tests/runtime/resource-borrow/runner.d | 14 ++++ tests/runtime/resource-borrow/test.d | 24 +++++++ .../resource-import-and-export/intermediate.d | 61 +++++++++++++++++ .../resource-import-and-export/leaf-thing.d | 38 +++++++++++ .../leaf-toplevel.d | 17 +++++ .../resource-import-and-export/runner.d | 27 ++++++++ tests/runtime/resource_aggregates/runner.d | 65 ++++++++++++++++++ tests/runtime/resource_aggregates/test.d | 66 +++++++++++++++++++ 8 files changed, 312 insertions(+) create mode 100644 tests/runtime/resource-borrow/runner.d create mode 100644 tests/runtime/resource-borrow/test.d create mode 100644 tests/runtime/resource-import-and-export/intermediate.d create mode 100644 tests/runtime/resource-import-and-export/leaf-thing.d create mode 100644 tests/runtime/resource-import-and-export/leaf-toplevel.d create mode 100644 tests/runtime/resource-import-and-export/runner.d create mode 100644 tests/runtime/resource_aggregates/runner.d create mode 100644 tests/runtime/resource_aggregates/test.d diff --git a/tests/runtime/resource-borrow/runner.d b/tests/runtime/resource-borrow/runner.d new file mode 100644 index 000000000..e68553c70 --- /dev/null +++ b/tests/runtime/resource-borrow/runner.d @@ -0,0 +1,14 @@ +import wit.test.resource_borrow.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + auto thing = Thing.makeNew(42); + scope(exit) thing.witDrop; + + assert(foo(thing) == 42 + 1 + 2); +} + +alias Exports = wit.test.resource_borrow.runner.Exports!( + run +); diff --git a/tests/runtime/resource-borrow/test.d b/tests/runtime/resource-borrow/test.d new file mode 100644 index 000000000..73e14f2dc --- /dev/null +++ b/tests/runtime/resource-borrow/test.d @@ -0,0 +1,24 @@ +import wit.test.resource_borrow.test; +import wit.common; + +@witExport("test:resource-borrow/to-test", "thing") +struct ThingImpl { + uint val; + + @witExport("test:resource-borrow/to-test", "[constructor]thing") + static Thing constructor(uint v) { + return Thing.makeNew((out typeof(this) self) { + self.val = v + 1; + }); + } +} + +@witExport("test:resource-borrow/to-test", "foo") +uint foo(Thing.Borrow v) { + return v.rep!ThingImpl.val + 2; +} + +alias Exports = wit.test.resource_borrow.test.Exports!( + ThingImpl, + foo +); diff --git a/tests/runtime/resource-import-and-export/intermediate.d b/tests/runtime/resource-import-and-export/intermediate.d new file mode 100644 index 000000000..649157f9b --- /dev/null +++ b/tests/runtime/resource-import-and-export/intermediate.d @@ -0,0 +1,61 @@ +import wit.test.resource_import_and_export.intermediate; +import wit.test.resource_import_and_export.test.imports : ThingImport = Thing; +import wit.test.resource_import_and_export.test.exports : ThingExport = Thing; + +import wit.common; + +@witExport("test:resource-import-and-export/test", "thing") +struct ThingImpl { + ThingImport thing; + + @witExport("test:resource-import-and-export/test", "[constructor]thing") + static ThingExport constructor(uint v) { + return ThingExport.makeNew((out typeof(this) self) { + self.thing = ThingImport.makeNew(v + 1); + }); + } + + @witExport("test:resource-import-and-export/test", "[method]thing.foo") + uint foo() { + return thing.foo + 2; + } + + @witExport("test:resource-import-and-export/test", "[method]thing.bar") + void bar(uint v) { + thing.bar(v + 3); + } + + @witExport("test:resource-import-and-export/test", "[static]thing.baz") + static ThingExport baz(ThingExport a, ThingExport b) { + scope(exit) { + a.witDrop; + b.witDrop; + } + + auto aRep = a.rep!ThingImpl; + auto bRep = b.rep!ThingImpl; + + auto result = ThingImport.baz( + aRep.thing, + bRep.thing + ); + aRep.thing = Thing.init; // consumed by `baz` + bRep.thing = Thing.init; // consumed by `baz` + scope(exit) result.witDrop; + + return ThingImpl.constructor(result.foo + 4); + } +} + +@witExport("$root", "toplevel-export") +ThingImport toplevelExport(ThingImport input) { + // `input` not dropped b/c ownership transferred + // to `toplevelImport` + + return toplevelImport(input); +} + +alias Exports = wit.test.resource_import_and_export.intermediate.Exports!( + ThingImpl, + toplevelExport +); diff --git a/tests/runtime/resource-import-and-export/leaf-thing.d b/tests/runtime/resource-import-and-export/leaf-thing.d new file mode 100644 index 000000000..15c5fce1f --- /dev/null +++ b/tests/runtime/resource-import-and-export/leaf-thing.d @@ -0,0 +1,38 @@ +module leaf_thing; + +import wit.test.resource_import_and_export.leaf_thing; + +import wit.common; + +@witExport("test:resource-import-and-export/test", "thing") +struct ThingImpl { + uint val; + + @witExport("test:resource-import-and-export/test", "[constructor]thing") + static Thing constructor(uint v) { + return Thing.makeNew((out typeof(this) self) { + self.val = v + 1; + }); + } + + @witExport("test:resource-import-and-export/test", "[method]thing.foo") + uint foo() { + return val + 2; + } + + @witExport("test:resource-import-and-export/test", "[method]thing.bar") + void bar(uint v) { + val = v + 3; + } + + @witExport("test:resource-import-and-export/test", "[static]thing.baz") + static Thing baz(Thing a, Thing b) { + return ThingImpl.constructor( + a.rep!ThingImpl.foo + b.rep!ThingImpl.foo + 4 + ); + } +} + +alias Exports = wit.test.resource_import_and_export.leaf_thing.Exports!( + ThingImpl +); diff --git a/tests/runtime/resource-import-and-export/leaf-toplevel.d b/tests/runtime/resource-import-and-export/leaf-toplevel.d new file mode 100644 index 000000000..fc10e773b --- /dev/null +++ b/tests/runtime/resource-import-and-export/leaf-toplevel.d @@ -0,0 +1,17 @@ +module leaf_toplevel; + +import wit.test.resource_import_and_export.leaf_toplevel; + +import wit.common; + +@witExport("$root", "toplevel-export") +Thing toplevelExport(Thing input) { + // `input` not dropped b/c ownership transferred + // via return + + return input; +} + +alias Exports = wit.test.resource_import_and_export.leaf_toplevel.Exports!( + toplevelExport +); diff --git a/tests/runtime/resource-import-and-export/runner.d b/tests/runtime/resource-import-and-export/runner.d new file mode 100644 index 000000000..a8b13dccd --- /dev/null +++ b/tests/runtime/resource-import-and-export/runner.d @@ -0,0 +1,27 @@ +import wit.test.resource_import_and_export.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + auto thing1 = Thing.makeNew(42); + scope(exit) thing1.witDrop; + + // 42 + 1 (constructor) + 1 (constructor) + 2 (foo) + 2 (foo) + assert(thing1.foo == 48); + + // 33 + 3 (bar) + 3 (bar) + 2 (foo) + 2 (foo) + thing1.bar(33); + assert(thing1.foo() == 43); + + auto thing2 = Thing.makeNew(81); + scope(exit) thing2.witDrop; + + auto thing3 = Thing.baz(thing1, thing2); + thing1 = Thing.init; // thing1 consumed by `baz` + thing2 = Thing.init; // thing2 consumed by `baz` + scope(exit) thing3.witDrop; +} + +alias Exports = wit.test.resource_import_and_export.runner.Exports!( + run +); diff --git a/tests/runtime/resource_aggregates/runner.d b/tests/runtime/resource_aggregates/runner.d new file mode 100644 index 000000000..8d4282b8c --- /dev/null +++ b/tests/runtime/resource_aggregates/runner.d @@ -0,0 +1,65 @@ +import wit.test.resource_aggregates.runner; +import wit.common; + +@witExport("$root", "run") +void run() { + auto r2Thing = Thing.makeNew(1); + scope(exit) r2Thing.witDrop; + + auto r3Thing1 = Thing.makeNew(2); + scope(exit) r3Thing1.witDrop; + + auto t2Thing = Thing.makeNew(6); + scope(exit) t2Thing.witDrop; + + auto v2Thing = Thing.makeNew(8); + scope(exit) v2Thing.witDrop; + + auto l2Thing1 = Thing.makeNew(11); + scope(exit) l2Thing1.witDrop; + + auto l2Thing2 = Thing.makeNew(12); + scope(exit) l2Thing2.witDrop; + + auto o2Thing = Thing.makeNew(14); + scope(exit) o2Thing.witDrop; + + auto result2Thing = Thing.makeNew(16); + scope(exit) result2Thing.witDrop; + + immutable Thing[2] l1Elems = [ + Thing.makeNew(9), + Thing.makeNew(10), + ]; + + immutable Thing.Borrow[2] l2Elems = [ + l2Thing1, + l2Thing2, + ]; + + assert(foo( + R1(thing: Thing.makeNew(0)), + R2(thing: r2Thing), + R3( + thing1: r3Thing1, + thing2: Thing.makeNew(3) + ), + tuple( + Thing.makeNew(4), + R1(thing: Thing.makeNew(5)) + ), + tuple(t2Thing.borrow), + V1.thing(Thing.makeNew(7)), + V2.thing(v2Thing), + l1Elems.witList, + l2Elems.witList, + Thing.makeNew(13).some, + o2Thing.borrow.some, + Thing.makeNew(15).ok!void, + result2Thing.borrow.ok!void + ) == 156); +} + +alias Exports = wit.test.resource_aggregates.runner.Exports!( + run +); diff --git a/tests/runtime/resource_aggregates/test.d b/tests/runtime/resource_aggregates/test.d new file mode 100644 index 000000000..932e1ad77 --- /dev/null +++ b/tests/runtime/resource_aggregates/test.d @@ -0,0 +1,66 @@ +import wit.test.resource_aggregates.test; + +import wit.common; +import std.algorithm.iteration : sum, map; + +@witExport("test:resource-aggregates/to-test", "thing") +struct ThingImpl { + uint val; + + @witExport("test:resource-aggregates/to-test", "[constructor]thing") + static Thing constructor(uint v) { + return Thing.makeNew((out typeof(this) self) { + self.val = v + 1; + }); + } +} + +@witExport("test:resource-aggregates/to-test", "foo") +uint foo( + scope ref R1 r1, scope ref R2 r2, scope ref R3 r3, + scope ref T1 t1, scope ref T2 t2, + scope ref V1 v1, scope ref V2 v2, + scope ref L1 l1, scope ref L2 l2, + scope ref Option!Thing o1, scope ref Option!(Thing.Borrow) o2, + scope ref Result!(Thing, void) result1, scope ref Result!(Thing.Borrow, void) result2 +) { + scope(exit) { + r1.witDrop; + r2.witDrop; + r3.witDrop; + t1.witDrop; + t2.witDrop; + v1.witDrop; + v2.witDrop; + l1.witDrop; + l2.witDrop; + o1.witDrop; + o2.witDrop; + result1.witDrop; + result2.witDrop; + } + + return ( + r1.thing.rep!ThingImpl.val + + r2.thing.rep!ThingImpl.val + + r3.thing1.rep!ThingImpl.val + + r3.thing2.rep!ThingImpl.val + + t1[0].rep!ThingImpl.val + + t1[1].thing.rep!ThingImpl.val + + t2[0].rep!ThingImpl.val + + v1.getThing.rep!ThingImpl.val + + v2.getThing.rep!ThingImpl.val + + l1[].map!((a) => a.rep!ThingImpl.val).sum + + l2[].map!((a) => a.rep!ThingImpl.val).sum + + o1.unwrap.rep!ThingImpl.val + + o2.unwrap.rep!ThingImpl.val + + result1.unwrap.rep!ThingImpl.val + + result2.unwrap.rep!ThingImpl.val + + 3 + ); +} + +alias Exports = wit.test.resource_aggregates.test.Exports!( + ThingImpl, + foo +); From 4037fffa8ef2602457f65a8b54e995847bce210d Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 7 Aug 2026 22:05:26 -0700 Subject: [PATCH 48/55] Fix `list-in-variant` with new Result helpers --- tests/runtime/list-in-variant/runner.d | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/runtime/list-in-variant/runner.d b/tests/runtime/list-in-variant/runner.d index e273c499d..34e85cec6 100644 --- a/tests/runtime/list-in-variant/runner.d +++ b/tests/runtime/list-in-variant/runner.d @@ -5,7 +5,7 @@ import wit.common; void run() { const WitString[2] hw = ["hello".witList, "world".witList]; { - auto result = listInOption(some(hw[].witList)); + auto result = listInOption(hw[].witList.some); scope(exit) result.witFree; assert(result == "hello,world"); @@ -34,20 +34,20 @@ void run() { const WitString[3] abc = ["a".witList, "b".witList, "c".witList]; { - auto result = listInResult(Result!(WitList!WitString, WitString).ok(abc[].witList)); + auto result = listInResult(abc[].witList.ok!WitString); scope(exit) result.witFree; assert(result == "a,b,c"); } { - auto result = listInResult(Result!(WitList!WitString, WitString).err("oops".witList)); + auto result = listInResult("oops".witList.err!(WitList!WitString)); scope(exit) result.witFree; assert(result == "err:oops"); } const WitString[2] hw2 = ["hello".witList, "world".witList]; - auto s1 = listInOptionWithReturn(some(hw2.witList)); + auto s1 = listInOptionWithReturn(hw2.witList.some); { auto result = s1.count; scope(exit) result.witFree; From f8e1ff650bad33b8f02669d03b89f792d90c7cf2 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Mon, 10 Aug 2026 15:58:22 -0700 Subject: [PATCH 49/55] `variant` test (+ param qual fix) --- crates/d/src/lib.rs | 49 +++++++++-------- tests/runtime/variants/runner.d | 93 +++++++++++++++++++++++++++++++++ tests/runtime/variants/test.d | 47 +++++++++++++++++ 3 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 tests/runtime/variants/runner.d create mode 100644 tests/runtime/variants/test.d diff --git a/crates/d/src/lib.rs b/crates/d/src/lib.rs index 1ead09d47..536ed32b6 100644 --- a/crates/d/src/lib.rs +++ b/crates/d/src/lib.rs @@ -1166,6 +1166,32 @@ impl<'a> DInterfaceGenerator<'a> { type_info.has_resource } + fn param_qualifier_for_type(&self, param_type: &Type) -> String { + match param_type { + Type::ErrorContext => todo!(), + Type::String => "in ".to_owned(), + Type::Id(id) => match &self.resolve.types[*id].kind { + TypeDefKind::Enum(_) | TypeDefKind::Flags(_) | TypeDefKind::Handle(_) => { + "".to_owned() + } + TypeDefKind::Future(_) | TypeDefKind::Map(_, _) | TypeDefKind::Stream(_) => { + todo!() + } + TypeDefKind::Type(r#type) => self.param_qualifier_for_type(r#type), + _ => { + if matches!(self.direction, Some(Direction::Export)) + && self.r#gen.types.get(*id).has_resource + { + "scope ref ".to_owned() + } else { + "in ".to_owned() + } + } + }, + _ => "".to_owned(), + } + } + fn get_d_signature(&mut self, func: &Function) -> DSig { match &func.kind { FunctionKind::Freestanding @@ -1231,28 +1257,7 @@ impl<'a> DInterfaceGenerator<'a> { let lower_param_name = name.to_lower_camel_case(); let escaped_param_name = escape_d_identifier(&lower_param_name); - let qualifier = match param { - Type::ErrorContext => todo!(), - Type::String => "in ".to_owned(), - Type::Id(id) => match &self.resolve.types[*id].kind { - TypeDefKind::Enum(_) | TypeDefKind::Flags(_) | TypeDefKind::Handle(_) => { - "".to_owned() - } - TypeDefKind::Future(_) | TypeDefKind::Map(_, _) | TypeDefKind::Stream(_) => { - todo!() - } - _ => { - if matches!(self.direction, Some(Direction::Export)) - && self.r#gen.types.get(*id).has_resource - { - "scope ref ".to_owned() - } else { - "in ".to_owned() - } - } - }, - _ => "".to_owned(), - }; + let qualifier = self.param_qualifier_for_type(param); res.arguments.push(( escaped_param_name.into(), diff --git a/tests/runtime/variants/runner.d b/tests/runtime/variants/runner.d new file mode 100644 index 000000000..406e81a46 --- /dev/null +++ b/tests/runtime/variants/runner.d @@ -0,0 +1,93 @@ +import wit.test.variants.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + assert(roundtripOption(1.0f.some) == ubyte(1).some); + assert(roundtripOption(none!float) == none!ubyte); + assert(roundtripOption(2.0f.some) == ubyte(2).some); + assert(roundtripOption(4.0f.some) == ubyte(4).some); + assert(roundtripOption(5.3f.some) == ubyte(5).some); + + assert(roundtripEnum(E1.a) == E1.a); + assert(roundtripEnum(E1.b) == E1.b); + + assert(invertBool(true) == false); + assert(invertBool(false) == true); + + { + auto result = variantCasts(tuple( + C1.a(1), + C2.a(2), + C3.a(3), + C4.a(4), + C5.a(5), + C6.a(6.0), + )); + + assert(result[0] == C1.a(1)); + assert(result[1] == C2.a(2)); + assert(result[2] == C3.a(3)); + assert(result[3] == C4.a(4)); + assert(result[4] == C5.a(5)); + assert(result[5] == C6.a(6.0)); + } + + { + auto result = variantCasts(tuple( + C1.b(1), + C2.b(2.0), + C3.b(3.0), + C4.b(4.0), + C5.b(5.0), + C6.b(6.0), + )); + + assert(result[0] == C1.b(1)); + assert(result[1] == C2.b(2.0)); + assert(result[2] == C3.b(3.0)); + assert(result[3] == C4.b(4.0)); + assert(result[4] == C5.b(5.0)); + assert(result[5] == C6.b(6.0)); + } + + { + auto result = variantZeros(tuple( + Z1.a(1), + Z2.a(2), + Z3.a(3.0), + Z4.a(4.0), + )); + + assert(result[0] == Z1.a(1)); + assert(result[1] == Z2.a(2)); + assert(result[2] == Z3.a(3.0)); + assert(result[3] == Z4.a(4.0)); + } + + { + auto result = variantZeros(tuple( + Z1.b, + Z2.b, + Z3.b, + Z4.b, + )); + + assert(result[0] == Z1.b); + assert(result[1] == Z2.b); + assert(result[2] == Z3.b); + assert(result[3] == Z4.b); + } + + variantTypedefs(none!uint, false, err!uint); + + assert( + variantEnums(true, ok!void, MyErrno.success) + == tuple(true, ok!void, MyErrno.success) + ); +} + +alias Exports = wit.test.variants.runner.Exports!( + run +); diff --git a/tests/runtime/variants/test.d b/tests/runtime/variants/test.d new file mode 100644 index 000000000..2da0702a8 --- /dev/null +++ b/tests/runtime/variants/test.d @@ -0,0 +1,47 @@ +import wit.test.variants.test; + +import wit.common; + +@witExport("test:variants/to-test", "roundtrip-option") +Option!ubyte roundtripOption(in Option!float a) { + if (a.isSome) return (cast(ubyte)a.unwrap).some; + return none!ubyte; +} + +@witExport("test:variants/to-test", "roundtrip-result") +Result!(double, ubyte) roundtripResult(in Result!(uint, float) a) { + if (a.isOk) return (cast(double)a.unwrap).ok!ubyte; + return (cast(ubyte)a.unwrapErr).err!double; +} + +@witExport("test:variants/to-test", "roundtrip-enum") +E1 roundtripEnum(E1 a) => a; + +@witExport("test:variants/to-test", "invert-bool") +bool invertBool(bool a) => !a; + +@witExport("test:variants/to-test", "variant-casts") +Casts variantCasts(in Casts a) => a; + +@witExport("test:variants/to-test", "variant-zeros") +Zeros variantZeros(in Zeros a) => a; + +@witExport("test:variants/to-test", "variant-typedefs") +void variantTypedefs(in Option!uint, bool, in Result!uint) {} + + +@witExport("test:variants/to-test", "variant-enums") +Tuple!(bool, Result!void, MyErrno) variantEnums(bool a, in Result!void b, MyErrno c) { + return tuple(a, b, c); +} + +alias Exports = wit.test.variants.test.Exports!( + roundtripOption, + roundtripResult, + roundtripEnum, + invertBool, + variantCasts, + variantZeros, + variantTypedefs, + variantEnums +); From 24d135f125edbe3d9d6857286f98d1922dd8b240 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Tue, 11 Aug 2026 00:31:54 -0700 Subject: [PATCH 50/55] Remaining non-resource tests --- crates/d/src/wit_common.d | 36 +++++++++++ tests/runtime/common-types/leaf.d | 2 +- tests/runtime/common-types/middle.d | 2 +- tests/runtime/results/intermediate.d | 43 +++++++++++++ tests/runtime/results/leaf.d | 85 +++++++++++++++++++++++++ tests/runtime/results/runner.d | 69 ++++++++++++++++++++ tests/runtime/strings-alias/runner.d | 17 +++++ tests/runtime/strings-alias/test.d | 18 ++++++ tests/runtime/strings-simple/runner.d | 17 +++++ tests/runtime/strings-simple/test.d | 18 ++++++ tests/runtime/strings/runner.d | 33 ++++++++++ tests/runtime/strings/test.d | 31 +++++++++ tests/runtime/symbol-conflicts/runner.d | 15 +++++ tests/runtime/symbol-conflicts/test.d | 23 +++++++ tests/runtime/unused-types/runner.d | 14 ++++ tests/runtime/unused-types/test.d | 12 ++++ 16 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 tests/runtime/results/intermediate.d create mode 100644 tests/runtime/results/leaf.d create mode 100644 tests/runtime/results/runner.d create mode 100644 tests/runtime/strings-alias/runner.d create mode 100644 tests/runtime/strings-alias/test.d create mode 100644 tests/runtime/strings-simple/runner.d create mode 100644 tests/runtime/strings-simple/test.d create mode 100644 tests/runtime/strings/runner.d create mode 100644 tests/runtime/strings/test.d create mode 100644 tests/runtime/symbol-conflicts/runner.d create mode 100644 tests/runtime/symbol-conflicts/test.d create mode 100644 tests/runtime/unused-types/runner.d create mode 100644 tests/runtime/unused-types/test.d diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index 3e61a132d..b666cf866 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -166,6 +166,19 @@ public: T unwrapOrElse(D)(scope D fallback) if (is(D R == return) && is(R : T) && is(D == __parameters)) { return _present ? _value : fallback(); } + + bool opEquals(in Option rhs) const { + if (isSome != rhs.isSome) return false; + + if (isSome) return unwrap == rhs.unwrap; + else return true; + } + + size_t toHash() const @safe pure nothrow + { + if (isSome) return this.unwrap.hashOf(true.hashOf); + return false.hashOf; + } } auto some(T)(inout T value) @safe @nogc nothrow { @@ -240,6 +253,29 @@ public: ref inout(E) unwrapErr() inout @trusted @nogc nothrow return in (isErr) do { return _storage.error; } } + + bool opEquals(in Result rhs) const { + if (isErr != rhs.isErr) return false; + + if (isErr) { + static if (!is(E == void)) return unwrapErr == rhs.unwrapErr; + else return true; + } + + static if (!is(T == void)) return unwrap == rhs.unwrap; + else return true; + } + + size_t toHash() const @safe pure nothrow + { + if (isErr) { + static if (!is(E == void)) return this.unwrapErr.hashOf(true.hashOf); + return true.hashOf; + } + + static if (!is(T == void)) return this.unwrap.hashOf(false.hashOf); + else return false.hashOf; + } } auto ok(E, T)(inout T value) @safe @nogc nothrow { diff --git a/tests/runtime/common-types/leaf.d b/tests/runtime/common-types/leaf.d index 2e387263c..b78d4a248 100644 --- a/tests/runtime/common-types/leaf.d +++ b/tests/runtime/common-types/leaf.d @@ -2,7 +2,7 @@ import wit.test.common.leaf; import wit.common; @witExport("test:common/to-test", "wrap") -R1 wrap(in F1 flag) { +R1 wrap(F1 flag) { switch (flag.bits) with (F1) { case a.bits: return R1(1, flag); diff --git a/tests/runtime/common-types/middle.d b/tests/runtime/common-types/middle.d index 5d000f754..50868f65e 100644 --- a/tests/runtime/common-types/middle.d +++ b/tests/runtime/common-types/middle.d @@ -4,7 +4,7 @@ import wit.common; import imps = wit.test.common.to_test.imports; @witExport("test:common/to-test", "wrap") -R1 wrap(in F1 flag) { +R1 wrap(F1 flag) { return imps.wrap(flag); } diff --git a/tests/runtime/results/intermediate.d b/tests/runtime/results/intermediate.d new file mode 100644 index 000000000..2bc6a8ee9 --- /dev/null +++ b/tests/runtime/results/intermediate.d @@ -0,0 +1,43 @@ +import wit.test.results.intermediate; +import imports = wit.test.results.test.imports; + +import wit.common; + +@witExport("test:results/test", "string-error") +Result!(float, WitString) stringError(float a) { + return imports.stringError(a); +} + +@witExport("test:results/test", "enum-error") +Result!(float, E) enumError(float a) { + return imports.enumError(a); +} + +@witExport("test:results/test", "record-error") +Result!(float, E2) recordError(float a) { + return imports.recordError(a); +} + +@witExport("test:results/test", "variant-error") +Result!(float, E3) variantError(float a) { + return imports.variantError(a); +} + +@witExport("test:results/test", "empty-error") +Result!(uint, void) emptyError(uint a) { + return imports.emptyError(a); +} + +@witExport("test:results/test", "double-error") +Result!(Result!(void, WitString), WitString) doubleError(uint a) { + return imports.doubleError(a); +} + +alias Exports = wit.test.results.intermediate.Exports!( + stringError, + enumError, + recordError, + variantError, + emptyError, + doubleError +); diff --git a/tests/runtime/results/leaf.d b/tests/runtime/results/leaf.d new file mode 100644 index 000000000..7b4bb7991 --- /dev/null +++ b/tests/runtime/results/leaf.d @@ -0,0 +1,85 @@ +import wit.test.results.leaf; + +import wit.common; + +@witExport("test:results/test", "string-error") +Result!(float, WitString) stringError(float a) { + if (a == 0.0) { + return "zero".witList.witClone.err!float; + } + + return a.ok!WitString; +} + +@witExport("test:results/test", "enum-error") +Result!(float, E) enumError(float a) { + if (a == 0.0) { + return E.a.err!float; + } + + return a.ok!E; +} + +@witExport("test:results/test", "record-error") +Result!(float, E2) recordError(float a) { + if (a == 0.0) { + return E2( + line: 420, + column: 0 + ).err!float; + } else if (a == 1.0) { + return E2( + line: 77, + column: 2 + ).err!float; + } + + return a.ok!E2; +} + +@witExport("test:results/test", "variant-error") +Result!(float, E3) variantError(float a) { + if (a == 0.0) { + return E3.e2(E2( + line: 420, + column: 0 + )).err!float; + } else if (a == 1.0) { + return E3.e1(E.b).err!float; + } else if (a == 2.0) { + return E3.e1(E.c).err!float; + } + + return a.ok!E3; +} + +@witExport("test:results/test", "empty-error") +Result!(uint, void) emptyError(uint a) { + if (a == 0) { + return err!uint; + } else if (a == 1) { + return 42u.ok!void; + } + + return a.ok!void; +} + +@witExport("test:results/test", "double-error") +Result!(Result!(void, WitString), WitString) doubleError(uint a) { + if (a == 0) { + return ok!WitString.ok!WitString; + } else if (a == 1) { + return "one".witList.witClone.err!void.ok!WitString; + } + + return "two".witList.witClone.err!(Result!(void, WitString)); +} + +alias Exports = wit.test.results.leaf.Exports!( + stringError, + enumError, + recordError, + variantError, + emptyError, + doubleError +); diff --git a/tests/runtime/results/runner.d b/tests/runtime/results/runner.d new file mode 100644 index 000000000..426c5c75e --- /dev/null +++ b/tests/runtime/results/runner.d @@ -0,0 +1,69 @@ +import wit.test.results.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + { + auto result = stringError(0.0); + scope(exit) result.witFree; + + assert(result == "zero".witList.err!float); + } + + { + auto result = stringError(1.0); + scope(exit) result.witFree; + + assert(result == 1.0f.ok!WitString); + } + + assert(enumError(0.0) == E.a.err!float); + assert(enumError(1.0) == 1.0f.ok!E); + + assert(recordError(0.0) == E2( + line: 420, + column: 0 + ).err!float); + assert(recordError(1.0) == E2( + line: 77, + column: 2 + ).err!float); + assert(recordError(2.0).isOk); + + assert(variantError(0.0) == E3.e2(E2( + line: 420, + column: 0 + )).err!float); + assert(variantError(1.0) == E3.e1(E.b).err!float); + assert(variantError(2.0) == E3.e1(E.c).err!float); + + assert(emptyError(0) == err!uint); + assert(emptyError(1) == 42u.ok!void); + assert(emptyError(2) == 2u.ok!void); + + { + auto result = doubleError(0); + scope(exit) result.witFree; + + assert(result == ok!WitString.ok!WitString); + } + + { + auto result = doubleError(1); + scope(exit) result.witFree; + + assert(result == "one".witList.err!void.ok!WitString); + } + + { + auto result = doubleError(2); + scope(exit) result.witFree; + + assert(result == "two".witList.err!(Result!(void, WitString))); + } +} + +alias Exports = wit.test.results.runner.Exports!( + run +); diff --git a/tests/runtime/strings-alias/runner.d b/tests/runtime/strings-alias/runner.d new file mode 100644 index 000000000..52ae6ae83 --- /dev/null +++ b/tests/runtime/strings-alias/runner.d @@ -0,0 +1,17 @@ +import wit.my.strings.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + foo("hello".witList); + + auto str = bar(); + scope(exit) str.witFree; + + assert(str == "world"); +} + +alias Exports = wit.my.strings.runner.Exports!( + run +); diff --git a/tests/runtime/strings-alias/test.d b/tests/runtime/strings-alias/test.d new file mode 100644 index 000000000..2817234d3 --- /dev/null +++ b/tests/runtime/strings-alias/test.d @@ -0,0 +1,18 @@ +import wit.my.strings.test; + +import wit.common; + +@witExport("cat", "foo") +void foo(in MyString str) { + assert(str == "hello"); +} + +@witExport("cat", "bar") +MyString bar() { + return "world".witList.witClone; +} + +alias Exports = wit.my.strings.test.Exports!( + foo, + bar +); diff --git a/tests/runtime/strings-simple/runner.d b/tests/runtime/strings-simple/runner.d new file mode 100644 index 000000000..52ae6ae83 --- /dev/null +++ b/tests/runtime/strings-simple/runner.d @@ -0,0 +1,17 @@ +import wit.my.strings.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + foo("hello".witList); + + auto str = bar(); + scope(exit) str.witFree; + + assert(str == "world"); +} + +alias Exports = wit.my.strings.runner.Exports!( + run +); diff --git a/tests/runtime/strings-simple/test.d b/tests/runtime/strings-simple/test.d new file mode 100644 index 000000000..dde48ffdb --- /dev/null +++ b/tests/runtime/strings-simple/test.d @@ -0,0 +1,18 @@ +import wit.my.strings.test; + +import wit.common; + +@witExport("cat", "foo") +void foo(in WitString str) { + assert(str == "hello"); +} + +@witExport("cat", "bar") +WitString bar() { + return "world".witList.witClone; +} + +alias Exports = wit.my.strings.test.Exports!( + foo, + bar +); diff --git a/tests/runtime/strings/runner.d b/tests/runtime/strings/runner.d new file mode 100644 index 000000000..fb9b6195b --- /dev/null +++ b/tests/runtime/strings/runner.d @@ -0,0 +1,33 @@ +import wit.test.strings.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + takeBasic("latin utf16".witList); + + { + auto str = returnUnicode(); + scope(exit) str.witFree; + + assert(str == "🚀🚀🚀 𠈄𓀀"); + } + + { + auto str = returnEmpty(); + scope(exit) str.witFree; + + assert(str == ""); + } + + { + auto str = roundtrip("🚀🚀🚀 𠈄𓀀".witList); + scope(exit) str.witFree; + + assert(str == "🚀🚀🚀 𠈄𓀀"); + } +} + +alias Exports = wit.test.strings.runner.Exports!( + run +); diff --git a/tests/runtime/strings/test.d b/tests/runtime/strings/test.d new file mode 100644 index 000000000..62ee4cc9d --- /dev/null +++ b/tests/runtime/strings/test.d @@ -0,0 +1,31 @@ +import wit.test.strings.test; + +import wit.common; + +@witExport("test:strings/to-test", "take-basic") +void takeBasic(in WitString str) { + assert(str == "latin utf16"); +} + +@witExport("test:strings/to-test", "return-unicode") +WitString returnUnicode() { + return "🚀🚀🚀 𠈄𓀀".witList.witClone; +} + +@witExport("test:strings/to-test", "return-empty") +WitString returnEmpty() { + return WitString(); +} + +@witExport("test:strings/to-test", "roundtrip") +WitString roundtrip(in WitString str) { + return str.witClone; +} + + +alias Exports = wit.test.strings.test.Exports!( + takeBasic, + returnUnicode, + returnEmpty, + roundtrip +); diff --git a/tests/runtime/symbol-conflicts/runner.d b/tests/runtime/symbol-conflicts/runner.d new file mode 100644 index 000000000..79f104033 --- /dev/null +++ b/tests/runtime/symbol-conflicts/runner.d @@ -0,0 +1,15 @@ +import wit.my.inline.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + wit.my.inline.foo1.imports.foo(); + wit.my.inline.foo2.imports.foo(); + wit.my.inline.bar1.imports.bar(); + wit.my.inline.bar2.imports.bar(); +} + +alias Exports = wit.my.inline.runner.Exports!( + run +); diff --git a/tests/runtime/symbol-conflicts/test.d b/tests/runtime/symbol-conflicts/test.d new file mode 100644 index 000000000..1d9b0997d --- /dev/null +++ b/tests/runtime/symbol-conflicts/test.d @@ -0,0 +1,23 @@ +import wit.my.inline.test; + +import wit.common; + +@witExport("my:inline/foo1", "foo") +void foo1() {} + +@witExport("my:inline/foo2", "foo") +void foo2() {} + +@witExport("my:inline/bar1", "bar") +WitString bar1() { return WitString(); } + +@witExport("my:inline/bar2", "bar") +WitString bar2() { return WitString(); } + + +alias Exports = wit.my.inline.test.Exports!( + foo1, + foo2, + bar1, + bar2 +); diff --git a/tests/runtime/unused-types/runner.d b/tests/runtime/unused-types/runner.d new file mode 100644 index 000000000..81a55e9ac --- /dev/null +++ b/tests/runtime/unused-types/runner.d @@ -0,0 +1,14 @@ +import wit.foo.bar.runner; + +import wit.foo.bar.component.common : UnusedEnum, UnusedRecord, UnusedVariant; + +import wit.common; + +@witExport("$root", "run") +void run() { + foo(); +} + +alias Exports = wit.foo.bar.runner.Exports!( + run +); diff --git a/tests/runtime/unused-types/test.d b/tests/runtime/unused-types/test.d new file mode 100644 index 000000000..bf0bba35a --- /dev/null +++ b/tests/runtime/unused-types/test.d @@ -0,0 +1,12 @@ +import wit.foo.bar.test; + +import wit.foo.bar.component.common : UnusedEnum, UnusedRecord, UnusedVariant; + +import wit.common; + +@witExport("foo:bar/component", "foo") +void foo() {} + +alias Exports = wit.foo.bar.test.Exports!( + foo +); From 106fab3002854b5cae733850f85e1a54a851b84b Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 20 Aug 2026 01:19:15 -0700 Subject: [PATCH 51/55] Most of the rest of the resource tests --- .../resource_borrow_in_record/runner.d | 31 ++++ .../runtime/resource_borrow_in_record/test.d | 61 +++++++ tests/runtime/resource_floats/intermediate.d | 55 ++++++ tests/runtime/resource_floats/leaf.d | 54 ++++++ tests/runtime/resource_floats/runner.d | 31 ++++ tests/runtime/resource_with_lists/leaf.d | 61 +++++++ .../resource_with_lists/resource-with-lists.d | 75 ++++++++ tests/runtime/resource_with_lists/runner.d | 36 ++++ tests/runtime/resources/leaf.d | 38 +++++ tests/runtime/resources/resources.d | 160 ++++++++++++++++++ tests/runtime/resources/runner.d | 52 ++++++ 11 files changed, 654 insertions(+) create mode 100644 tests/runtime/resource_borrow_in_record/runner.d create mode 100644 tests/runtime/resource_borrow_in_record/test.d create mode 100644 tests/runtime/resource_floats/intermediate.d create mode 100644 tests/runtime/resource_floats/leaf.d create mode 100644 tests/runtime/resource_floats/runner.d create mode 100644 tests/runtime/resource_with_lists/leaf.d create mode 100644 tests/runtime/resource_with_lists/resource-with-lists.d create mode 100644 tests/runtime/resource_with_lists/runner.d create mode 100644 tests/runtime/resources/leaf.d create mode 100644 tests/runtime/resources/resources.d create mode 100644 tests/runtime/resources/runner.d diff --git a/tests/runtime/resource_borrow_in_record/runner.d b/tests/runtime/resource_borrow_in_record/runner.d new file mode 100644 index 000000000..02f44b30e --- /dev/null +++ b/tests/runtime/resource_borrow_in_record/runner.d @@ -0,0 +1,31 @@ +import wit.test.resource_borrow_in_record.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + auto thing1 = Thing.makeNew("Bonjour".witList); + scope(exit) thing1.witDrop; + + auto thing2 = Thing.makeNew("mon cher".witList); + scope(exit) thing2.witDrop; + + Foo[2] things = [ + Foo(thing1), + Foo(thing2) + ]; + + auto result = test(things.witList); + scope(exit) { + result.witDrop; + result.witFree; + } + + assert(result.length == 2); + assert(result[0].get == "Bonjour new test get"); + assert(result[1].get == "mon cher new test get"); +} + +alias Exports = wit.test.resource_borrow_in_record.runner.Exports!( + run +); diff --git a/tests/runtime/resource_borrow_in_record/test.d b/tests/runtime/resource_borrow_in_record/test.d new file mode 100644 index 000000000..190e40133 --- /dev/null +++ b/tests/runtime/resource_borrow_in_record/test.d @@ -0,0 +1,61 @@ +import wit.test.resource_borrow_in_record.test; + +import wit.common; + +extern(C) void* malloc(size_t size); +extern(C) void free(void* ptr); + +char[] concat(in char[] a, in char[] b) { + auto ptr = cast(char*)malloc(a.length + b.length); + assert((a.length + b.length) == 0 || ptr); + + if (!ptr) return null; + + ptr[0..a.length] = a[]; + ptr[a.length..a.length+b.length] = b[]; + + return ptr[0..a.length+b.length]; +} + +@witExport("test:resource-borrow-in-record/to-test", "thing") +struct ThingImpl { + const(char)[] contents; + + @witExport("test:resource-borrow-in-record/to-test", "[constructor]thing") + static Thing constructor(in WitString v) { + return Thing.makeNew((out typeof(this) self) { + self.contents = concat(v, " new"); + }); + } + + @witExport("test:resource-borrow-in-record/to-test", "[method]thing.get") + WitString get() { + return concat(contents, " get").witList; + } +} + +@witExport("test:resource-borrow-in-record/to-test", "test") +WitList!Thing test(ref scope WitList!Foo list) { + if (list.length == 0) return WitList!Thing(); + + auto ptr = cast(Thing*)malloc(Thing.sizeof*list.length); + assert(ptr); + + auto things = ptr[0..list.length]; + + foreach (i, ref thing; things) { + auto orig = list[i].thing.rep!ThingImpl.contents; + auto str = concat(orig, " test"); + + thing = Thing.makeNew((out ThingImpl self) { + self.contents = str; + }); + } + + return things.witList; // already on C heap; no clone +} + +alias Exports = wit.test.resource_borrow_in_record.test.Exports!( + ThingImpl, + test +); diff --git a/tests/runtime/resource_floats/intermediate.d b/tests/runtime/resource_floats/intermediate.d new file mode 100644 index 000000000..125709f37 --- /dev/null +++ b/tests/runtime/resource_floats/intermediate.d @@ -0,0 +1,55 @@ +import wit.test.resource_floats.intermediate; + +import wit.test.resource_floats.intermediate.exports.exports : EFloat = Float; + +import wit.test.resource_floats.intermediate.imports.imports : IFloat1 = Float; +import wit.test.resource_floats.test.imports : IFloat2 = Float; + +import wit.common; + +@witExport("exports", "float") +struct FloatImpl { + IFloat1 val; + + @witExport("exports", "[constructor]float") + static EFloat constructor(double v) { + return EFloat.makeNew((out typeof(this) self) { + self.val = IFloat1.makeNew(v + 1); + }); + } + + @witExport("exports", "[method]float.get") + double get() { + return val.get + 3; + } + + @witExport("exports", "[static]float.add") + static EFloat add(EFloat a, double b) { + scope(exit) a.witDrop; + + auto added = IFloat1.add(a.rep!FloatImpl.val, b); + scope(exit) added.witDrop; + + return FloatImpl.constructor( + added.get + 5 + ); + } +} + + +@witExport("$root", "add") +static IFloat2 add(IFloat2.Borrow a, IFloat2.Borrow b) { + scope(exit) { + a.witDrop; + b.witDrop; + } + + return IFloat2.makeNew( + a.get + b.get + 5 + ); +} + +alias Exports = wit.test.resource_floats.intermediate.Exports!( + FloatImpl, + add +); diff --git a/tests/runtime/resource_floats/leaf.d b/tests/runtime/resource_floats/leaf.d new file mode 100644 index 000000000..8c39af757 --- /dev/null +++ b/tests/runtime/resource_floats/leaf.d @@ -0,0 +1,54 @@ +import wit.test.resource_floats.leaf; + +import wit.test.resource_floats.leaf.exports.imports : Float; + +import wit.test.resource_floats.test.exports : Float2 = Float; +import wit.common; + +@witExport("imports", "float") +struct FloatImpl { + double val; + + @witExport("imports", "[constructor]float") + static Float constructor(double v) { + return Float.makeNew((out typeof(this) self) { + self.val = v + 2; + }); + } + + @witExport("imports", "[method]float.get") + double get() { + return val + 4; + } + + @witExport("imports", "[static]float.add") + static Float add(Float a, double b) { + scope(exit) a.witDrop; + + return FloatImpl.constructor( + a.rep!FloatImpl.val + b + 6 + ); + } +} + +@witExport("test:resource-floats/test", "float") +struct Float2Impl { + double val; + + @witExport("test:resource-floats/test", "[constructor]float") + static Float2 constructor(double v) { + return Float2.makeNew((out typeof(this) self) { + self.val = v + 1; + }); + } + + @witExport("test:resource-floats/test", "[method]float.get") + double get() { + return val + 3; + } +} + +alias Exports = wit.test.resource_floats.leaf.Exports!( + FloatImpl, + Float2Impl +); diff --git a/tests/runtime/resource_floats/runner.d b/tests/runtime/resource_floats/runner.d new file mode 100644 index 000000000..cac87830f --- /dev/null +++ b/tests/runtime/resource_floats/runner.d @@ -0,0 +1,31 @@ +import wit.test.resource_floats.runner; + +import wit.test.resource_floats.runner.imports.exports : Float2 = Float; + +import wit.common; + +@witExport("$root", "run") +void run() { + auto float1 = Float.makeNew(42); + scope(exit) float1.witDrop; + + auto float2 = Float.makeNew(55); + scope(exit) float2.witDrop; + + auto float3 = add(float1, float2); + scope(exit) float3.witDrop; + assert(float3.get == 114.0); + + auto float4 = Float2.makeNew(22); + scope(exit) float4.witDrop; + assert(float4.get == 22.0 + 1.0 + 2.0 + 4.0 + 3.0); + + auto res = Float2.add(float4, 7.0); + scope(exit) res.witDrop; + float4 = Float2.init; // consumed + assert(res.get == 59.0); +} + +alias Exports = wit.test.resource_floats.runner.Exports!( + run +); diff --git a/tests/runtime/resource_with_lists/leaf.d b/tests/runtime/resource_with_lists/leaf.d new file mode 100644 index 000000000..eb7718ead --- /dev/null +++ b/tests/runtime/resource_with_lists/leaf.d @@ -0,0 +1,61 @@ +import wit.test.resource_with_lists.leaf; + +import wit.common; + +extern(C) void* malloc(size_t size); +extern(C) void free(void* ptr); + +ubyte[] concat(in ubyte[] a, in char[] b) { + auto ptr = cast(ubyte*)malloc(a.length + b.length); + assert((a.length + b.length) == 0 || ptr); + + if (!ptr) return null; + + ptr[0..a.length] = a[]; + ptr[a.length..a.length+b.length] = cast(const(ubyte[]))b[]; + + return ptr[0..a.length+b.length]; +} + +@witExport("test:resource-with-lists/test", "thing") +struct ThingImpl { + ubyte[] val; + + @witExport("test:resource-with-lists/test", "[constructor]thing") + static Thing constructor(in WitList!ubyte a) { + return Thing.makeNew((out typeof(this) self) { + auto result = a.concat(" HostThing"); + + self.val = result; + }); + } + + @witExport("test:resource-with-lists/test", "[method]thing.foo") + WitList!ubyte foo() { + auto result = val.concat(" HostThing.foo"); + return result.witList; // no clone, no free; already on C heap + } + + @witExport("test:resource-with-lists/test", "[method]thing.bar") + auto bar(in WitList!ubyte l) { + auto result = l.concat(" HostThing.bar"); + + if (val.ptr) free(val.ptr); + val = result; + } + + + @witExport("test:resource-with-lists/test", "[static]thing.baz") + static WitList!ubyte baz(in WitList!ubyte l) { + auto result = l.concat(" HostThing.baz"); + return result.witList; // no clone, no free; already on C heap + } + + ~this() { + if (val.ptr) free(val.ptr); + } +} + +alias Exports = wit.test.resource_with_lists.leaf.Exports!( + ThingImpl +); diff --git a/tests/runtime/resource_with_lists/resource-with-lists.d b/tests/runtime/resource_with_lists/resource-with-lists.d new file mode 100644 index 000000000..123361b71 --- /dev/null +++ b/tests/runtime/resource_with_lists/resource-with-lists.d @@ -0,0 +1,75 @@ +module resource_with_lists; + +import wit.test.resource_with_lists.resource_with_lists; +import wit.test.resource_with_lists.test.imports : IThing = Thing; +import wit.test.resource_with_lists.test.exports : EThing = Thing; + +import wit.common; + +extern(C) void* malloc(size_t size); +extern(C) void free(void* ptr); + +ubyte[] concat(in ubyte[] a, in char[] b) { + auto ptr = cast(ubyte*)malloc(a.length + b.length); + assert((a.length + b.length) == 0 || ptr); + + if (!ptr) return null; + + ptr[0..a.length] = a[]; + ptr[a.length..a.length+b.length] = cast(const(ubyte[]))b[]; + + return ptr[0..a.length+b.length]; +} + +@witExport("test:resource-with-lists/test", "thing") +struct ThingImpl { + IThing val; + + @witExport("test:resource-with-lists/test", "[constructor]thing") + static EThing constructor(in WitList!ubyte a) { + return EThing.makeNew((out typeof(this) self) { + auto result = a.concat(" Thing"); + scope(exit) free(result.ptr); + + self.val = IThing.makeNew(result.witList); + }); + } + + @witExport("test:resource-with-lists/test", "[method]thing.foo") + WitList!ubyte foo() { + auto list = val.foo; + scope(exit) list.witFree; + + auto result = list.concat(" Thing.foo"); + return result.witList; // no clone, no free; already on C heap + } + + @witExport("test:resource-with-lists/test", "[method]thing.bar") + auto bar(in WitList!ubyte l) { + auto result = l.concat(" Thing.bar"); + scope(exit) free(result.ptr); + + val.bar(result.witList); + } + + + @witExport("test:resource-with-lists/test", "[static]thing.baz") + static WitList!ubyte baz(in WitList!ubyte l) { + auto input = l.concat(" Thing.baz"); + scope(exit) free(input.ptr); + + auto impBaz = IThing.baz(input.witList); + scope(exit) impBaz.witFree; + + auto result = impBaz.concat(" Thing.baz again"); + return result.witList; + } + + ~this() { + val.witDrop; + } +} + +alias Exports = wit.test.resource_with_lists.resource_with_lists.Exports!( + ThingImpl +); diff --git a/tests/runtime/resource_with_lists/runner.d b/tests/runtime/resource_with_lists/runner.d new file mode 100644 index 000000000..23783700a --- /dev/null +++ b/tests/runtime/resource_with_lists/runner.d @@ -0,0 +1,36 @@ +import wit.test.resource_with_lists.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + auto thingInstance = Thing.makeNew((cast(immutable ubyte[])"Hi").witList); + scope(exit) thingInstance.witDrop; + + { + auto result = thingInstance.foo(); + scope(exit) result.witFree; + + assert(cast(char[])result[] == "Hi Thing HostThing HostThing.foo Thing.foo"); + } + + thingInstance.bar((cast(immutable ubyte[])"Hola").witList); + + { + auto result = thingInstance.foo(); + scope(exit) result.witFree; + + assert(cast(char[])result[] == "Hola Thing.bar HostThing.bar HostThing.foo Thing.foo"); + } + + { + auto result = thingInstance.baz((cast(immutable ubyte[])"Ohayo Gozaimas").witList); + scope(exit) result.witFree; + + assert(cast(char[])result[] == "Ohayo Gozaimas Thing.baz HostThing.baz Thing.baz again"); + } +} + +alias Exports = wit.test.resource_with_lists.runner.Exports!( + run +); diff --git a/tests/runtime/resources/leaf.d b/tests/runtime/resources/leaf.d new file mode 100644 index 000000000..59b19aaca --- /dev/null +++ b/tests/runtime/resources/leaf.d @@ -0,0 +1,38 @@ +import wit.test.resources.leaf; + +import wit.common; + +@witExport("imports", "y") +struct YImpl { + int val; + + @witExport("imports", "[constructor]y") + static Y constructor(int a) { + return Y.makeNew((out typeof(this) self) { + self.val = a; + }); + } + + @witExport("imports", "[method]y.get-a") + int getA() { + return val; + } + + @witExport("imports", "[method]y.set-a") + void setA(int a) { + val = a; + } + + @witExport("imports", "[static]y.add") + static Y add(Y y, int a) { + scope(exit) y.witDrop; + + return Y.makeNew((out typeof(this) self) { + self.val = y.rep!YImpl.getA + a; + }); + } +} + +alias Exports = wit.test.resources.leaf.Exports!( + YImpl +); diff --git a/tests/runtime/resources/resources.d b/tests/runtime/resources/resources.d new file mode 100644 index 000000000..2c61c706d --- /dev/null +++ b/tests/runtime/resources/resources.d @@ -0,0 +1,160 @@ +import wit.test.resources.resources; + +import wit.common; + +@witExport("exports", "x") +struct XImpl { + int val; + + @witExport("exports", "[constructor]x") + static X constructor(int a) { + return X.makeNew((out typeof(this) self) { + self.val = a; + }); + } + + @witExport("exports", "[method]x.get-a") + int getA() { + return val; + } + + @witExport("exports", "[method]x.set-a") + void setA(int a) { + val = a; + } + + @witExport("exports", "[static]x.add") + static X add(X x, int a) { + scope(exit) x.witDrop; + + return X.makeNew((out typeof(this) self) { + self.val = x.rep!XImpl.getA + a; + }); + } +} + +@witExport("exports", "z") +struct ZImpl { + static uint numDropped = 0; + + int val; + + @witExport("exports", "[constructor]z") + static Z constructor(int a) { + return Z.makeNew((out typeof(this) self) { + self.val = a; + }); + } + + @witExport("exports", "[method]z.get-a") + int getA() { + return val; + } + + @witExport("exports", "[static]z.num-dropped") + static uint getNumDropped() { + return numDropped + 1; + } + + ~this() { + numDropped += 1; + } +} + +@witExport("exports", "kebab-case") +struct KebabCaseImpl { + uint val; + + @witExport("exports", "[constructor]kebab-case") + static KebabCase constructor(uint a) { + return KebabCase.makeNew((out typeof(this) self) { + self.val = a; + }); + } + + @witExport("exports", "[method]kebab-case.get-a") + uint getA() { + return val; + } + + @witExport("exports", "[static]kebab-case.take-owned") + static uint takeOwned(KebabCase k) { + scope(exit) k.witDrop; + + return k.rep!KebabCaseImpl.getA; + } +} + + +@witExport("exports", "add") +Z add(Z.Borrow a, Z.Borrow b) { + scope(exit) { + a.witDrop; + b.witDrop; + } + + return ZImpl.constructor(a.rep!ZImpl.val + b.rep!ZImpl.val); +} + + +@witExport("exports", "consume") +void consume(X x) { + x.witDrop; +} + + +@witExport("exports", "test-imports") +Result!(void, WitString) testImports() { + { + auto y = Y.makeNew(10); + scope(exit) y.witDrop; + assert(y.getA == 10); + y.setA(20); + assert(y.getA == 20); + + auto y2 = Y.add(y, 20); + scope(exit) y2.witDrop; + y = Y.init; // consumed + assert(y2.getA == 40); + } + + { + auto y1 = Y.makeNew(1); + scope(exit) y1.witDrop; + auto y2 = Y.makeNew(2); + scope(exit) y2.witDrop; + + assert(y1.getA == 1); + assert(y2.getA == 2); + + y1.setA(10); + y2.setA(20); + assert(y1.getA == 10); + assert(y2.getA == 20); + + auto y3 = Y.add(y1, 20); + scope(exit) y3.witDrop; + y1 = Y.init; // consumed + assert(y3.getA == 30); + + auto y4 = Y.add(y2, 30); + scope(exit) y4.witDrop; + y2 = Y.init; // consumed + assert(y4.getA == 50); + } + + + return ok!WitString; +} + + + +alias Exports = wit.test.resources.resources.Exports!( + XImpl, + ZImpl, + KebabCaseImpl, + + add, + consume, + testImports +); diff --git a/tests/runtime/resources/runner.d b/tests/runtime/resources/runner.d new file mode 100644 index 000000000..db4bdd5d9 --- /dev/null +++ b/tests/runtime/resources/runner.d @@ -0,0 +1,52 @@ +import wit.test.resources.runner; + +import wit.common; + +@witExport("$root", "run") +void run() { + { + auto result = testImports(); + scope(exit) result.witFree; + + assert(result.isOk); + } + + auto x = X.makeNew(5); + scope(exit) x.witDrop; + assert(x.getA == 5); + x.setA(10); + assert(x.getA == 10); + + auto z1 = Z.makeNew(10); + scope(exit) z1.witDrop; + assert(z1.getA() == 10); + + auto z2 = Z.makeNew(20); + scope(exit) z2.witDrop; + assert(z2.getA() == 20); + + auto xadd = X.add(x, 5); + scope(exit) xadd.witDrop; + x = X.init; // consumed + assert(xadd.getA() == 15); + + auto zadd = add(z1, z2); + scope(exit) zadd.witDrop; + assert(zadd.getA() == 30); + + auto droppedZsStart = Z.numDropped; + z1.witDrop; + z2.witDrop; + + consume(xadd); + xadd = X.init; // consumed + + auto droppedZsEnd = Z.numDropped; + if (droppedZsStart != 0) { + assert(droppedZsEnd == droppedZsStart + 2); + } +} + +alias Exports = wit.test.resources.runner.Exports!( + run +); From f546d200abcb62ffff6e1617fa547c2925ef7ad2 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 20 Aug 2026 12:32:48 -0700 Subject: [PATCH 52/55] Fix `crates/test/src/d.rs` --- crates/test/src/d.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/test/src/d.rs b/crates/test/src/d.rs index 6ee1bc7e9..43563ce2b 100644 --- a/crates/test/src/d.rs +++ b/crates/test/src/d.rs @@ -163,5 +163,6 @@ fn verify(runner: &Runner, verify: &Verify<'_>, compiler: PathBuf) -> Result<()> .arg("--preview=in") .arg("-of") .arg(verify.artifacts_dir.join("tmp.o")); - runner.run_command(&mut cmd) + runner.run_command(&mut cmd)?; + Ok(()) } From 1dda1bfdcbf684050b2e5dde1195b7b1835538f4 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 20 Aug 2026 13:44:01 -0700 Subject: [PATCH 53/55] `resource_alias` tests and brief README --- crates/d/README.md | 34 ++++++++- tests/runtime/resource_alias/runner.d | 36 ++++++++++ tests/runtime/resource_alias/test.d | 45 ++++++++++++ tests/runtime/resource_alias_redux/runner.d | 78 +++++++++++++++++++++ tests/runtime/resource_alias_redux/test.d | 75 ++++++++++++++++++++ 5 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 tests/runtime/resource_alias/runner.d create mode 100644 tests/runtime/resource_alias/test.d create mode 100644 tests/runtime/resource_alias_redux/runner.d create mode 100644 tests/runtime/resource_alias_redux/test.d diff --git a/crates/d/README.md b/crates/d/README.md index ebb79098b..646c52eb6 100644 --- a/crates/d/README.md +++ b/crates/d/README.md @@ -12,6 +12,36 @@ $ wit-bindgen d [OPTIONS] See the output of `wit-bindgen help d` for available options. -------- +## Output Structure -TODO: Flesh out fuller docs (ownership, more usage, examples, etc.) +Running `wit-bindgen d` on a WIT world will produce a full package structure mirroring the organization of the WIT world and the interfaces it uses. The default output directory and root package are both `wit`. + +`wit.common` holds some definitions used across the generated bindings (for e.g. `result`, `option`, `list`, etc.). + +The generated file for the world will by default, `public import` all relavent definitions. More selective imports can be made by importing the appropriate modules corresponding to particular interfaces. + +The module for the world `foo:bar/world` will be placed in `wit/foo/bar/world/package.d`. Anonymous interface import and exports for the world will emitted in `wit/foo/bar/world/{imports|exports}`. + +Top-level interfaces will be output similarly to worlds. `foo:bar/interface` gets split across multiple modules in `wit/foo/bar/interface/*.d`. Type definitions that are agnostic to import or export (any records, tuples, lists, etc. that do not contain resource handles) will go in `/common.d`. Function imports and import-specific types go in `/imports.d`, and exports go in `/exports.d` + +## Memory/Resource Management + +Currently, all imports take in parameters as "borrowing" and return memory "owning", with some caveats due to resource handles. + +Memory is managed on the C `malloc` heap. This assumption can be used in some cases to reduce copies when unecessary. + +When giving parameters to imports, any `list` or `string` memory is NOT freed, and the caller retains ownership/responibility for it. However, the ownership rules of WIT mean that any owning resource handles are invalidated. The bindings will not automatically "consume" these for you, and you will have to zero them out (default init) yourself after the call to prevent double-free. + +When receiving parameters to exports, `list` and `string` memory is automatically freed. You must copy this memory elsewhere to retain it past the end of the call. Resource handles are not dropped however, and BOTH owning and borrowing handles (e.g. `Res` and `Res.Borrow`) must be dropped. To drop a handle, use `witDrop`. + +When receiving values from imports, you are in charge of freeing the memory and dropping resource handles after you are finished with it/them. `witFree` deeply frees all such memory, and `witDrop` is also deep. + +When return values from exports, all memory must be on the WIT/C heap, so it can be reliably `free`d by the bindings. `witClone` deeply copies all `list`s and `string`s. + +`scope(exit)` is a valuable tool in helping keep track of this. + +In the future, changes may be made to help make this more automatic. + +## Examples + +It is recommended to peruse `tests/runtime` to find concrete examples of how to use the bindings. diff --git a/tests/runtime/resource_alias/runner.d b/tests/runtime/resource_alias/runner.d new file mode 100644 index 000000000..420ff52c4 --- /dev/null +++ b/tests/runtime/resource_alias/runner.d @@ -0,0 +1,36 @@ +import wit.test.resource_alias.runner; + +import wit.test.resource_alias.e1.imports : a1 = a, Foo1 = Foo, X; +import wit.test.resource_alias.e2.imports : a2 = a, Foo2 = Foo; + +import wit.common; + +@witExport("$root", "run") +void run() { + auto fooE1 = Foo1( + x: X.makeNew(42) + ); + + // consumed later + + a1(fooE1); + + auto fooE2 = Foo2( + x: X.makeNew(7) + ); + // consumed later + + auto barE2 = Foo1( + x: X.makeNew(8) + ); + // consumed later + + auto y = X.makeNew(8); + scope(exit) y.witDrop; + + a2(fooE2, barE2, y); +} + +alias Exports = wit.test.resource_alias.runner.Exports!( + run +); diff --git a/tests/runtime/resource_alias/test.d b/tests/runtime/resource_alias/test.d new file mode 100644 index 000000000..7d42b10f0 --- /dev/null +++ b/tests/runtime/resource_alias/test.d @@ -0,0 +1,45 @@ +import wit.test.resource_alias.test; + +import wit.test.resource_alias.e1.exports : Foo1 = Foo, X; +import wit.test.resource_alias.e2.exports : Foo2 = Foo, Bar, Y; + +import wit.common; + +@witExport("test:resource-alias/e1", "x") +struct XImpl { + uint val; + + @witExport("test:resource-alias/e1", "[constructor]x") + static X constructor(uint v) { + return X.makeNew((out typeof(this) self) { + self.val = v; + }); + } +} + +@witExport("test:resource-alias/e1", "a") +WitList!X a1(ref scope Foo1 f) { + // `f.x` consumed by return + + immutable X[1] ret = [f.x]; + + return ret.witList.witClone; +} + +@witExport("test:resource-alias/e2", "a") +WitList!Y a2(ref scope Foo2 f, ref scope Bar g, Y.Borrow h) { + // `f.x` consumed by return + // `f.g` consumed by return + //scope(exit) h.witDrop; + + immutable X[2] ret = [f.x, g.x]; + + return ret.witList.witClone; +} + + +alias Exports = wit.test.resource_alias.test.Exports!( + XImpl, + a1, + a2 +); diff --git a/tests/runtime/resource_alias_redux/runner.d b/tests/runtime/resource_alias_redux/runner.d new file mode 100644 index 000000000..109fb8f5a --- /dev/null +++ b/tests/runtime/resource_alias_redux/runner.d @@ -0,0 +1,78 @@ +import wit.test.resource_alias_redux.runner; + +import wit.test.resource_alias_redux.runner.imports.the_test : Thing1 = Thing; +import wit.test.resource_alias_redux.resource_alias1.imports : Foo1 = Foo, Thing2 = Thing; +import wit.test.resource_alias_redux.resource_alias2.imports : Foo2 = Foo; + +import wit.common; + +@witExport("$root", "run") +void run() { + auto thing1 = Thing.makeNew("Ni Hao".witList); + scope(exit) thing1.witDrop; + + { + Thing1[1] things = [thing1]; + auto result = test(things.witList); + thing1 = Thing1.init; // consumed + scope(exit) result.witFree; + assert(result.length == 1); + + { + auto str = result[0].get; + scope(exit) str.witFree; + + assert(str == "Ni Hao GuestThing GuestThing.get"); + } + } + + + auto thing2 = Thing2.makeNew("Ciao".witList); + scope(exit) thing2.witDrop; + + { + auto result = a(Foo1(thing: thing2)); + thing2 = Thing2.init; // consumed + scope(exit) result.witFree; + assert(result.length == 1); + + { + auto str = result[0].get; + scope(exit) str.witFree; + + assert(str == "Ciao GuestThing GuestThing.get"); + } + } + + + auto thing3 = Thing2.makeNew("Ciao".witList); + scope(exit) thing3.witDrop; + auto thing4 = Thing2.makeNew("Aloha".witList); + scope(exit) thing4.witDrop; + + { + auto result = b(Foo2(thing: thing3), Bar(thing: thing4)); + thing3 = Thing2.init; // consumed + thing4 = Thing2.init; // consumed + scope(exit) result.witFree; + assert(result.length == 2); + + { + auto str = result[0].get; + scope(exit) str.witFree; + + assert(str == "Ciao GuestThing GuestThing.get"); + } + + { + auto str = result[1].get; + scope(exit) str.witFree; + + assert(str == "Aloha GuestThing GuestThing.get"); + } + } +} + +alias Exports = wit.test.resource_alias_redux.runner.Exports!( + run +); diff --git a/tests/runtime/resource_alias_redux/test.d b/tests/runtime/resource_alias_redux/test.d new file mode 100644 index 000000000..cb77b2dd8 --- /dev/null +++ b/tests/runtime/resource_alias_redux/test.d @@ -0,0 +1,75 @@ +import wit.test.resource_alias_redux.test; + +import wit.test.resource_alias_redux.test.exports.the_test : Thing1 = Thing; +import wit.test.resource_alias_redux.resource_alias1.exports : Foo1 = Foo, Thing2 = Thing; +import wit.test.resource_alias_redux.resource_alias2.exports : Foo2 = Foo; +import wit.common; + +extern(C) void* malloc(size_t size); +extern(C) void free(void* ptr); + +char[] concat(in char[] a, in char[] b) { + auto ptr = cast(char*)malloc(a.length + b.length); + assert((a.length + b.length) == 0 || ptr); + + if (!ptr) return null; + + ptr[0..a.length] = a[]; + ptr[a.length..a.length+b.length] = b[]; + + return ptr[0..a.length+b.length]; +} + +@witExport("test:resource-alias-redux/resource-alias1", "thing") +struct ThingImpl { + const(char)[] str; + + @witExport("test:resource-alias-redux/resource-alias1", "[constructor]thing") + static Thing1 constructor(in WitString msg) { + return Thing1.makeNew((out typeof(this) self) { + self.str = concat(msg, " GuestThing"); + }); + } + + @witExport("test:resource-alias-redux/resource-alias1", "[method]thing.get") + WitString get() { + return concat(str, " GuestThing.get").witList; // no clone; already on C heap + } +} + +@witExport("test:resource-alias-redux/resource-alias1", "a") +WitList!Thing1 a(scope ref Foo1 f) { + scope(exit) f.witDrop; + + Thing1[1] things = [f.thing]; + f.thing = Thing1.init; // consumed + + return things.witList.witClone; +} + +@witExport("test:resource-alias-redux/resource-alias2", "b") +WitList!Thing2 b(scope ref Foo2 f, scope ref Bar g) { + scope(exit) { + f.witDrop; + g.witDrop; + } + + Thing1[2] things = [f.thing, g.thing]; + f.thing = Thing2.init; // consumed + g.thing = Thing2.init; // consumed + + return things.witList.witClone; +} + +@witExport("the-test", "test") +WitList!Thing1 test(scope ref WitList!Thing1 things) { + return things.witClone; +} + + +alias Exports = wit.test.resource_alias_redux.test.Exports!( + ThingImpl, + a, + b, + test +); From aabf807f101159a5786e586448ea52dbbf3f3383 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Thu, 20 Aug 2026 14:13:02 -0700 Subject: [PATCH 54/55] Rewind back to 0.60.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 649c59a73..de39607ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1456,8 +1456,8 @@ dependencies = [ "clap", "heck", "indexmap", - "wasm-encoder 0.257.0", - "wasm-metadata 0.257.0", + "wasm-encoder 0.254.0", + "wasm-metadata 0.254.0", "wit-bindgen-core", "wit-component", ] From b2811559dfe099e3feafc05009421c33844ead76 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 21 Aug 2026 09:47:20 -0700 Subject: [PATCH 55/55] Fix `witList` parameter, and same for `witClone` & `witFree` for `T[L]` --- crates/d/src/wit_common.d | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d index b666cf866..e8230add7 100644 --- a/crates/d/src/wit_common.d +++ b/crates/d/src/wit_common.d @@ -34,7 +34,7 @@ struct WitList(T) { bool opEquals(in T[] other) const => this[] == other; size_t toHash() const => this[].hashOf; } -auto witList(T : U[], U)(inout T slice) => inout WitList!U(slice); +auto witList(T)(inout T[] slice) => inout WitList!T(slice); // WIT ABI for string matches List, // except list in WIT is actually List!(dchar) @@ -363,7 +363,7 @@ void witDrop(T : WitList!U, U)(scope ref T val) { } val = null; } -T witClone(T : WitList!U, U)(in T val) { +T witClone(T : WitList!U, U)(in T val) @trusted { if (val.ptr == null || val.length == 0) return T(null); auto clone = mallocSlice!U(val.length); @@ -394,18 +394,18 @@ T witClone(T : Tuple!U, U...)(in T val) { } -void witFree(T : U[L], U, size_t L)(scope ref T val) { +void witFree(T, size_t L)(scope ref T[L] val) { foreach (ref e; val) { e.witFree; } } -void witDrop(T : U[L], U, size_t L)(scope ref T val) { +void witDrop(T, size_t L)(scope ref T[L] val) { foreach (ref e; val) { e.witDrop; } } -T witClone(T : U[L], U, size_t L)(in T val) { - T clone; +T[L] witClone(T, size_t L)(in T[L] val) { + T[L] clone; foreach (i, ref e; clone) { e = val[i].witClone; }