diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3a86dcb49..de1337521 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: ldc-1.42 + 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' diff --git a/Cargo.lock b/Cargo.lock index ec2bd3b10..de39607ea 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.254.0", + "wasm-metadata 0.254.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/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", 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..dc64d0e88 --- /dev/null +++ b/crates/d/src/lib.rs @@ -0,0 +1,3430 @@ +use anyhow::Result; +use heck::*; +use std::borrow::Cow; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::mem::{replace, take}; +use std::path::PathBuf; +use wit_bindgen_core::{ + Direction, Files, InterfaceGenerator, Source, Types, WorldGenerator, + abi::{self, Bindgen, Bitcast, WasmType}, + wit_parser::*, +}; + +type DType = String; +#[derive(Default, Debug)] +struct DSig { + static_member: bool, + result: DType, + arguments: Vec<(String, DType)>, + name: String, + implicit_self: bool, + post_return: bool, +} + +#[derive(Default)] +struct D { + root_pkg: String, + common_module: String, + + 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, + + world_id: Option, + world_fqn: String, + interface_fqns: HashMap, + + cur_interface: Option, + + types: Types, +} + +#[derive(Default, Debug)] +struct InterfaceFQNSet { + import: Option, + export: Option, + common: Option, +} + +#[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, + + /// 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 custom section containing + /// the component type. + #[cfg_attr(feature = "clap", arg(long, value_name = "STRING"))] + pub type_section_suffix: Option, + + /// Choose root package other than `wit` to nest everything under. + #[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 + /// 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, + */ + /// 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 { + 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 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_", + + // 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_", + "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` + "rep" => "rep_", // part of the expansion of `resource` + "makeNew" => "makeNew_", // part of the expansion of `resource` + "constructor" => "constructor_", // 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(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 + && p.name.name == pkg.name.name + && p.name.version != pkg.name.version + }); + + format!( + "{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 { + if let Some(version) = &pkg.name.version { + let version = version + .to_string() + .replace('.', "_") + .replace('-', "_") + .replace('+', "_"); + format!("_{version}") + } else { + String::default() + } + } else { + String::default() + } + ) +} + +fn get_interface_fqn( + root_pkg: &str, + interface_id: &WorldKey, + world_fqn: &str, + resolve: &Resolve, + direction: Option, +) -> String { + match interface_id { + WorldKey::Name(name) => { + 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]; + + format!( + "{}.{}.{}", + get_package_fqn(root_pkg, 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", + }, + ) + } + } +} + +fn get_world_fqn(root_pkg: &str, id: WorldId, resolve: &Resolve) -> String { + let world = &resolve.worlds[id]; + format!( + "{}.{}", + get_package_fqn(root_pkg, world.package.unwrap(), resolve), + escape_d_identifier(&world.name.to_snake_case()) + ) +} + +impl D { + 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(), + stub_src: Source::default(), + stubs: Vec::default(), + fqn: "", + r#gen: self, + resolve, + interface: None, + name: name, + sizes, + direction, + + wasm_import_module, + + return_pointer_area_size: Default::default(), + return_pointer_area_align: Default::default(), + } + } + + 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) -> Result<()> { + self.root_pkg = self.opts.root_package.as_deref().unwrap_or("wit").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); + 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( + &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, + Some(Direction::Import), + )); + } + } + + result + }); + (*fqns).import = Some(get_interface_fqn( + &self.root_pkg, + &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( + &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, + Some(Direction::Export), + )); + } + } + + result + }); + (*fqns).export = Some(get_interface_fqn( + &self.root_pkg, + &name, + &self.world_fqn, + resolve, + Some(Direction::Export), + )) + } + _ => {} + } + } + + Ok(()) + } + + fn import_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + 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); + + 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( + ["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()); + + //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, + _world: WorldId, + 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); + } + + self.type_imports_src = take(&mut r#gen.src); + } + + fn import_funcs( + &mut self, + resolve: &Resolve, + _world: WorldId, + 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); + } + + self.function_imports_src = take(&mut r#gen.src); + } + + fn export_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + id: InterfaceId, + files: &mut Files, + ) -> Result<()> { + self.used_interfaces.insert((name.clone(), 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 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(); + + 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); + } + + r#gen.types(id); + + 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 { + 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!(\"{wasm_import_module}\", \"{type_name}\", Impl);\n" + )); + + 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 => {} + 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(&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 { + r#gen.stub_src.push_str("}\n"); + } + } + _ => {} + } + } + + 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\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( + ["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()); + + self.cur_interface = None; + Ok(()) + } + + fn export_funcs( + &mut self, + resolve: &Resolve, + _world: WorldId, + 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 => { + r#gen.export_func(func); + } + _ => {} + } + } + + let ret_area_decl = r#gen.emit_ret_area_if_needed(); + + 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(()) + } + + 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(); + + 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); + + 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()); + } + } + + 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(&format!("import {};\n\n", self.common_module)); + world_src.push_str( + &self + .interface_imports + .iter() + .map(|fqn| format!("public import {fqn};")) + .collect::>() + .join("\n"), + ); + + world_src.push_str("\n"); + + world_src.push_str( + &self + .interface_exports + .iter() + .map(|fqn| format!("public import {fqn};")) + .collect::>() + .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("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(&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); + } + + // 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"); + + 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!( + " + 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!( + \"\", + \"\", + `!wasm.custom_sections = !{{!0}} + !0 = !{{!\"component-type:wit-bindgen:{version}:{pkg}:{world_name}:{opts_suffix}\", !\"{}\"}}`, + void + ); + }} + ", + self.root_pkg, + &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( + ["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()); + + 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(()) + } +} + +struct DInterfaceGenerator<'a> { + src: Source, + stub_src: Source, + stubs: Vec, + 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, + + return_pointer_area_size: ArchitectureSize, + return_pointer_area_align: Alignment, +} + +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, 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); + + 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::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::String => Cow::Borrowed("WitString"), + Type::Id(id) => { + let typedef = &self.resolve.types[*id]; + + match typedef.owner { + TypeOwner::None => match &typedef.kind { + 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::Owned(self.scoped_type_name(*id, from_module_fqn)) + } + TypeDefKind::Handle(Handle::Borrow(id)) => { + Cow::Owned(self.scoped_type_name(*id, from_module_fqn) + ".Borrow") + } + TypeDefKind::Tuple(t) => Cow::Owned(format!( + "Tuple!({})", + t.types + .iter() + .map(|ty| self.type_name(ty, from_module_fqn).into_owned()) + .collect::>() + .join(", ") + )), + TypeDefKind::Option(o) => { + Cow::Owned(format!("Option!({})", self.type_name(o, from_module_fqn))) + } + TypeDefKind::Result(r) => Cow::Owned(format!( + "Result!({}, {})", + 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!({})", + self.type_name(&ty, from_module_fqn) + )), + TypeDefKind::Future(_) => { + todo!("type_name of `future`") + } + TypeDefKind::Stream(_) => { + todo!("type_name of `stream`") + } + TypeDefKind::FixedLengthList(ty, size) => { + Cow::Owned(format!("{}[{size}]", self.type_name(ty, from_module_fqn))) + } + TypeDefKind::Map(_, _) => todo!("type_name of `map`"), + TypeDefKind::Unknown => unimplemented!(), + unhandled => { + panic!( + "Encountered unexpected `type_name` invocation of ownerless typedef: {unhandled:?}." + ); + } + }, + _ => Cow::Owned(self.scoped_type_name(*id, from_module_fqn)), + } + } + Type::ErrorContext => todo!(), + } + } + + 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, 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(|| { + 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) => { + if *world_id != self.r#gen.world_id.unwrap() { + panic!("Dealing with type from different world?"); + } + + Some(&self.r#gen.world_fqn) + } + } + } + + fn prologue(&mut self) { + let id = self.interface.unwrap(); + + let interface = &self.resolve.interfaces[self.interface.unwrap()]; + + self.src.push_str(&format!( + "/++\n{}\n+/\n", + interface.docs.contents.as_deref().unwrap_or_default() + )); + + 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({version}):\n")); + } + + self.src + .push_str(&format!("\nimport {};\n\n", self.r#gen.common_module)); + 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"); + } + + let mut deps = BTreeSet::new(); + + for dep_id in self.resolve.interface_direct_deps(id) { + deps.insert(dep_id); + } + + 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, + } + )); + + 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 {common_fqn};\n")); + + if let Some(fqn) = directional_fqn { + self.src.push_str(&format!("static import {fqn};\n")); + }; + } + } + 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 { + let type_info = &self.r#gen.types.get(id); + + type_info.has_resource + } + + fn get_d_signature(&mut self, func: &Function) -> DSig { + match &func.kind { + FunctionKind::Freestanding + | FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => {} + + FunctionKind::AsyncFreestanding + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => { + todo!() + } + } + + let mut res = DSig::default(); + + let split_name = match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => &func.name, + FunctionKind::Constructor(_) => "", + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => func.name.split(".").skip(1).next().unwrap(), + }; + + let lower_name = split_name.to_lower_camel_case(); + 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, + }; + + 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))); + + 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); + + let needs_in_qualifier = match param { + Type::ErrorContext | Type::String | Type::Id(_) => true, + _ => false, + }; + + res.arguments.push(( + escaped_param_name.into(), + if needs_in_qualifier { + "in ".to_owned() + } else { + "".to_owned() + } + &self.type_name(¶m, self.fqn), + )); + } + + res + } + + fn import_func(&mut self, func: &Function) { + match &func.kind { + FunctionKind::Freestanding + | FunctionKind::Constructor(_) + | FunctionKind::Method(_) + | FunctionKind::Static(_) => {} + kind => { + todo!("Import {kind:?} - {}\n", func.name); + } + } + + 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!( + "{} {}({}) @trusted nothrow {{\n", + d_sig.result, + d_sig.name, + d_sig + .arguments + .iter() + .map(|(name, ty)| ty.to_owned() + " " + 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, + 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"); + + 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 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 + .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) {} __import_{}({}) nothrow;\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() + .map(|param| wasm_type(*param)) + .collect::>() + .join(", ") + )); + } + + fn export_func(&mut self, func: &Function) { + match &func.kind { + FunctionKind::Freestanding + | FunctionKind::Constructor(_) + | FunctionKind::Method(_) + | FunctionKind::Static(_) => {} + kind => { + todo!("Export {kind:?} - {}\n", func.name); + } + } + + 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 = {} function({});\n", + d_sig.name, + d_sig.result, + d_sig + .arguments + .iter() + .map(|(name, ty)| ty.to_owned() + " " + 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, + d_sig.implicit_self, + match &func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => "Impl", + _ => { + "witExportsIn!_Resource_Impl" + } + } + )); + + if self.r#gen.opts.emit_export_stubs { + self.stub_src.push_str(&format!( + "@witExport(\"{}\", \"{}\")\n", + self.wasm_import_module.unwrap(), + 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 + .arguments + .iter() + .map(|(name, ty)| ty.to_owned() + " " + name) + .collect::>() + .join(", ") + )); + + match func.kind { + FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { + self.stubs.push(d_sig.name.clone() + "_STUB"); + } + _ => {} + } + } + + 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!(\"{export_name}\")\n")); + + self.src.push_str(&format!( + "pragma(mangle, \"__wit_export_{}\")\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) {} __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, + 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); + self.return_pointer_area_align = self + .return_pointer_area_align + .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"); + + 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); + + 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() + } + } + + 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> { + fn resolve(&self) -> &'a Resolve { + self.resolve + } + + // 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); + } + } + } + + 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 owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner, false) + .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")); + + 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 { + 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!( + "{} {escaped_name};\n", + self.type_name(&field.ty, &owner_fqn) + )); + } + + 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); + + 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 @nogc nothrow {{\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"); + } + + 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) => { + 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({}) uint __handle = 0; + + package({0}) this(uint handle) @safe @nogc nothrow {{ + __handle = handle; + }} + + @disable this(); + + ", + self.r#gen.root_pkg + )); + + 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( + "\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(), + 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) @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); + alias borrow this; + + struct Borrow {{ + package({}) uint __handle = 0; + + package({0}) this(uint handle) @safe @nogc nothrow {{ + __handle = handle; + }} + + @disable this(); + + void witFree() @safe @nogc nothrow {{}} + Borrow witClone() const @safe @nogc nothrow {{ return Borrow(__handle); }} + ", + self.r#gen.root_pkg + )); + + 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); + } + } + } + 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 + && 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({}) uint __handle = 0; + + package({0}) this(uint handle) @safe @nogc nothrow {{ + __handle = handle; + }} + + @disable this(); +", + self.r#gen.root_pkg + )); + + 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; + + dg(*ptr); + return {escaped_name}(__import_makeNew(ptr)); + }} + ", + )); + self.src.push_str(&format!( + "@wasmImport!(\"[export]{}\", \"[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)() @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", + 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() @trusted @nogc nothrow {\n__import_drop(__handle);\n}\n", + ); + self.src.push_str(&format!( + "@wasmImport!(\"[export]{}\", \"[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) @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() @safe @nogc nothrow => Borrow(__handle); + alias borrow this; + + struct Borrow {{ + package({}) uint __handle = 0; + + package({0}) this(uint handle) @safe @nogc nothrow {{ + __handle = handle; + }} + + @disable this(); + + void witFree() @safe @nogc nothrow {{}} + Borrow witClone() const @safe @nogc nothrow {{ return Borrow(__handle); }} + + ", + self.r#gen.root_pkg + )); + + self.src.push_str("}\n"); + + self.src.push_str("}\n"); + } + TypeOwner::World(_) => unimplemented!("resource exports in worlds"), + TypeOwner::None => { + panic!("Resource definition without owner?"); + } + }, + } + } + + 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); + + 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, false) + .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(", ") + )); + } + + 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); + + 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+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + self.src.push_str(&format!("struct {escaped_name} {{\n")); + + 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", + }; + + let owner_fqn = self + .type_owner_fqn(&self.resolve.types[id].owner, false) + .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.push_str("mixin WitVariant!(\n"); + self.src.indent(1); + + for case in &variant.cases { + self.src.push_str(&format!( + "{}, // {}\n", + self.optional_type_name(case.ty.as_ref(), &owner_fqn), + escape_d_identifier(&case.name.to_lower_camel_case()) + )); + } + + self.src.deindent(1); + 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 + .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()) + )); + } + + self.src.push_str("}\n"); + + self.src + .push_str("Tag tag() const @safe @nogc nothrow pure => _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); + + 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", + )); + + if case.ty.is_some() { + self.src.push_str(&format!( + "///ditto\nalias get{escaped_upper_case_name} = _get!(Tag.{escaped_lower_case_name});\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 { + 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 @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(); + 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"); + } + + 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); + + 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, false) + .unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Option!({});", + self.type_name(payload, owner_fqn) + )); + } + + 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, false) + .unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = Result!({}, {});", + self.optional_type_name(result.ok.as_ref(), owner_fqn), + self.optional_type_name(result.err.as_ref(), owner_fqn), + )); + } + + 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", + }; + + 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()) + )); + } + + self.src.push_str("}"); + } + + 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); + + self.src.push_str(&format!( + "\n/++\n{}\n+/\n", + docs.contents.as_deref().unwrap_or_default() + )); + + let typename = self.type_name( + alias_ty, + self.type_owner_fqn(&self.resolve.types[id].owner, false) + .unwrap(), + ); + + self.src + .push_str(&format!("alias {escaped_name} = {typename};\n")); + } + + 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, false) + .unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = WitList!({});", + self.type_name(ty, owner_fqn) + )); + } + + 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); + + 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, false) + .unwrap(); + self.src.push_str(&format!( + "alias {escaped_name} = {}[{size}];", + self.type_name(ty, owner_fqn) + )); + } + + 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}"); + } + + fn type_stream(&mut self, _id: TypeId, name: &str, _ty: &Option, _docs: &Docs) { + todo!("def of `stream` - {name}"); + } + + fn type_builtin(&mut self, _id: TypeId, name: &str, _ty: &Type, _docs: &Docs) { + todo!("def of `builtin` - {name}"); + } +} + +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, + /// intermediate calculations for contained objects + blocks: Vec, + payloads: Vec, + return_pointer_area_size: ArchitectureSize, + return_pointer_area_align: Alignment, + needs_deallocate: bool, +} + +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(), + needs_deallocate: false, + } + } + + 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() { + 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; + + 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::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( + match ty { + WasmType::Pointer => "null", + _ => "0", + } + .to_string(), + ); + } + } + + abi::Instruction::I32Load { offset } => self.load("uint", *offset, operands, results), + 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::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::I32Store8 { offset } => self.store("ubyte", *offset, operands), + abi::Instruction::I32Store16 { offset } => self.store("ushort", *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::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::I32FromS32 => top_as("uint"), + 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 => results.push(operands.pop().unwrap()), + abi::Instruction::S64FromI64 => top_as("long"), + abi::Instruction::U64FromI64 => results.push(operands.pop().unwrap()), + 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::ListCanonLower { .. } | abi::Instruction::StringLower { .. } => { + results.push(format!("cast(void*)({}.ptr)", operands[0])); + results.push(format!("{}.length", operands[0])); + } + abi::Instruction::ListLower { element, .. } => { + let Block { + body, + 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_src = tempname("_listSrc", tmp); + + 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", + 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!("if ({list_src}.length) deallocate ~= {list};\n")); + } + + self.push_str(&format!( + "foreach ({block_element}_idx, 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"); + + if !matches!(self.r#gen.direction, Some(Direction::Import)) { + self.push_str(&format!( + "if ({list_src}.length) {}.free({list_src}.ptr);\n", + self.r#gen.r#gen.common_module + )); + } + + 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 {len} = {}; + auto {ptr} = {len} ? cast({elem_name}*)({}) : null; + ", + 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 => { + let tmp = self.tmp(); + + let ptr = tempname("_ptr", tmp); + let len = tempname("_len", tmp); + + self.push_str(&format!( + "auto {len} = {}; + auto {ptr} = {len} ? cast(char*)({}) : null; + ", + 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, .. } => { + 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 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} = {list_len} ? {}.mallocSlice!({elem_type_name})({list_len}) : []; + assert(!{list_len} || {list}.ptr);\n", + self.r#gen.r#gen.common_module + )); + + if matches!(self.r#gen.direction, Some(Direction::Export)) { + self.needs_deallocate = true; + self.push_str(&format!( + "if ({list_len}) deallocate ~= cast(void*){list}.ptr;\n" + )); + } + + self.push_str(&format!( + "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); + self.push_str(&format!("{block_element} = {};", block_results[0])); + self.push_str("\n}\n"); + + 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); + results.push(format!("{list_name}({list})")); + } + + 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 { size, .. } => { + for i in 0..(*size as usize) { + results.push(format!("{}[{i}]", operands[0])); + } + } + abi::Instruction::FixedLengthListLowerToMemory { element, .. } => { + let Block { + body, + results: _, + element: block_element, + 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, ref {block_element}; {arr_src}) {{\n" + )); + self.push_str(&format!( + "const auto {base} = {arr_dst} + {block_element}_idx * {size_str};\n" + )); + self.push_str(&body); + self.push_str("\n}\n"); + } + 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 { .. } => { + 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(); + 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 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::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 { 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::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::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); + + 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 ((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); + + let uppper_name = case.name.to_upper_camel_case(); + let upper_escaped_name = escape_d_identifier(&uppper_name); + + 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!( + "{}ref {ty_name} {payload} = {}.get{upper_escaped_name}();\n", + if matches!(self.r#gen.direction, Some(Direction::Import)) { + "const " + } else { + "" + }, + operands[0], + )); + } + self.src.push_str(&block.body); + + 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); + 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!("alias {tag_type} = {ty}.Tag;\n")); + self.push_str(&format!("final switch (cast({ty}.Tag){tag}) {{\n")); + 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); + + let payload = tempname("_payload", self.tmp()); + + 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])); + &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::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, + .. + } => { + 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(); + + 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 Block { + body: some, + results: some_results, + .. + } = self.blocks.pop().unwrap(); + let Block { + results: 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}.makeSome({some_value}); + }} else {{ + {resultname} = {type_name}.makeNone; + }} + " + )); + results.push(format!("{resultname}")); + } + + abi::Instruction::ResultLower { + results: result_types, + result, + .. + } => { + 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(); + + 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 Block { + body: err, + results: err_results, + .. + } = self.blocks.pop().unwrap(); + assert!(err_results.len() == (result.err.is_some() as usize)); + let Block { + body: 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); + 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::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 = if name.starts_with("[constructor]") { + "makeNew" + } else { + 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().cloned().collect::>().join(", ") + )); + + if self.needs_deallocate { + self.push_str(&format!("deallocate.purge();\n")); + } + } + 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::Constructor(_) => "", + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::AsyncMethod(_) + | FunctionKind::AsyncStatic(_) => func.name.split(".").skip(1).next().unwrap(), + }; + + let lower_name = split_name.to_lower_camel_case(); + let escaped_name = if let FunctionKind::Constructor(_) = &func.kind { + "constructor" + } else { + 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}_Impl)(" + )); + true + } + }; + self.src.push_str( + &operands + .iter() + .skip(if implicit_self { 1 } else { 0 }) + .cloned() + .collect::>() + .join(", "), + ); + self.src.push_str(");\n"); + + if self.needs_deallocate { + self.push_str(&format!("deallocate.purge();\n")); + } + } + abi::Instruction::Return { amt, .. } => 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::Malloc { .. } => { + todo!("instr: Malloc") + } + abi::Instruction::GuestDeallocate { .. } => { + self.push_str(&format!("free({});", operands[0])); + } + abi::Instruction::GuestDeallocateString { .. } => { + self.push_str(&format!("if ({} > 0) {{\n", operands[1])); + self.push_str(&format!("free({});\n", operands[0])); + self.push_str("}\n"); + } + abi::Instruction::GuestDeallocateList { element } => { + let Block { + body, + 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_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 { + 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 {i}: {{\n")); + 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") + } + + abi::Instruction::Flush { amt } => { + for op in operands.iter().take(*amt) { + let result = tempname("_flush", self.tmp()); + self.push_str(&format!("auto {result} = {op};\n")); + 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 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 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 { + &self.r#gen.sizes + } + + fn is_list_canonical(&self, _resolve: &Resolve, ty: &Type) -> bool { + self.r#gen.resolve.all_bits_valid(ty) + } +} diff --git a/crates/d/src/wit_common.d b/crates/d/src/wit_common.d new file mode 100644 index 000000000..3077bcbf3 --- /dev/null +++ b/crates/d/src/wit_common.d @@ -0,0 +1,517 @@ +import core.attribute : mustuse; +import ldc.attributes : llvmAttr; + +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: + T* ptr; + size_t length; + + this(inout T[] slice) inout @trusted { + ptr = slice.ptr; + length = slice.length; + } + + 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; + } + + 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); + +// 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 = WitList!(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; +} + +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); + + T bits; + + @safe nothrow @nogc pure: + + static typeof(this) opIndex(size_t i) + in(i < T.sizeof*8) => F(cast(T)(1 << i)); + + auto opUnary(string op : "~")() const => F(~bits); + + auto ref opOpAssign(string op)(F rhs) + if (op == "|" || op == "&" || op == "^") + { + mixin("bits "~op~"= rhs.bits;"); + return this; + } + + auto opBinary(string op)(F flags) const + if (op == "|" || op == "&" || op == "^") + { + F result = this; + result.opOpAssign!op(flags); + return result; + } + + typeof(this) witClone() const { return this; } +} + + +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, inout Storage storage = Storage.init) inout @nogc nothrow @trusted { + _tag = tag; + _storage = storage; + } + + + static auto _create(Tag tag)() if (is(Types[tag] == void)) { + return typeof(this)(tag); + } + static auto _create(Tag tag)(inout Types[tag] val) if (!is(Types[tag] == void)) { + Storage storage = Storage.init; + 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)) + 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; + + this(bool present, inout T value) inout @safe @nogc nothrow { + _present = present; + _value = value; + } +public: + static inout(Option) makeSome(inout T value) @safe @nogc nothrow { + return inout Option(true, value); + } + + static Option makeNone() @safe @nogc nothrow { + return Option(false, T.init); + } + + 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 @trusted @nogc nothrow return + in (_present) do { return _value; } + + 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)) + { return _present ? _value : fallback(); } +} + +auto some(T)(inout T value) @safe @nogc nothrow { + return Option!T.makeSome(value); +} + +auto none(T)() @safe @nogc nothrow { + return Option!T.makeNone; +} + +/// Based on Rust's Result +@mustuse +struct Result(T = void, E = void) { +private: + bool _hasError; + union Storage { + ubyte __zeroinit = 0; + static if (!is(T == void)) { + T value; + } + static if (!is(E == void)) { + E error; + } + } + Storage _storage; + + this(bool hasError, inout(Storage) storage) inout @safe @nogc nothrow { + _hasError = hasError; + _storage = storage; + } + +public: + static if (is(T == void)) { + static Result ok() @safe @nogc nothrow => Result(false, Storage.init); + } else { + static Result ok(inout(T) value) @trusted @nogc nothrow { + Storage newStorage = Storage.init; + newStorage.value = cast(T)value; + + return Result(false, cast(inout Storage)newStorage); + } + } + + static if (is(E == void)) { + static Result err() @safe @nogc nothrow => Result(true, Storage.init); + } else { + static inout(Result) err(inout(E) error) @trusted @nogc nothrow { + Storage newStorage = Storage.init; + newStorage.error = cast(E)error; + + return inout Result(true, cast(inout Storage)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 @trusted @nogc nothrow return + in (isOk) do { return _storage.value; } + + 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)) + { return isOk ? _storage.value : fallback(); } + } + + static if (!is(E == void)) { + ref inout(E) unwrapErr() inout @trusted @nogc nothrow return + in (isErr) do { return _storage.error; } + } +} + + +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.makeSome(val.unwrap.witClone); + } else { + return T.makeSome; + } + } else { + return T.makeNone; + } +} + +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 = void; + static foreach (F; T.tupleof) { + __traits(child, clone, F) = __traits(child, val, F).witClone; + } + 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: + +extern(C) @nogc nothrow { + void* malloc(size_t size); + void* realloc(void* ptr, size_t newSize); + void free(void* ptr); + noreturn abort(); +} + +// 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; } + return tmp(from).to; +} + +T[] mallocSlice(T)(size_t count) @nogc nothrow { + if (count == 0) return []; + auto ptr = malloc(count*T.sizeof); + if (ptr is null) return []; + + return (cast(T*)ptr)[0..count]; +} + +// from std.meta +alias AliasSeq(T...) = T; + + +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)), + "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) && __traits(isStaticFunction, findWitExportFunc) != implicitSelf, + "The implementation of '", mod, "#", name, "' ", + "`", __traits(fullyQualifiedName, findWitExportFunc), "` ", + "must conform to the necessary signature. ", + "Found `", typeof(&findWitExportFunc), "`", + ", 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); + } + } + } + } +} + +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(); + } +} + +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) { + if (newSize == 0) return cast(void*)alignment; + void *ret = realloc(ptr, newSize); + if (!ret) abort(); + return ret; +} diff --git a/crates/test/d-test-support/runtime.d b/crates/test/d-test-support/runtime.d new file mode 100644 index 000000000..c03ed2c9f --- /dev/null +++ b/crates/test/d-test-support/runtime.d @@ -0,0 +1,31 @@ +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; +} + +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 new file mode 100644 index 000000000..83bbc3147 --- /dev/null +++ b/crates/test/d-test-support/walloc.d @@ -0,0 +1,548 @@ +// 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 + + 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: + +/// MODIFIED FOR wit-bindgen TESTS +enum MAX_ALLOCATIONS = 2048; +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 { + if (size == 0) + return null; + + size_t granules = size_to_granules(size); + chunk_kind kind = granules_to_chunk_kind(granules); + + /// MODIFIED FOR wit-bindgen TESTS + auto result = (kind == chunk_kind.LARGE_OBJECT) ? allocate_large(size) : allocate_small(kind); + assert(result !is null); + foreach (i, ref ptr; activePointers) { + if (ptr !is null) continue; + ptr = result; + activeAllocSizes[i] = size; + walloc_allocated_bytes += size; + return result; + } + assert(0); + /// END +} + +export +void free(void *ptr) @nogc nothrow @system { + /// MODIFIED FOR wit-bindgen TESTS + assert(ptr !is null); + + bool found = false; + foreach (i, ref existingPtr; activePointers) { + if (ptr !is existingPtr) continue; + existingPtr = null; + walloc_allocated_bytes -= activeAllocSizes[i]; + found = true; + break; + } + assert(found); + /// END + + _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 new file mode 100644 index 000000000..8870be079 --- /dev/null +++ b/crates/test/src/d.rs @@ -0,0 +1,165 @@ +use crate::{Compile, LanguageMethods, Runner, Verify}; +use anyhow::{Context, Result}; +use clap::Parser; +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, + _runner: &Runner, + name: &str, + config: &crate::config::WitConfig, + _args: &[String], + ) -> bool { + config.async_ || config.error_context || name == "map.wit" || name == "issue1642.wit" + } + + 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<()> { + let mut cmd = Command::new(compiler); + + let output = compile.output.with_extension("core.wasm"); + + std::fs::write( + compile.artifacts_dir.join("runtime.d"), + include_bytes!("../d-test-support/runtime.d"), + )?; + std::fs::write( + compile.artifacts_dir.join("walloc.d"), + include_bytes!("../d-test-support/walloc.d"), + )?; + + cmd.arg(&compile.component.path) + .arg("-betterC") // don't allow features needing DRuntime + .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") // 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("runtime.d")) + .arg(compile.artifacts_dir.join("walloc.d")); + + 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<()> { + 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) +} diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index 7a54f4612..de496275b 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), } @@ -451,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)?), }; @@ -1322,6 +1325,7 @@ impl Language { Language::Csharp, Language::MoonBit, Language::Go, + Language::D, ]; fn obj(&self) -> &dyn LanguageMethods { @@ -1333,6 +1337,7 @@ impl Language { Language::Csharp => &csharp::Csharp, Language::MoonBit => &moonbit::MoonBit, Language::Go => &go::Go, + Language::D => &d::D, Language::Custom(custom) => custom, } } 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()), }; 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..88a201de7 --- /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(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(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(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(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..decba03b1 --- /dev/null +++ b/tests/runtime/flavorful/runner.d @@ -0,0 +1,93 @@ +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(const ListInRecord3("list_in_record3 input".witList)); + scope(exit) result.witFree; + + assert( + result.a + == "list_in_record3 output" + ); + } + + { + auto result = fListInRecord4(const ListInAlias("input4".witList)); + scope(exit) result.witFree; + + assert( + result.a + == "result4" + ); + } + + fListInVariant1(some("foo".witList), Result!(void, WitString).err("bar".witList)); + + { + auto result = fListInVariant2(); + scope(exit) result.witFree; + + + assert( + result + == some("list_in_variant2".witList) + ); + } + + { + auto result = fListInVariant3(some("input3".witList)); + scope(exit) result.witFree; + + assert( + result + == some("output3".witList) + ); + } + + { + auto errno = errnoResult(); + assert(errno.isErr && errno.unwrapErr == MyErrno.b); + } + assert(errnoResult().isOk); + + { + immutable WitString[1] input = ["typedef2".witList]; + auto result = listTypedefs("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"); + } + + { + 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); + } +} + +alias Exports = wit.test.flavorful.runner.Exports!( + run +); diff --git a/tests/runtime/flavorful/test.d b/tests/runtime/flavorful/test.d new file mode 100644 index 000000000..d1b25e209 --- /dev/null +++ b/tests/runtime/flavorful/test.d @@ -0,0 +1,106 @@ +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 tuple( + (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 tuple( + boolsOut.witList, + resultsOut.witList, + enumsOut.witList + ).witClone; +} + +alias Exports = wit.test.flavorful.test.Exports!( + fListInRecord1, + fListInRecord2, + fListInRecord3, + fListInRecord4, + fListInVariant1, + fListInVariant2, + fListInVariant3, + errnoResult, + listTypedefs, + listOfVariants +); 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-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/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 +); 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 +); 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 +);